thats-me/frontend/dev/UMSETZUNG-FLOATING-LINES.md
2026-03-06 13:56:20 +01:00

387 lines
15 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# FloatingLines Migration & Event-Integration
**Stand:** 20. Februar 2026
**Bereich:** Frontend (Quasar/Vue.js 3)
---
## 1. Zusammenfassung
Die alte LifeWave-Visualisierung (zwei Versionen: Spline + Glow) wurde komplett durch eine einzige **FloatingLines**-Implementierung ersetzt. Diese basiert auf einem WebGL-Fragment-Shader (Three.js), der animierte Bezier-Linien zwischen Event-Punkten zeichnet, inklusive farbiger Glow-Kreise pro Event.
### Was wurde gemacht
1. **Shader aus `dev/floating-lines.js` migriert**`FloatingLines.vue`
2. **Settings aus `dev/init-fl.html` migriert**`LifeWaveSettings.vue` + `settings.js` Store
3. **Event-Punkte mit Shader synchronisiert** — Shader-Kreise sitzen exakt auf den GlowDots
4. **Per-Event-Farben** — Jeder Shader-Kreis und jedes Liniensegment nutzt die Emotion-Farbe des Events
5. **GlowDot vereinfacht** — Nur noch weißer Kreis + Bild als Klick-Target, Glow kommt vom Shader
6. **Kreisgröße synchronisiert** — Settings-Slider steuert Shader-Kreis UND DOM-Dot
7. **Zoom entkoppelt** — Zoom ändert Abstände, nicht Kreisgrößen
### Gelöschte Dateien
- `LifeWavePath.vue` — Alte SVG-Pfad-Visualisierung
- `LifeWaveSpline.vue` — Alte Spline-Kurven-Variante
- `LifeWaveGlow.vue` — Alte Glow-Effekt-Variante
- `WaveSettings.vue` — Alte Settings (ersetzt durch `LifeWaveSettings.vue`)
---
## 2. Architektur-Übersicht
```
┌─────────────────────────────────────────────────────────┐
│ LifeWaveLayout.vue │
│ │
│ ┌─────────────────────────────────────────────────┐ │
│ │ FloatingLines.vue (WebGL Fullscreen) │ │
│ │ - Fragment Shader (GLSL) │ │
│ │ - Bezier-Linien zwischen Events │ │
│ │ - Glow-Kreise pro Event (pointColor[]) │ │
│ │ - Animierte Wellen │ │
│ │ - Background Gradient + optionales Bild │ │
│ └─────────────────────────────────────────────────┘ │
│ │
│ ┌─────────────────────────────────────────────────┐ │
│ │ TimelineView.vue (scrollbar, z-index: 5) │ │
│ │ - Horizontales Scroll-Container │ │
│ │ - GlowDot pro Event (Klick-Target) │ │
│ │ - Monat/Jahr-Labels │ │
│ │ - Pinch-to-Zoom │ │
│ │ - Emittiert @view-update an Layout │ │
│ └─────────────────────────────────────────────────┘ │
│ │
│ Header | AddEventButton | EventPanel | LifeWaveSettings│
└─────────────────────────────────────────────────────────┘
```
---
## 3. Datenfluss
### 3.1 Event-Positionen → Shader
```
TimelineView LifeWaveLayout FloatingLines
───────────── ────────────── ──────────────
displayEvents ──@viewUpdate──► onViewUpdate()
(emotion, x, color) │
├─ shaderNumPoints (computed)
├─ shaderPointX[] (computed) ──► pointX[8] uniform
├─ shaderPointY[] (computed) ──► pointY[8] uniform
└─ shaderPointColors[] (comp.) ──► pointColor[8] uniform
```
**Koordinaten-Konvertierung (Screen → Shader UV):**
```js
// Layout: screenToUV(sx, sy)
// sx, sy = CSS-Pixel vom oberen linken Viewport-Rand
function screenToUV(sx, sy) {
const w = layoutWidth // = 100dvh Breite
const h = layoutHeight // = 100dvh Höhe
return {
x: (2 * sx - w) / h,
y: (2 * sy - h) / h
}
}
```
```glsl
// Shader: gleiche Formel
vec2 baseUv = (2.0 * fragCoord - iResolution.xy) / iResolution.y;
baseUv.y *= -1.0; // Y-Flip (CSS: top→bottom, GL: bottom→top)
```
**GlowDot Y-Position:**
```
yPercent = 50 - emotion * 35
emotion +1.0 → top (15%)
emotion 0.0 → mitte (50%)
emotion -1.0 → unten (85%)
```
**Screen Y für Shader:**
```
TIMELINE_TOP = 60px (CSS: .timeline { top: 60px })
screenY = TIMELINE_TOP + (yPercent / 100) * containerHeight
```
### 3.2 Emotion-Slider → Live-Update
```
EventPanel Events Store TimelineView Shader
────────── ──────────── ──────────── ──────
v-model="ghostEmotion" ──► ghostEmotion (ref)
├─ watch → persistToEvent()
│ (updates events[])
└─ sortedEvents (computed) ──► displayEvents
└─ watch → emitViewState()
Layout: shaderPointY[]
Layout: shaderPointColors[]
FloatingLines: watch → uniform update
```
### 3.3 Event-Farben
Jeder Event hat eine Glow-Farbe basierend auf:
1. `event.customColor` (falls gesetzt, hat Priorität)
2. `emotionToColor(emotion, gradientPreset)` — interpoliert zwischen 3 Farben
```
events.js: getGlowColor(event)
→ customColor || emotionToColor(emotion, gradientPreset)
10 Gradient-Presets: Standard, Sunset, Earth, Ocean, Spring,
Neon, Pastel, Aurora, Forest, Berry
```
Die Farbe fließt als `pointColor[8]` Uniform in den Shader:
- **Kreise:** `vec3 circCol = pointColor[p]`
- **Liniensegmente:** `vec3 lineCol = mix(pointColor[s], pointColor[s+1], t_seg)`
---
## 4. Komponenten-Referenz
### 4.1 FloatingLines.vue
**Zweck:** Fullscreen WebGL-Hintergrund mit animierten Bezier-Linien und Glow-Kreisen.
**Technologie:** Three.js mit custom Fragment Shader (GLSL).
**Props:**
| Prop | Typ | Default | Beschreibung |
|------|-----|---------|-------------|
| `numPoints` | Number | 0 | Anzahl aktiver Punkte (max 8) |
| `pointXValues` | Array | [] | X-UV-Koordinaten der Punkte |
| `pointYValues` | Array | [] | Y-UV-Koordinaten der Punkte |
| `pointColors` | Array | [] | Hex-Farben pro Punkt (z.B. '#ff0000') |
| `lineCount` | Array/Number | [10] | Anzahl Wellenlinien |
| `animationSpeed` | Number | 1 | Geschwindigkeit der Wellenanimation |
| `lineSpread` | Number | 0.05 | Wellenamplitude |
| `fanSpread` | Number | 0.05 | Fächerbreite der Linien |
| `lineSharpness` | Number | 8.0 | Feinheit/Schärfe der Linien |
| `waveFrequency` | Number | 7.0 | Welligkeit |
| `bezierCurvature` | Number | 0.2 | Kurvenstärke der Bezier-Verbindungen |
| `circleRadiusPx` | Number | 75 | Kreisradius in Pixeln |
| `circleGlowSize` | Number | 18 | Glow-Ausdehnung um den Kreis |
| `circleGlowStrength` | Number | 1.5 | Glow-Intensität |
| `linesGradient` | Array | [...] | Hex-Farbwerte für Linien-Gradient |
| `bgColorCenter` | String | '#0a0514' | Hintergrundfarbe Mitte |
| `bgColorEdge` | String | '#000000' | Hintergrundfarbe Rand |
| `backgroundImage` | String | '' | URL für Hintergrundbild |
| `mixBlendMode` | String | 'screen' | CSS Blend-Mode des Canvas |
**Shader-Architektur:**
- `drawCircle()` — Zeichnet weißen Kern + farbigen Glow + Fog
- `waveFocal()` — Berechnet Wellenlinien entlang Bezier-Segmenten
- `bezierClosestT()` — Findet nächsten Punkt auf quadratischer Bezier-Kurve
- `mainImage()` — Compositing: Background + Segmente + Kreise
### 4.2 GlowDot.vue
**Zweck:** Klickbarer DOM-Overlay pro Event (weißer Kreis + optionales Bild).
**Größe:** Dynamisch aus `settingsStore.floatingLines.circleRadius`:
```js
const dpr = Math.min(window.devicePixelRatio || 1, 2)
const dotSize = 2 * circleRadius / dpr // Matches shader circle
```
**Kein Zoom-Scaling** — Größe ist konstant, unabhängig vom Zoom-Level.
**Props:** `event`, `x`, `isGhost`, `selected`
### 4.3 TimelineView.vue
**Zweck:** Horizontal scrollbarer Container mit GlowDots und Labels.
**CSS-Position:** `top: 60px; bottom: 70px` (unterhalb Header, oberhalb AddButton)
**Features:**
- Pinch-to-Zoom (Touch + Ctrl+Wheel)
- Zoom-Range: 0.4x 3.0x
- Scroll-to-center beim Mount (letztes Event)
- Ghost-Event-Insertion bei Panel-Open (Create-Mode)
**Emits:**
- `@dotSelect(eventId)` — Event angeklickt
- `@viewUpdate({ scrollLeft, viewportWidth, containerHeight, events[] })` — Bei jedem Scroll/Zoom/Resize/Event-Change
### 4.4 LifeWaveLayout.vue
**Zweck:** Haupt-Layout, orchestriert alle Komponenten.
**Verantwortlichkeiten:**
- Empfängt `@view-update` von TimelineView
- Konvertiert Screen-Pixel → Shader-UV-Koordinaten
- Berechnet `shaderNumPoints`, `shaderPointX[]`, `shaderPointY[]`, `shaderPointColors[]`
- Reicht Settings-Store-Werte an FloatingLines weiter
- Parsed Gradient-Stops aus dem Textarea-String
**Wichtige Konstante:** `TIMELINE_TOP = 60` (muss mit `.timeline { top: 60px }` übereinstimmen)
### 4.5 LifeWaveSettings.vue
**Zweck:** Einstellungs-Panel (Slide-Up, 75dvh).
**Sektionen:**
1. **Linien** — Speed, Anzahl, Wellen-Amp, Fächerbreite, Feinheit, Welligkeit, Kurve, Kreis, Glow Größe, Glow Stärke
2. **Hintergrundbild** — 10 vordefinierte Bilder (`/images/bg-image-1.jpg` bis `10.jpg`)
3. **Hintergrundfarbe** — BG Mitte + BG Rand (Color Picker)
4. **Farbverlauf** — Textarea mit Hex-Werten (eine pro Zeile)
5. **Extras** — Dark/Light-Mode Toggle
6. **Reset** — Setzt alle Werte auf Defaults zurück
### 4.6 EventPanel.vue
**Zweck:** Event-Erstellung und -Bearbeitung (Slide-Up, 75dvh).
**Features:**
- Key Image Upload (Platzhalter)
- Titel-Input (inline, groß)
- Datum-Picker (QDate mit deutscher Locale)
- Emotion-Slider (-1 bis +1) mit Gradient-Track
- 10 Gradient-Presets + "Standard"-Option
- Beschreibungs-Textarea
- Weitere Medien (Platzhalter)
- Event löschen (nur Edit-Mode)
- Auto-Save: Änderungen werden sofort auf das Event persistiert
---
## 5. Stores
### 5.1 events.js
```js
// State
events // Array aller Events
selectedEventId // Aktuell ausgewählter Event (oder null)
panelOpen // Ob EventPanel offen ist
editingEventId // ID des Events im Edit-Mode (null = Create)
ghost* // Temporäre Felder für Live-Preview (ghostEmotion, ghostTitle, ...)
// Computed
ghostEvent // Computed Event-Objekt aus ghost-Feldern
sortedEvents // Nach Datum sortierte Events
// Methods
selectEvent(id), openPanel(eventId?), closePanel(), deleteEvent(id)
getGlowColor(event) // → Hex-Farbe basierend auf Emotion + Preset
```
**Demo-Daten:** 8 Events (19952023) mit verschiedenen Emotionen, Presets und Bildern.
### 5.2 settings.js
```js
// State
theme // 'light' | 'dark'
floatingLines // Objekt mit allen Shader-Parametern
// Methods
toggleTheme(), updateFloatingLines(changes), resetFloatingLines()
// Persistence
localStorage.setItem('thatsme-settings', JSON.stringify({...}))
```
**Defaults:** Siehe `FLOATING_LINES_DEFAULTS` in `settings.js`.
---
## 6. CSS-Architektur
### 6.1 Globale Styles (`app.scss`)
- `.glass--button` — Glasmorphismus für Buttons (blur + transparenter Hintergrund)
- `.glass--panel` — Glasmorphismus für Slide-Up-Panels
- Light: `background: rgba(255,255,255,0.7); color: #1a1a1a`
- Dark: `background: rgba(30,30,30,0.7); color: #f5f5f5`
### 6.2 Quasar Theme (`quasar.variables.scss`)
```scss
$primary : #d946ef; // Fuchsia — Slider, Toggles, aktive States
$secondary : #a855f7; // Purple
$accent : #ec4899; // Pink
```
### 6.3 Wichtige CSS-Hinweise
**Timeline-Positionierung:**
```css
/* TimelineView.vue — eigene Positionierung */
.timeline { position: absolute; top: 60px; bottom: 70px; }
/* LifeWaveLayout.vue — NUR z-index, KEIN inset: 0! */
/* inset: 0 würde top/bottom der Timeline überschreiben (CSS Cascade) */
.lifewave-layout__timeline { z-index: 5; }
```
**GlowDot — kein Zoom-Scaling:**
```css
.glow-dot { transform: translate(-50%, -50%); }
/* Breite/Höhe kommt dynamisch aus dem Settings-Store */
```
---
## 7. Bekannte Einschränkungen
1. **Max 8 Events im Shader**`pointX[8]`, `pointY[8]`, `pointColor[8]` sind fest auf 8 begrenzt. Bei mehr als 8 Events werden nur die ersten 8 als Shader-Punkte dargestellt.
2. **Bilder nur als Demo** — Key-Image-Upload und Medien-Upload sind Platzhalter (TODO).
3. **Kein Backend-Sync** — Alle Daten liegen nur lokal (Demo-Events + localStorage für Settings).
4. **DPR-Abhängigkeit** — Die GlowDot-Größe wird einmalig beim Mount aus `window.devicePixelRatio` berechnet. Bei Wechsel zwischen Displays (z.B. Retina → Nicht-Retina) stimmt die Größe nicht mehr exakt.
5. **Hintergrundbilder** — Müssen unter `/images/bg-image-{1-10}.jpg` auf dem Webspace liegen.
---
## 8. Entwicklung fortsetzen
### Dev-Server starten
```bash
# Im Docker-Container quasar.app:
npm run dev
# → http://app.thats-me.test:9000
```
### Produktions-Build
```bash
npm run build
# → Output: frontend/dist/spa/
# Statisches SPA, einfach hochladen
```
### Dateien für die Weiterentwicklung
| Was | Wo |
|-----|-----|
| Shader-Code (GLSL) | `FloatingLines.vue` (Zeile ~67366) |
| UV-Konvertierung | `LifeWaveLayout.vue``screenToUV()` |
| Event-Farben | `events.js``emotionToColor()`, `getGlowColor()` |
| Settings-Defaults | `settings.js``FLOATING_LINES_DEFAULTS` |
| Slider-Ranges | `LifeWaveSettings.vue` (`:min`, `:max`, `:step` auf jedem `q-slider`) |
| Quasar-Theme | `quasar.variables.scss` |
| Glass-Styles | `app.scss``.glass--panel`, `.glass--button` |
| Dev-Referenz | `dev/init-fl.html`, `dev/floating-lines.js` (Original-Prototyp) |
### Nächste Schritte (offen)
- [ ] Key-Image-Upload implementieren (Camera/File-Picker → IndexedDB/S3)
- [ ] Medien-Gallery pro Event
- [ ] Backend-Sync über Laravel REST API
- [ ] Mehr als 8 Events im Shader unterstützen (dynamisches Chunking oder LOD)
- [ ] Touch-Gesten: Long-Press auf GlowDot für Kontextmenü
- [ ] Onboarding / Leer-Zustand wenn keine Events vorhanden