Reskin auf Pinterest-Desktop-Optik, Funktionen unveraendert

Vorlage ist die Pinterest-Web-App. Geaendert wurde ausschliesslich die
Gestaltung: state, Tastaturkuerzel, deck/flash/commit/undo, die
Drag-Handler und die Beispieldaten sind Byte-fuer-Byte identisch.

- Hell statt dunkel: weisser Grund, Text #111, roter Akzent #e60023
  statt Mint auf Tannen-/Weinrot-Verlauf
- Sidebar (236px, beschriftet) wird zur 80px-Icon-Rail, Einstellungen
  sitzt unten; Zustaende ueber .rail/.rail.active statt inline, weil die
  dc-Laufzeit style-hover mit !important setzt und den aktiven Button
  sonst beim Hover aufhellt
- Kopfzeile bekommt das Pinterest-Suchfeld (Pille, 48px) plus Konto;
  Tabs wandern in eine eigene Leiste mit 3px-Unterstrich
- Karten randlos mit 16px-Radius; Merken-Pille erscheint beim Hover oben
  rechts, dazu ein Scrim - beides an :hover/:focus-within der Karte
- Profil-Galerie wird Masonry (columns:5, gestaffelte Hoehen)
- Nur noch Manrope, JetBrains Mono und backdrop-filter entfallen

Der Tageszaehler "Karten gesichtet" hat mit der schmalen Rail seinen
Platz verloren und sitzt jetzt in der Tab-Leiste.

Offen: das Suchfeld ist eine Attrappe - die App hat keine Suche. Im
README unter "Offene Punkte" vermerkt.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
2026-08-05 23:25:15 +02:00
parent 39e6aa20e7
commit 347fe7008a
3 changed files with 342 additions and 289 deletions

View File

@@ -9,6 +9,9 @@ Setze das Design aus diesem Ordner in unserem Stack um.
in unserer bestehenden Umgebung mit unseren Patterns und Komponenten nachbauen. in unserer bestehenden Umgebung mit unseren Patterns und Komponenten nachbauen.
3. Fidelity ist high — pixelgenau umsetzen: Farben, Radien, Übergänge, Hover-/Focus-States, 3. Fidelity ist high — pixelgenau umsetzen: Farben, Radien, Übergänge, Hover-/Focus-States,
Drag-Schwellen und Animationsdauern wie im README angegeben. Drag-Schwellen und Animationsdauern wie im README angegeben.
Die visuelle Sprache ist Pinterest Desktop: weißer Hintergrund, 80px-Icon-Rail ohne
Beschriftung, Suchfeld als Pille, rote Primäraktion `#e60023`, randlose 16px-Karten,
Masonry im Profil. Hover-Elemente (Merken-Pille, Scrim) hängen am Hover der Karte.
4. Umfang: App-Shell (Sidebar + Kopfzeile mit Tabs) und die vier Screens 4. Umfang: App-Shell (Sidebar + Kopfzeile mit Tabs) und die vier Screens
Feed, Profil, Erstellen, Einstellungen. Feed, Profil, Erstellen, Einstellungen.
5. Icons als Lucide-Icons umsetzen (im Prototyp stehen Unicode-Glyphen als Platzhalter). 5. Icons als Lucide-Icons umsetzen (im Prototyp stehen Unicode-Glyphen als Platzhalter).

View File

@@ -2,8 +2,17 @@
## Überblick ## Überblick
Die bestehende Mobile-App („videogen", Swipe-Feed für Creator-/Produkt-Posts) als Desktop-Web-App. Die bestehende Mobile-App („videogen", Swipe-Feed für Creator-/Produkt-Posts) als Desktop-Web-App.
Statt einer schmalen Handy-Spalte im Browser: dreispaltiges App-Layout mit permanenter Navigation, Statt einer schmalen Handy-Spalte im Browser: Icon-Rail links, breite Kopfzeile mit Suche,
zentralem Kartenstapel und Detailspalte. Swipen wird durch Maus-Drag, Buttons und Tastatur ersetzt. zentraler Kartenstapel und Detailspalte. Swipen wird durch Maus-Drag, Buttons und Tastatur ersetzt.
**Visuelle Sprache: Pinterest Desktop.** Heller Hintergrund, schmale Icon-Leiste ohne Beschriftung,
Suchfeld als breite Pille, rote Primäraktion („Merken"), randlose Karten mit 16px-Radius,
Masonry-Raster im Profil. Referenz ist die Pinterest-Web-App (`de.pinterest.com`); der Screenshot
dafür liegt bisher nicht im Repo, siehe [Offene Punkte](#offene-punkte).
**Die Funktionen sind gegenüber dem ersten Entwurf unverändert** — Kartenstapel, Merken/Ablehnen,
Undo, Tabs, die vier Screens und das State-Modell bleiben, wie sie waren. Geändert hat sich
ausschließlich die Gestaltung.
## Zu den Design-Dateien ## Zu den Design-Dateien
Die Dateien in diesem Bundle sind **Design-Referenzen in HTML** — Prototypen, die Aussehen und Die Dateien in diesem Bundle sind **Design-Referenzen in HTML** — Prototypen, die Aussehen und
@@ -27,40 +36,40 @@ und sollen pixelgenau nachgebaut werden — mit den Komponenten/Utilities des Zi
- Wurzel: `display:flex`, `min-height:100vh`, **`min-width:1180px`** (darunter horizontales Scrollen, - Wurzel: `display:flex`, `min-height:100vh`, **`min-width:1180px`** (darunter horizontales Scrollen,
die App ist Desktop-only gedacht). die App ist Desktop-only gedacht).
- Hintergrund (fix, nicht mitscrollend): - Hintergrund durchgehend `#ffffff`, Grundschrift `#111111`. Keine Verläufe, kein `backdrop-filter`.
`radial-gradient(120% 80% at 50% 0%, #0d4c3d 0%, #0a3a30 34%, #24121a 68%, #40161b 100%)`,
`background-attachment: fixed`. Oben Tannengrün, unten Weinrot — direkt aus der App übernommen.
- Grundschrift `#f2efe9` auf diesem Verlauf.
### 1. Sidebar (links) ### 1. Icon-Rail (links)
- Breite `236px`, `flex:none`, `position:sticky; top:0; height:100vh`. - Breite `80px`, `flex:none`, `position:sticky; top:0; height:100vh`, `background:#ffffff`,
- `background: rgba(8,26,22,.42)`, `backdrop-filter: blur(14px)`, rechte Kante `1px solid rgba(255,255,255,.08)`. rechte Kante `1px solid #efefef`. Padding `12px 0 16px`, Inhalt zentrierte Flex-Spalte, `gap:8px`.
- Padding `26px 20px`, Inhalt als Flex-Spalte, `gap:28px`. - **Brand**: 32×32 Kreis `#e60023` in einem 48px-Slot, weißes „V" 17px/700.
- **Brand**: 30×30 Kachel, `border-radius:9px`, `linear-gradient(150deg,#2fa87f,#0e5442)`, Buchstabe „V" 15px/700; - **Nav-Buttons**: 48×48, `border-radius:50%`, Icon 20px, **ohne Textlabel** — der Name steht im
daneben Wortmarke „videogen" 16px/600, `letter-spacing:-.01em`. `title`-Attribut und wird als Tooltip gezeigt.
- **Nav** (Flex-Spalte, `gap:4px`), Einträge: Feed, Profil, Erstellen, Einstellungen. - Inaktiv: transparent, Icon `#111111`. Hover: `background:#e9e9e9`.
- Item: `display:flex; gap:11px; padding:11px 13px; border-radius:11px; font:500 14px Manrope; text-align:left; cursor:pointer`. - Aktiv: `background:#111111`, Icon `#ffffff`. Aktiv + Hover: `background:#000000`.
- Inaktiv: transparent, Text `rgba(242,239,233,.7)`, Border transparent. - Transition `.12s ease`.
- Aktiv: `background: rgba(126,226,184,.12)`, `border:1px solid rgba(126,226,184,.4)`, Text `#d6fbec`. - **Umsetzungshinweis:** Die Zustände gehören in Klassen (`.rail`, `.rail.active`), nicht in
- Hover: `background: rgba(255,255,255,.09)`, Text `#ffffff`. Transition `.18s ease`. Inline-Styles. Im Prototyp setzt die `.dc`-Laufzeit `style-hover` mit `!important`; ein inline
- Icon-Slot 20px breit (im Prototyp Glyphen ◧ ◍ ⚙ — **in der Implementierung durch Lucide-Icons ersetzen**: gesetztes Aktiv-Schwarz würde beim Hover überschrieben und der aktive Button hellte auf.
`layout-grid`, `user`, `plus`, `settings`). - Reihenfolge oben: **Feed, Profil, Erstellen**. Unten (`margin-top:auto`): **Einstellungen**.
- **Fußbereich** (`margin-top:auto`): - Icons (im Prototyp Unicode-Glyphen als Platzhalter) — **Lucide**: `home`, `user`, `plus`, `settings`.
- Statistik-Karte: `1px solid rgba(255,255,255,.12)`, `radius:14px`, `padding:14px`, `background: rgba(255,255,255,.03)`.
Kicker „HEUTE" 11px, `letter-spacing:.14em`, uppercase, `rgba(242,239,233,.5)`; Zahl 22px JetBrains Mono,
`font-variant-numeric: tabular-nums`, zweistellig mit führender Null; Label „Karten gesichtet" 12px `rgba(242,239,233,.55)`.
- Konto-Zeile: 32px Kreis `linear-gradient(140deg,#b2323c,#5d1620)`, Name 13px/600, Rolle 11px `rgba(242,239,233,.5)`.
### 2. Kopfzeile (über dem Inhalt) ### 2. Kopfzeile
- Höhe `72px`, `padding: 0 34px`, Unterkante `1px solid rgba(255,255,255,.07)`, Inhalt `space-between`. - `padding:12px 16px 4px`, Flex-Zeile, `gap:8px`.
- **Tabs** links (`gap:6px`): „Videos", „Folge ich", „Posts". Jeder Tab wechselt den Kartenstapel und - **Suchfeld**: `flex:1`, Höhe `48px`, `border-radius:24px`, `background:#efefef`, `padding:0 16px`,
springt zurück auf den Feed-Screen (Index/History zurücksetzen). Platzhaltertext „Suchen" 16px `#767676`; links Lupe, rechts Objektiv-Symbol
- `padding:9px 18px; border-radius:999px; font:500 16px Manrope`. (Lucide `search` und `camera`).
- Aktiv: `background: rgba(255,255,255,.08)`, `border:1px solid rgba(255,255,255,.22)`, Text `#fff`, 600. - **Konto**: 40px Kreis `linear-gradient(140deg,#b2323c,#5d1620)` mit weißer Initiale 15px/700,
- Inaktiv: transparent, Text `rgba(242,239,233,.58)`; Hover `#fff`. daneben 28px Chevron-Button (`▾`, Hover `#e9e9e9`).
- **Rechts**: Zähler „3 / 5" (12px JetBrains Mono, tabular-nums, `rgba(242,239,233,.5)`) und runder
Info-Button 36px (`border:1px solid rgba(255,255,255,.18)`, `background: rgba(255,255,255,.04)`; ### 3. Tab-Leiste
Hover `rgba(255,255,255,.12)` + `translateY(-1px)`), toggelt das Kürzel-Panel. - `padding:0 24px`, Unterkante `1px solid #efefef`, Tabs mit `gap:24px`, an der Grundlinie ausgerichtet.
- **Tabs**: „Videos", „Folge ich", „Posts". Jeder Tab wechselt den Kartenstapel und springt zurück
auf den Feed-Screen (Index/History zurücksetzen).
- `padding:0 2px 12px`, Unterkante `3px solid` — aktiv `#111111`, inaktiv `transparent`.
- Aktiv: `#111111`, 700. Inaktiv: `#767676`, 500; Hover → `#111111`. Schriftgröße 16px.
- **Rechts**: Tageszähler „**NN** heute gesichtet" (Zahl 700 `#111111` mit `tabular-nums`,
Rest 13px `#767676`), Stapelzähler „3 / 5" (13px `#767676`, `tabular-nums`) und runder
Info-Button 36px (transparent, Hover `#e9e9e9`), der das Kürzel-Panel toggelt.
--- ---
@@ -77,108 +86,122 @@ und sollen pixelgenau nachgebaut werden — mit den Komponenten/Utilities des Zi
- Es werden **bis zu 3 Karten** gestapelt gerendert (konfigurierbar 15). - Es werden **bis zu 3 Karten** gestapelt gerendert (konfigurierbar 15).
- Karte k (0 = oben): `transform: translate3d(0, -12px·k, 0) scale(1 - 0.045·k)`, `z-index: 10-k`, - Karte k (0 = oben): `transform: translate3d(0, -12px·k, 0) scale(1 - 0.045·k)`, `z-index: 10-k`,
`pointer-events` nur auf der obersten. `pointer-events` nur auf der obersten.
- Karte: `position:absolute; inset:0; border-radius:26px; overflow:hidden; - Karte: `position:absolute; inset:0; border-radius:26px`**`16px`**, `overflow:hidden`,
border:1px solid rgba(255,255,255,.14); box-shadow: 0 26px 60px rgba(0,0,0,.45); **kein Rahmen**, `box-shadow: 0 1px 20px rgba(0,0,0,.18)`,
touch-action:none; user-select:none; cursor:grab` (beim Ziehen `grabbing`). `touch-action:none; user-select:none; cursor:grab` (beim Ziehen `grabbing`).
- Transition: `transform .34s cubic-bezier(.22,.9,.3,1), opacity .3s ease` — während des aktiven Drags `none`. - Transition: `transform .34s cubic-bezier(.22,.9,.3,1), opacity .3s ease` — während des aktiven Drags `none`.
- **Karteninhalt (von unten nach oben gestapelt):** - **Karteninhalt (von unten nach oben gestapelt):**
1. Grundfläche: `linear-gradient(155deg, <hue0> 0%, <hue1> 100%)` (pro Karte, siehe Daten unten). 1. Grundfläche: `linear-gradient(155deg, <hue0> 0%, <hue1> 100%)` (pro Karte, siehe Daten unten).
2. Streifen-Overlay: `repeating-linear-gradient(<40+9·i>deg, rgba(255,255,255,.08) 0 2px, transparent 2px 16px)`, `opacity:.5`. 2. Streifen-Overlay: `repeating-linear-gradient(<40+9·i>deg, rgba(255,255,255,.08) 0 2px, transparent 2px 16px)`, `opacity:.5`.
(Im Prototyp Platzhalter für das Video-/Bild-Asset — im Produkt liegt hier das Medium.) (Im Prototyp Platzhalter für das Video-/Bild-Asset — im Produkt liegt hier das Medium.)
3. Lesbarkeits-Verlauf: `linear-gradient(180deg, rgba(0,0,0,.32) 0%, transparent 26%, transparent 52%, rgba(0,0,0,.66) 100%)`. 3. **Hover-Scrim**: `rgba(0,0,0,.24)`, `opacity` 0 → 1 in `.12s`, nur wenn die Karte gehovert wird.
4. **Pills** oben links (`padding:18px`, `gap:9px`): Art („Produkt"/„Video") und Assets („3 Bilder"). 4. Lesbarkeits-Verlauf: `linear-gradient(180deg, transparent 46%, rgba(0,0,0,.62) 100%)`.
`padding:7px 15px; border-radius:999px; border:1px solid rgba(255,255,255,.34); 5. **Pills** oben links (`padding:16px`, `gap:8px`): Art („Produkt"/„Video") und Assets („3 Bilder").
background: rgba(0,0,0,.22); backdrop-filter: blur(6px); font:600 13px`. `padding:6px 12px; border-radius:16px; background: rgba(255,255,255,.94); color:#111111; font:600 13px`.
5. **Caption** mittig, 118px über der Unterkante: JetBrains Mono 15px, `padding:9px 16px`, 6. **Merken-Pille** oben rechts, **nur auf der obersten Karte und erst beim Hover**
`border-radius:9px`, `background: rgba(0,0,0,.5)`, `backdrop-filter: blur(4px)`. (`opacity` 0 → 1 in `.12s`): `padding:11px 16px; border-radius:22px; background:#e60023;
6. **Creator-Zeile** unten (`padding:20px`, `gap:13px`): 46px Avatar-Kreis (Farbverlauf, `border:2px solid rgba(255,255,255,.3)`), color:#ffffff; font:700 15px`, Hover `#ad081b`. Klick = dieselbe Aktion wie „Merken"/→.
Name 19px/700 `letter-spacing:-.01em`, Untertitel 14px `rgba(255,255,255,.72)`. 7. **Caption** mittig, 104px über der Unterkante: `font:600 15px`, `padding:8px 14px`,
7. **Entscheidungs-Badge** (nur oberste Karte, erscheint ab 40px Drag oder beim Exit): `border-radius:16px`, `background: rgba(255,255,255,.94)`, `color:#111111`.
`top:86px`, links bei „GEMERKT" / rechts bei „SKIP", `padding:8px 16px; border-radius:10px; 8. **Creator-Zeile** unten (`padding:18px`, `gap:12px`, Text weiß): 44px Avatar-Kreis (Farbverlauf,
font:800 15px; letter-spacing:.12em`, `border:2px solid` und Text in ohne Rand), Name 18px/700 `letter-spacing:-.01em`, Untertitel 14px `rgba(255,255,255,.82)`.
Grün `rgba(126,226,184,.9)` / `#a9f0d0` bzw. Rot `rgba(232,120,120,.9)` / `#f0a8a8`, 9. **Entscheidungs-Badge** (nur oberste Karte, erscheint ab 40px Drag oder beim Exit):
`rotate(-10deg)` bzw. `rotate(10deg)`, Opacity `min(1, |dx|/110)`, Transition `.16s`. `top:86px`, links bei „GEMERKT" / rechts bei „SKIP", `padding:10px 18px; border-radius:22px;
- **Leerzustand** (Stapel durch): gestrichelter Rahmen `1px dashed rgba(255,255,255,.2)`, `radius:26px`, font:700 15px; letter-spacing:.08em`, Text immer `#ffffff`, Fläche
Titel „Alles gesichtet" 20px/600, Text „Für heute sind keine neuen Karten mehr in dieser Ansicht.", `#e60023` (GEMERKT) bzw. `#111111` (SKIP), `rotate(-10deg)` bzw. `rotate(10deg)`,
Outline-Button „Stapel neu laden" (`border:1px solid rgba(126,226,184,.6)`, Text `#8fe3c1`, Opacity `min(1, |dx|/110)`, Transition `.16s`.
Hover `background: rgba(126,226,184,.14)`), Einblendung `fadeIn .3s`. - **Leerzustand** (Stapel durch): Fläche `#f5f5f5`, `radius:16px`, Titel „Alles gesichtet" 20px/700,
- **Aktions-Buttons** unter der Bühne (`gap:18px`), alle `border-radius:50%`, Text „Für heute sind keine neuen Karten mehr in dieser Ansicht." 15px `#5f5f5f`,
`background: rgba(14,10,12,.72)`, `border:1px solid rgba(255,255,255,.16)`, roter Pill-Button „Stapel neu laden" (`#e60023`, Hover `#ad081b`, weiß 15px/700,
Transition `.18s`, Hover `translateY(-3px) scale(1.05)`, Active `scale(.95)`: `padding:12px 20px; radius:24px`), Einblendung `fadeIn .3s`.
- ✕ Ablehnen — 62px, Hover-Border `rgba(230,120,120,.75)`, Hover-BG `rgba(120,32,38,.5)`. - **Aktions-Buttons** unter der Bühne (`gap:16px`), Hover `scale(1.04)`, Active `scale(.96)`,
- ↺ Zurück — 52px, Hover-Border `rgba(255,255,255,.4)`. Transition `.12s`:
- ♥ Merken — 70px, Text `#8fe3c1`, Border `rgba(126,226,184,.45)`, Hover-BG `rgba(20,88,68,.6)`, Hover `scale(1.06)`. - ✕ Ablehnen — 56px Kreis, `background:#efefef`, Icon `#111111`, Hover `#e2e2e2`.
(Icons in der Implementierung: Lucide `x`, `rotate-ccw`, `heart`.) - ↺ Zurück — 48px Kreis, gleiche Flächen.
- **Kürzel-Panel** (Info-Button): absolut `right:26px; top:26px`, 250px, `radius:14px`, - **Merken** — roter Pill statt Kreis: Höhe 56px, `padding:0 30px`, `border-radius:28px`,
`background: rgba(10,26,22,.9)`, `blur(12px)`, `border:1px solid rgba(255,255,255,.14)`, `background:#e60023`, `color:#ffffff`, `font:700 17px`, Hover `#ad081b`.
(Icons in der Implementierung: Lucide `x`, `rotate-ccw`; „Merken" trägt Text, kein Icon.)
- **Kürzel-Panel** (Info-Button): absolut `right:26px; top:26px`, 250px, `radius:16px`,
`background:#ffffff`, `box-shadow: 0 1px 20px rgba(0,0,0,.16)`, `padding:16px`,
Einblendung `riseIn .22s`. Inhalt: ← Ablehnen, → Merken, ↑ Zurücknehmen, Maus Karte ziehen. Einblendung `riseIn .22s`. Inhalt: ← Ablehnen, → Merken, ↑ Zurücknehmen, Maus Karte ziehen.
#### A2 Detailspalte (rechts, 372px) #### A2 Detailspalte (rechts, 372px)
- `border-left:1px solid rgba(255,255,255,.07)`, `background: rgba(8,22,19,.34)`, `padding:30px 28px`, - `border-left:1px solid #efefef`, `background:#ffffff`, `padding:28px`,
Flex-Spalte `gap:24px`, `overflow:auto`. Flex-Spalte `gap:22px`, `overflow:auto`.
- Kicker „AKTUELLE KARTE" (11px, `letter-spacing:.16em`, uppercase, `rgba(242,239,233,.45)`), - Kicker „Aktuelle Karte" 12px/600 `#767676`, Titel = Caption der obersten Karte 28px/700
Titel = Caption der obersten Karte 26px/600 `letter-spacing:-.02em`, darunter „Name · Untertitel" 14px `rgba(242,239,233,.62)`. `letter-spacing:-.02em`, darunter „Name · Untertitel" 15px `#5f5f5f`.
- Hairline `1px rgba(255,255,255,.09)`. - Hairline `1px #efefef`.
- **Statistik-Raster** 3 Spalten, `gap:12px`: Kachel `border:1px solid rgba(255,255,255,.1)`, `radius:12px`, - **Statistik-Raster** 3 Spalten, `gap:10px`: Kachel `background:#f5f5f5`, `radius:16px`,
`padding:12px`; Wert 17px JetBrains Mono tabular-nums, Label 11px `rgba(242,239,233,.5)`. `padding:14px`; Wert 18px/700 `tabular-nums`, Label 12px `#767676`.
- **Beschreibung**: Kicker + Fließtext 14px, `line-height:1.75`, `rgba(242,239,233,.78)`, `text-wrap:pretty`. - **Beschreibung**: Kicker + Fließtext 15px, `line-height:1.7`, `#333333`, `text-wrap:pretty`.
- **Tags**: Pills `padding:6px 12px`, `radius:999px`, `border:1px solid rgba(255,255,255,.18)`, 12px. - **Tags**: Pills `padding:8px 14px`, `radius:18px`, `background:#efefef`, 13px/600 `#111111`.
- **Aktionen unten** (`margin-top:auto`, `gap:10px`), beide `padding:13px; radius:12px`: - **Aktionen unten** (`margin-top:auto`, `gap:8px`), beide `padding:14px; radius:24px; font:700 15px`:
„Zur Sammlung hinzufügen" (Akzent-Outline `rgba(126,226,184,.55)`, Text `#8fe3c1`, Hover `rgba(126,226,184,.14)`) = „Zur Sammlung hinzufügen" (rot `#e60023`, weiß, Hover `#ad081b`) = gleiche Aktion wie „Merken";
gleiche Aktion wie ♥; „Überspringen" (neutrale Outline) = gleiche Aktion wie ✕. „Überspringen" (`#efefef`, `#111111`, Hover `#e2e2e2`) = gleiche Aktion wie ✕.
### B. Profil ### B. Profil
- `padding:38px 44px`, `overflow:auto`, Einblendung `riseIn .26s`. - `padding:32px 24px 40px`, `overflow:auto`, Einblendung `riseIn .26s`.
- Kopf: 96px Avatar-Kreis (`linear-gradient(140deg,#b2323c,#5d1620)`, `border:2px solid rgba(255,255,255,.2)`), - Kopf **zentriert** als Spalte, `gap:10px`, `padding-bottom:28px`: 120px Avatar-Kreis
Name 32px/600 `letter-spacing:-.02em`, Meta „Studio-Konto · seit März 2026" 14px `rgba(242,239,233,.6)`; (`linear-gradient(140deg,#b2323c,#5d1620)`, ohne Rand), Name 36px/700 `letter-spacing:-.02em`,
rechts drei Kennzahlen (`gap:34px`): Posts 38, Gemerkt (live aus dem Feed), Follower 1.204 — Meta „Studio-Konto · seit März 2026" 15px `#5f5f5f`; darunter drei Kennzahlen nebeneinander
Werte 22px JetBrains Mono tabular-nums, Labels 12px. Unterkante Hairline `rgba(255,255,255,.09)`, `padding-bottom:26px`. (`gap:28px`, zentriert): Posts 38, Gemerkt (live aus dem Feed), Follower 1.204 —
- Galerie: `grid-template-columns: repeat(4,1fr)`, `gap:18px`, `margin-top:28px`. Werte 18px/700 `tabular-nums`, Labels 13px `#767676`.
Kachel: Höhe 230px, `radius:16px`, `border:1px solid rgba(255,255,255,.12)`, Kartenverlauf + Streifen (`opacity:.45`), - **Masonry-Raster** statt Gitter: `columns:5; column-gap:16px`.
Titel unten auf `linear-gradient(0deg, rgba(0,0,0,.7), transparent)`, 13px/600, `padding:14px`. Kachel: `break-inside:avoid; margin-bottom:16px`, **unterschiedliche Höhen**
Hover: `translateY(-4px)`, Border `rgba(126,226,184,.5)`, Transition `.2s`. (Zyklus 320 / 240 / 400 / 280 / 360 / 220 / 300 / 260 px), `radius:16px`, **kein Rahmen**,
Kartenverlauf + Streifen (`opacity:.45`), `cursor:zoom-in`.
Titel unten auf `linear-gradient(0deg, rgba(0,0,0,.62), transparent)`, 14px/600 weiß, `padding:14px`.
**Hover:** Scrim `rgba(0,0,0,.24)` und rote Merken-Pille oben rechts (`right:12px; top:12px`,
`padding:11px 16px; radius:22px`) blenden mit `.12s` ein.
### C. Erstellen ### C. Erstellen
- `padding:38px 44px`; Inhalt `max-width:920px`, `grid-template-columns: 1fr 320px`, `gap:34px`. - `padding:32px 24px 40px`; Inhalt in einer weißen Karte: `max-width:920px`, zentriert,
- Titel „Neuen Post erstellen" 30px/600, Untertitel 14px `rgba(242,239,233,.6)`. `radius:24px`, `box-shadow: 0 1px 20px rgba(0,0,0,.12)`, `padding:32px`,
- **Dropzone**: Höhe 220px, `border:1px dashed rgba(255,255,255,.24)`, `radius:18px`, `grid-template-columns: 1fr 320px`, `gap:32px`.
`background: rgba(255,255,255,.03)`; Hover Border `rgba(126,226,184,.6)`, BG `rgba(126,226,184,.06)`. - Titel „Neuen Post erstellen" 30px/700, Untertitel 15px `#5f5f5f`.
Text „Dateien hierher ziehen" 15px/600 + „MP4, MOV oder bis zu 10 Bilder" 13px. - **Dropzone**: Höhe 220px, `border:2px dashed #cdcdcd`, `radius:16px`, `background:#f5f5f5`;
- **Felder** Titel und Untertitel: Label 12px uppercase `letter-spacing:.14em` `rgba(242,239,233,.5)`; Hover Border `#e60023`, BG `#fafafa`.
Input `padding:13px 15px`, `radius:11px`, `border:1px solid rgba(255,255,255,.16)`, Text „Dateien hierher ziehen" 16px/700 + „MP4, MOV oder bis zu 10 Bilder" 14px `#767676`.
`background: rgba(0,0,0,.24)`, Text `#f2efe9`, 15px. Beide sind an die Live-Vorschau gebunden. - **Felder** Titel und Untertitel: Label 13px/700 `#111111` (keine Versalien, kein `letter-spacing`);
- **Art**: Segmented Pills „Produkt / Video / Serie", `padding:10px 18px`, `radius:999px`; Input `padding:14px 16px`, `radius:16px`, `border:2px solid #cdcdcd`, `background:#ffffff`,
aktiv `border rgba(126,226,184,.6)`, `background rgba(126,226,184,.12)`, Text `#a9f0d0`. Text `#111111`, 15px. Beide sind an die Live-Vorschau gebunden.
- **Aktionen**: „Veröffentlichen" (Akzent-Outline, löst Toast „Post veröffentlicht" aus), - **Art**: Pills „Produkt / Video / Serie", `padding:11px 18px`, `radius:22px`, `font:700 15px`;
„Als Entwurf sichern" (neutrale Outline), beide `padding:13px 24px`, `radius:12px`. aktiv `background:#111111`, Text `#ffffff`; inaktiv `background:#efefef`, Text `#111111`.
- **Vorschau** (rechte Spalte): Kicker + Karte 420px hoch, `radius:22px`, - **Aktionen**: „Veröffentlichen" (rot `#e60023`, weiß, löst Toast „Post veröffentlicht" aus),
„Als Entwurf sichern" (`#efefef`), beide `padding:14px 24px`, `radius:24px`, `font:700 15px`.
- **Vorschau** (rechte Spalte): Kicker 12px/600 `#767676` + Karte 420px hoch, `radius:16px`,
`linear-gradient(155deg,#1f8163,#0d4034)` + `repeating-linear-gradient(48deg, rgba(255,255,255,.07) 0 2px, transparent 2px 15px)`, `linear-gradient(155deg,#1f8163,#0d4034)` + `repeating-linear-gradient(48deg, rgba(255,255,255,.07) 0 2px, transparent 2px 15px)`,
unten Titel 18px/700 und Untertitel 13px auf schwarzem Verlauf — spiegelt die Eingaben live. unten Titel 18px/700 und Untertitel 14px auf schwarzem Verlauf — spiegelt die Eingaben live.
### D. Einstellungen ### D. Einstellungen
- `padding:38px 44px`, Inhalt `max-width:660px`. Titel „Einstellungen" 30px/600. - `padding:32px 24px 40px`, Inhalt `max-width:660px`, zentriert. Titel „Einstellungen" 30px/700.
- Liste: Container `border:1px solid rgba(255,255,255,.1)`, `radius:16px`, `overflow:hidden`. - Liste: Container `radius:16px`, `overflow:hidden`, `box-shadow: 0 1px 20px rgba(0,0,0,.12)`,
Zeile `padding:18px 20px`, `background: rgba(255,255,255,.02)`, Trenner `1px solid rgba(255,255,255,.07)`; **kein Rahmen**. Zeile `padding:20px`, `background:#ffffff`, Trenner `1px solid #efefef`;
Label 15px/600, Hinweis 13px `rgba(242,239,233,.55)`. Label 16px/700, Hinweis 14px `#767676`.
- Toggle: Track 52×30, `radius:999px`; aus → `background rgba(255,255,255,.05)`, `border rgba(255,255,255,.2)`, - Toggle: Track 52×30, `radius:999px`, kein Rahmen; aus → `background:#cdcdcd`,
Knopf links 3px, `rgba(242,239,233,.55)`; an → `background rgba(126,226,184,.18)`, an → `background:#e60023`. Knopf 24px, immer `#ffffff` mit `box-shadow: 0 1px 3px rgba(0,0,0,.25)`,
`border rgba(126,226,184,.6)`, Knopf links 25px, `#8fe3c1`. Knopf 22px, Transition links 3px → 25px, Transition `left .2s cubic-bezier(.22,.9,.3,1)`.
`left .2s cubic-bezier(.22,.9,.3,1), background .2s`.
- Einträge: Videos automatisch abspielen (an), Ton beim Öffnen (aus), Wochenrückblick (an), Bewegte Übergänge (an). - Einträge: Videos automatisch abspielen (an), Ton beim Öffnen (aus), Wochenrückblick (an), Bewegte Übergänge (an).
--- ---
## Interaktion & Verhalten ## Interaktion & Verhalten
**Karte entscheiden — drei gleichwertige Wege:** **Karte entscheiden — vier gleichwertige Wege:**
1. **Maus-Drag**: Pointer Events auf der obersten Karte (`pointerdown``setPointerCapture`, 1. **Maus-Drag**: Pointer Events auf der obersten Karte (`pointerdown``setPointerCapture`,
`pointermove` → Delta, `pointerup`/`pointercancel` → Auswertung). `pointermove` → Delta, `pointerup`/`pointercancel` → Auswertung).
Live-Transform: `translate3d(dx, dy, 0) rotate(dx/22 deg)`, Transition währenddessen aus. Live-Transform: `translate3d(dx, dy, 0) rotate(dx/22 deg)`, Transition währenddessen aus.
Schwelle **±120px**: `dx > 120` = merken, `dx < -120` = ablehnen, sonst Rücksprung (Transition wieder an). Schwelle **±120px**: `dx > 120` = merken, `dx < -120` = ablehnen, sonst Rücksprung (Transition wieder an).
2. **Buttons** ✕ / ↺ / unter der Bühne (und die beiden Buttons in der Detailspalte). 2. **Buttons** ✕ / ↺ / „Merken" unter der Bühne (und die beiden Buttons in der Detailspalte).
3. **Tastatur** (global, nur auf dem Feed-Screen, mit `preventDefault`): 3. **Merken-Pille auf der Karte** (erscheint beim Hover, oben rechts) — identisch mit „merken".
4. **Tastatur** (global, nur auf dem Feed-Screen, mit `preventDefault`):
`ArrowLeft` = ablehnen, `ArrowRight` = merken, `ArrowUp` = zurücknehmen. `ArrowLeft` = ablehnen, `ArrowRight` = merken, `ArrowUp` = zurücknehmen.
**Hover-Zustände:** Scrim und Merken-Pille hängen am Hover der *Karte*, nicht am eigenen Element
(im Prototyp `.pin:hover .pin-save` / `.pin:hover .pin-scrim`, zusätzlich `:focus-within` für
Tastaturbedienung). Im Ziel-Framework als Hover-State der Kartenkomponente umsetzen —
und den Fokusfall nicht vergessen, sonst ist die Pille per Tastatur nicht erreichbar.
**Exit-Animation:** `transform: translate3d(±620px, -40px, 0) rotate(±22deg)`, `opacity: 0`, **Exit-Animation:** `transform: translate3d(±620px, -40px, 0) rotate(±22deg)`, `opacity: 0`,
Dauer **320ms**, danach Index +1 und `exiting` zurücksetzen. Während `exiting` sind weitere Dauer **320ms**, danach Index +1 und `exiting` zurücksetzen. Während `exiting` sind weitere
Entscheidungen gesperrt. Entscheidungen gesperrt.
@@ -186,15 +209,15 @@ Entscheidungen gesperrt.
**Undo:** letzte Entscheidung aus der History, Index zurück, „Gemerkt"-Zähler ggf. 1. **Undo:** letzte Entscheidung aus der History, Index zurück, „Gemerkt"-Zähler ggf. 1.
Leere History → Toast „Nichts zurückzunehmen". Leere History → Toast „Nichts zurückzunehmen".
**Toast:** fixiert `bottom:34px`, horizontal zentriert, `padding:12px 22px`, `radius:999px`, **Toast:** fixiert `bottom:28px`, horizontal zentriert, `padding:16px 22px`, `radius:16px`,
`background: rgba(10,24,20,.92)`, `blur(10px)`, `border:1px solid rgba(255,255,255,.16)`, 14px, `background:#ffffff`, `box-shadow: 0 1px 20px rgba(0,0,0,.22)`, `color:#111111`, 15px/600,
Einblendung `riseIn .2s`, verschwindet nach **1700ms**. Texte: „Zu <Name>' gemerkt", Einblendung `riseIn .2s`, verschwindet nach **1700ms**. Texte: „Zu <Name>' gemerkt",
„Übersprungen", „Nichts zurückzunehmen", „Post veröffentlicht". „Übersprungen", „Nichts zurückzunehmen", „Post veröffentlicht".
**Keyframes:** **Keyframes:**
- `riseIn`: `opacity:0; translateY(14px)``opacity:1; none` (.2.26s ease) - `riseIn`: `opacity:0; translateY(14px)``opacity:1; none` (.2.26s ease)
- `fadeIn`: `opacity:0``1` - `fadeIn`: `opacity:0``1`
- Fokus: `:focus-visible { outline: 2px solid #7ee2b8; outline-offset: 2px; }` — nie der Browser-Default. - Fokus: `:focus-visible { outline: 2px solid #0074e8; outline-offset: 2px; }` — nie der Browser-Default.
**Responsive:** Desktop-only, `min-width:1180px`; darunter horizontal scrollen. **Responsive:** Desktop-only, `min-width:1180px`; darunter horizontal scrollen.
Der Feed-Bereich scrollt bei geringer Fensterhöhe (`min-height:0; overflow:auto`). Der Feed-Bereich scrollt bei geringer Fensterhöhe (`min-height:0; overflow:auto`).
@@ -205,13 +228,13 @@ Der Feed-Bereich scrollt bei geringer Fensterhöhe (`min-height:0; overflow:auto
| State | Typ | Zweck | | State | Typ | Zweck |
|---|---|---| |---|---|---|
| `screen` | `'feed' \| 'profile' \| 'create' \| 'settings'` | aktive Ansicht (Sidebar) | | `screen` | `'feed' \| 'profile' \| 'create' \| 'settings'` | aktive Ansicht (Icon-Rail) |
| `tab` | `'videos' \| 'following' \| 'posts'` | aktiver Stapel; Wechsel setzt `idx`/`history` zurück und springt auf `feed` | | `tab` | `'videos' \| 'following' \| 'posts'` | aktiver Stapel; Wechsel setzt `idx`/`history` zurück und springt auf `feed` |
| `idx` | number | Index der obersten Karte im aktuellen Stapel | | `idx` | number | Index der obersten Karte im aktuellen Stapel |
| `drag` | `{x, y, active} \| null` | laufende Drag-Geste | | `drag` | `{x, y, active} \| null` | laufende Drag-Geste |
| `exiting` | `'left' \| 'right' \| null` | laufende Exit-Animation (sperrt Eingaben) | | `exiting` | `'left' \| 'right' \| null` | laufende Exit-Animation (sperrt Eingaben) |
| `history` | `{idx, dir}[]` | für Undo | | `history` | `{idx, dir}[]` | für Undo |
| `seen`, `saved` | number | Zähler Sidebar / Profil | | `seen`, `saved` | number | Zähler Tab-Leiste / Profil |
| `showInfo` | boolean | Kürzel-Panel | | `showInfo` | boolean | Kürzel-Panel |
| `toast` | string | Toast-Text (leer = versteckt) | | `toast` | string | Toast-Text (leer = versteckt) |
| `draftTitle`, `draftSub`, `draftKind` | string | Formular „Erstellen", live in der Vorschau | | `draftTitle`, `draftSub`, `draftKind` | string | Formular „Erstellen", live in der Vorschau |
@@ -229,33 +252,45 @@ Die drei Tabs liefern drei Stapel (Videos, Folge ich, Posts).
**Farben** **Farben**
| Rolle | Wert | | Rolle | Wert |
|---|---| |---|---|
| Seitenverlauf | `#0d4c3d` → `#0a3a30` → `#24121a` → `#40161b` | | Seite | `#ffffff` |
| Text | `#f2efe9` | | Text | `#111111` |
| Text gedämpft | `rgba(242,239,233,.62)` / `.55` / `.5` / `.45` | | Text gedämpft | `#5f5f5f` (Fließtext sekundär), `#767676` (Labels, inaktive Tabs), `#333333` (Beschreibung) |
| Akzent (Mint) | `#8fe3c1`, hell `#a9f0d0` / `#d6fbec`, Fokus `#7ee2b8` | | Akzent (Rot) | `#e60023`, Hover `#ad081b` |
| Akzent-Tint | `rgba(126,226,184,.12)` Fläche, `.4.6` Rahmen, `.14` Hover | | Dunkel | `#111111` (aktiver Rail-Button, aktive Art-Pille, SKIP-Badge), Hover `#000000` |
| Ablehnen | `rgba(232,120,120,.9)` Rahmen, `#f0a8a8` Text, `rgba(120,32,38,.5)` Hover | | Neutrale Flächen | `#efefef` (Suchfeld, Buttons, Tags), Hover `#e2e2e2`; `#e9e9e9` (Rail-Hover); `#f5f5f5` (Kacheln, Dropzone, Leerzustand) |
| Flächen | `rgba(8,26,22,.42)` Sidebar, `rgba(8,22,19,.34)` Detailspalte, `rgba(255,255,255,.02.09)` | | Linien | `#efefef` (Trenner, Kanten), `#cdcdcd` (Eingabe-Rahmen, Toggle aus) |
| Rahmen | `rgba(255,255,255,.07)` / `.1` / `.12` / `.16` / `.18` / `.22` | | Auf Medien | `rgba(255,255,255,.94)` Pills, `rgba(255,255,255,.82)` Untertitel, `rgba(0,0,0,.24)` Hover-Scrim, `rgba(0,0,0,.62)` Lesbarkeitsverlauf |
| Fokus | `#0074e8` |
| Karten-Verläufe | grün `#1f8163→#0d4034`, blau `#256f8b→#0c2f3f`, ocker `#8a6a2f→#3a2a11`, rot `#7a2f38→#2c1014`, tannen `#2f6f5a→#0e332b`, türkis `#1c6f7f→#0a2c33`, violett `#6a2f7a→#26102c`, gold `#7a5a2a→#2f2110` | | Karten-Verläufe | grün `#1f8163→#0d4034`, blau `#256f8b→#0c2f3f`, ocker `#8a6a2f→#3a2a11`, rot `#7a2f38→#2c1014`, tannen `#2f6f5a→#0e332b`, türkis `#1c6f7f→#0a2c33`, violett `#6a2f7a→#26102c`, gold `#7a5a2a→#2f2110` |
**Typografie** — Manrope 400/500/600/700 (UI), JetBrains Mono 400/500 (Zahlen, Captions). **Typografie** **nur Manrope** 400/500/600/700. JetBrains Mono entfällt; Zahlen laufen in Manrope
Skala: 32 / 30 / 26 / 22 / 19 / 17 / 16 / 15 / 14 / 13 / 12 / 11 px. mit `font-variant-numeric: tabular-nums`.
Große Überschriften `letter-spacing:-.02em`, Kicker `+.14em`/`.16em` uppercase. Skala: 36 / 30 / 28 / 20 / 18 / 17 / 16 / 15 / 14 / 13 / 12 px.
Alle Zahlen mit `font-variant-numeric: tabular-nums`. Große Überschriften `letter-spacing:-.02em`. Keine Versalien-Kicker mehr — Kicker sind
12px/600 in `#767676`, normal geschrieben.
**Abstände** 3 / 4 / 6 / 8 / 9 / 10 / 12 / 13 / 14 / 18 / 20 / 22 / 26 / 28 / 34 / 38 / 44 px. **Abstände** 2 / 4 / 6 / 8 / 10 / 12 / 14 / 16 / 18 / 20 / 22 / 24 / 26 / 28 / 30 / 32 / 40 px.
**Radien** 9 / 11 / 12 / 14 / 16 / 18 / 22 / 26 px, Pills `999px`, Kreise `50%`. **Radien** 16px (Karten, Kacheln, Eingaben, Pills auf Medien, Toast, Panel),
**Schatten** nur an der Karte: `0 26px 60px rgba(0,0,0,.45)`. 18 / 22 / 24 / 28px (Tag-, Art-, Button-Pillen), `50%` Kreise, `999px` Toggle-Track.
**Blur** 4 / 6 / 10 / 12 / 14 px (`backdrop-filter`). **Schatten** `0 1px 20px rgba(0,0,0,.12)` Flächen/Listen · `.16` Kürzel-Panel · `.18` Karte ·
**Transitions** `.16s` / `.18s` / `.2s` / `.3s` ease, Karten-Bewegung `.34s cubic-bezier(.22,.9,.3,1)`, Exit `320ms`. `.22` Toast · `0 1px 3px rgba(0,0,0,.25)` Toggle-Knopf.
**Kein `backdrop-filter`** — die Pinterest-Oberfläche ist deckend.
**Transitions** `.12s ease` (Hover, Farbwechsel, Ein-/Ausblenden), `.16s` (Badge),
`.2s` (Toggle), Karten-Bewegung `.34s cubic-bezier(.22,.9,.3,1)`, Exit `320ms`.
--- ---
## Assets ## Assets
Keine Binärdateien. Alle Medien sind Platzhalter aus CSS-Verläufen und Streifenmustern — Keine Binärdateien im Prototyp. Alle Medien sind Platzhalter aus CSS-Verläufen und Streifenmustern —
im Produkt durch echte Bilder/Videos ersetzen. Icons: **Lucide** (im Prototyp Unicode-Glyphen als Platzhalter). im Produkt durch echte Bilder/Videos ersetzen. Icons: **Lucide** (im Prototyp Unicode-Glyphen als
Fonts: Google Fonts — Manrope, JetBrains Mono. Platzhalter). Fonts: Google Fonts — **nur Manrope**.
## Offene Punkte
- **Suchfeld ohne Funktion.** Die Pinterest-Kopfzeile lebt von der Suche, im Prototyp ist sie eine
reine Attrappe — es gibt bisher keine Suchfunktion in der App. Vor der Umsetzung klären, ob Suche
gebaut wird; wenn nicht, gehört das Feld raus, statt eine Funktion vorzutäuschen.
- **Pinterest-Referenz liegt nicht im Repo.** Die Vorlage kam als Screenshot im Chat. Wer sie
dauerhaft braucht, legt sie als `referenz/pinterest-desktop.png` daneben.
## Dateien in diesem Bundle ## Dateien in diesem Bundle
- `PROMPT.md` — fertiger Auftrag zum Einfügen in Claude Code. - `PROMPT.md` — fertiger Auftrag zum Einfügen in Claude Code.

View File

@@ -10,66 +10,70 @@
<helmet> <helmet>
<link rel="preconnect" href="https://fonts.googleapis.com"> <link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin> <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Manrope:wght@400;500;600;700&family=JetBrains+Mono:wght@400;500&display=swap" rel="stylesheet"> <link href="https://fonts.googleapis.com/css2?family=Manrope:wght@400;500;600;700&display=swap" rel="stylesheet">
<style> <style>
html, body { margin:0; padding:0; background:#120b0d; } html, body { margin:0; padding:0; background:#ffffff; }
* { box-sizing: border-box; } * { box-sizing: border-box; }
a { color:#8fe3c1; text-decoration:none; } a { color:#111111; text-decoration:none; }
a:hover { color:#b9f2da; } ::selection { background:rgba(230,0,35,.16); }
::selection { background:rgba(126,226,184,.28); } button:focus-visible, [tabindex]:focus-visible, input:focus-visible { outline:2px solid #0074e8; outline-offset:2px; }
button:focus-visible, [tabindex]:focus-visible { outline:2px solid #7ee2b8; outline-offset:2px; }
@keyframes riseIn { from { opacity:0; transform:translateY(14px);} to { opacity:1; transform:none;} } @keyframes riseIn { from { opacity:0; transform:translateY(14px);} to { opacity:1; transform:none;} }
@keyframes fadeIn { from { opacity:0 } to { opacity:1 } } @keyframes fadeIn { from { opacity:0 } to { opacity:1 } }
@keyframes pulseRing { 0%{ box-shadow:0 0 0 0 rgba(126,226,184,.35)} 70%{ box-shadow:0 0 0 18px rgba(126,226,184,0)} 100%{ box-shadow:0 0 0 0 rgba(126,226,184,0)} } .pin-save { opacity:0; transition:opacity .12s ease; }
.pin:hover .pin-save, .pin:focus-within .pin-save { opacity:1; }
.pin-scrim { opacity:0; transition:opacity .12s ease; }
.pin:hover .pin-scrim { opacity:1; }
.rail { background:transparent; color:#111111; }
.rail:hover { background:#e9e9e9; }
.rail.active { background:#111111; color:#ffffff; }
.rail.active:hover { background:#000000; }
</style> </style>
</helmet> </helmet>
<div style="min-height:100vh; width:100%; min-width:1180px; display:flex; font-family:'Manrope',system-ui,sans-serif; color:#f2efe9; background:radial-gradient(120% 80% at 50% 0%, #0d4c3d 0%, #0a3a30 34%, #24121a 68%, #40161b 100%); background-attachment:fixed;"> <div style="min-height:100vh; width:100%; min-width:1180px; display:flex; font-family:'Manrope',system-ui,sans-serif; color:#111111; background:#ffffff;">
<aside style="width:236px; flex:none; display:flex; flex-direction:column; gap:28px; padding:26px 20px; border-right:1px solid rgba(255,255,255,.08); background:rgba(8,26,22,.42); backdrop-filter:blur(14px); position:sticky; top:0; height:100vh;"> <aside style="width:80px; flex:none; display:flex; flex-direction:column; align-items:center; gap:8px; padding:12px 0 16px; border-right:1px solid #efefef; background:#ffffff; position:sticky; top:0; height:100vh;">
<div style="display:flex; align-items:center; gap:10px;"> <div style="width:48px; height:48px; display:grid; place-items:center; margin-bottom:4px;">
<div style="width:30px; height:30px; border-radius:9px; background:linear-gradient(150deg,#2fa87f,#0e5442); display:grid; place-items:center; font-size:15px; font-weight:700;">V</div> <div style="width:32px; height:32px; border-radius:50%; background:#e60023; display:grid; place-items:center; color:#ffffff; font-size:17px; font-weight:700;">V</div>
<div style="font-size:16px; font-weight:600; letter-spacing:-.01em;">videogen</div>
</div> </div>
<nav style="display:flex; flex-direction:column; gap:4px;"> <nav style="display:flex; flex-direction:column; align-items:center; gap:8px;">
<sc-for list="{{ navItems }}" as="item" hint-placeholder-count="4"> <sc-for list="{{ navTop }}" as="item" hint-placeholder-count="3">
<button onClick="{{ item.go }}" style="{{ item.style }}" style-hover="background:rgba(255,255,255,.09); color:#ffffff;"> <button onClick="{{ item.go }}" title="{{ item.label }}" class="{{ item.cls }}" style="{{ item.style }}">{{ item.glyph }}</button>
<span style="width:20px; display:grid; place-items:center; opacity:.9;">{{ item.glyph }}</span>
<span>{{ item.label }}</span>
</button>
</sc-for> </sc-for>
</nav> </nav>
<div style="margin-top:auto; display:flex; flex-direction:column; gap:14px;"> <div style="margin-top:auto; display:flex; flex-direction:column; align-items:center; gap:10px;">
<div style="border:1px solid rgba(255,255,255,.12); border-radius:14px; padding:14px; background:rgba(255,255,255,.03);"> <sc-for list="{{ navBottom }}" as="item" hint-placeholder-count="1">
<div style="font-size:11px; letter-spacing:.14em; text-transform:uppercase; color:rgba(242,239,233,.5); margin-bottom:8px;">Heute</div> <button onClick="{{ item.go }}" title="{{ item.label }}" class="{{ item.cls }}" style="{{ item.style }}">{{ item.glyph }}</button>
<div style="font-family:'JetBrains Mono',monospace; font-size:22px; font-variant-numeric:tabular-nums;">{{ swipesToday }}</div> </sc-for>
<div style="font-size:12px; color:rgba(242,239,233,.55);">Karten gesichtet</div>
</div>
<div style="display:flex; align-items:center; gap:10px; padding:8px 6px;">
<div style="width:32px; height:32px; border-radius:50%; background:linear-gradient(140deg,#b2323c,#5d1620); flex:none;"></div>
<div style="min-width:0;">
<div style="font-size:13px; font-weight:600;">Jonas K.</div>
<div style="font-size:11px; color:rgba(242,239,233,.5);">Studio-Konto</div>
</div>
</div>
</div> </div>
</aside> </aside>
<main style="flex:1; min-width:0; display:flex; flex-direction:column;"> <main style="flex:1; min-width:0; display:flex; flex-direction:column;">
<header style="height:72px; flex:none; display:flex; align-items:center; justify-content:space-between; padding:0 34px; border-bottom:1px solid rgba(255,255,255,.07);"> <header style="flex:none; display:flex; align-items:center; gap:8px; padding:12px 16px 4px;">
<div style="display:flex; gap:6px; align-items:center;"> <div style="flex:1; min-width:0; display:flex; align-items:center; gap:12px; height:48px; padding:0 16px; border-radius:24px; background:#efefef; color:#767676; font-size:16px;">
<span style="font-size:15px;"></span>
<span style="flex:1; min-width:0;">Suchen</span>
<span style="font-size:15px;"></span>
</div>
<button title="Konto" style="width:40px; height:40px; flex:none; border:none; border-radius:50%; background:linear-gradient(140deg,#b2323c,#5d1620); color:#ffffff; font-size:15px; font-weight:700; cursor:pointer; display:grid; place-items:center;">J</button>
<button title="Konto-Menü" style="width:28px; height:28px; flex:none; border:none; border-radius:50%; background:transparent; color:#5f5f5f; font-size:12px; cursor:pointer;" style-hover="background:#e9e9e9;"></button>
</header>
<div style="flex:none; display:flex; align-items:flex-end; justify-content:space-between; gap:20px; padding:0 24px; border-bottom:1px solid #efefef;">
<div style="display:flex; gap:24px; align-items:flex-end;">
<sc-for list="{{ tabs }}" as="t" hint-placeholder-count="3"> <sc-for list="{{ tabs }}" as="t" hint-placeholder-count="3">
<button onClick="{{ t.go }}" style="{{ t.style }}" style-hover="color:#ffffff;">{{ t.label }}</button> <button onClick="{{ t.go }}" style="{{ t.style }}" style-hover="color:#111111;">{{ t.label }}</button>
</sc-for> </sc-for>
</div> </div>
<div style="display:flex; align-items:center; gap:12px;"> <div style="display:flex; align-items:center; gap:14px; padding-bottom:10px;">
<div style="font-size:12px; color:rgba(242,239,233,.5); font-family:'JetBrains Mono',monospace; font-variant-numeric:tabular-nums;">{{ counter }}</div> <div style="font-size:13px; color:#767676;"><span style="font-weight:700; color:#111111; font-variant-numeric:tabular-nums;">{{ swipesToday }}</span> heute gesichtet</div>
<button onClick="{{ toggleInfo }}" title="Kürzel anzeigen" style="width:36px; height:36px; border-radius:50%; border:1px solid rgba(255,255,255,.18); background:rgba(255,255,255,.04); color:#f2efe9; font-size:14px; cursor:pointer; transition:background .18s ease, transform .18s ease;" style-hover="background:rgba(255,255,255,.12); transform:translateY(-1px);">i</button> <div style="font-size:13px; color:#767676; font-variant-numeric:tabular-nums;">{{ counter }}</div>
<button onClick="{{ toggleInfo }}" title="Kürzel anzeigen" style="width:36px; height:36px; border:none; border-radius:50%; background:transparent; color:#111111; font-size:14px; font-weight:700; cursor:pointer; transition:background .12s ease;" style-hover="background:#e9e9e9;">i</button>
</div>
</div> </div>
</header>
<sc-if value="{{ isFeed }}" hint-placeholder-val="{{ true }}"> <sc-if value="{{ isFeed }}" hint-placeholder-val="{{ true }}">
<section style="flex:1; min-height:0; overflow:auto; display:grid; grid-template-columns:minmax(0,1fr) 372px; gap:0;"> <section style="flex:1; min-height:0; overflow:auto; display:grid; grid-template-columns:minmax(0,1fr) 372px; gap:0;">
@@ -77,25 +81,29 @@
<div style="position:relative; display:flex; flex-direction:column; align-items:center; justify-content:center; padding:26px 20px 30px; gap:22px; min-width:0; min-height:min-content;"> <div style="position:relative; display:flex; flex-direction:column; align-items:center; justify-content:center; padding:26px 20px 30px; gap:22px; min-width:0; min-height:min-content;">
<div style="position:relative; width:min(404px,100%); height:560px; flex:none;"> <div style="position:relative; width:min(404px,100%); height:560px; flex:none;">
<sc-for list="{{ stack }}" as="c" hint-placeholder-count="3"> <sc-for list="{{ stack }}" as="c" hint-placeholder-count="3">
<div style="{{ c.shell }}" onPointerDown="{{ c.down }}" onPointerMove="{{ c.move }}" onPointerUp="{{ c.up }}" onPointerCancel="{{ c.up }}"> <div class="pin" style="{{ c.shell }}" onPointerDown="{{ c.down }}" onPointerMove="{{ c.move }}" onPointerUp="{{ c.up }}" onPointerCancel="{{ c.up }}">
<div style="{{ c.media }}"> <div style="{{ c.media }}">
<div style="position:absolute; inset:0; background:{{ c.stripes }}; opacity:.5;"></div> <div style="position:absolute; inset:0; background:{{ c.stripes }}; opacity:.5;"></div>
<div style="position:absolute; inset:0; background:linear-gradient(180deg, rgba(0,0,0,.32) 0%, rgba(0,0,0,0) 26%, rgba(0,0,0,0) 52%, rgba(0,0,0,.66) 100%);"></div> <div class="pin-scrim" style="position:absolute; inset:0; background:rgba(0,0,0,.24);"></div>
<div style="position:absolute; inset:0; background:linear-gradient(180deg, rgba(0,0,0,0) 46%, rgba(0,0,0,.62) 100%);"></div>
<div style="position:relative; display:flex; gap:9px; padding:18px;"> <div style="position:relative; display:flex; align-items:flex-start; justify-content:space-between; gap:9px; padding:16px;">
<span style="padding:7px 15px; border-radius:999px; border:1px solid rgba(255,255,255,.34); background:rgba(0,0,0,.22); backdrop-filter:blur(6px); font-size:13px; font-weight:600;">{{ c.kind }}</span> <div style="display:flex; gap:8px;">
<span style="padding:7px 15px; border-radius:999px; border:1px solid rgba(255,255,255,.34); background:rgba(0,0,0,.22); backdrop-filter:blur(6px); font-size:13px; font-weight:600;">{{ c.assets }}</span> <span style="padding:6px 12px; border-radius:16px; background:rgba(255,255,255,.94); color:#111111; font-size:13px; font-weight:600;">{{ c.kind }}</span>
<span style="padding:6px 12px; border-radius:16px; background:rgba(255,255,255,.94); color:#111111; font-size:13px; font-weight:600;">{{ c.assets }}</span>
</div>
<button class="pin-save" onClick="{{ onLike }}" style="{{ c.save }}" style-hover="background:#ad081b;">Merken</button>
</div> </div>
<div style="position:absolute; inset:auto 0 118px 0; display:flex; justify-content:center; padding:0 22px;"> <div style="position:absolute; inset:auto 0 104px 0; display:flex; justify-content:center; padding:0 22px;">
<div style="font-family:'JetBrains Mono',monospace; font-size:15px; padding:9px 16px; border-radius:9px; background:rgba(0,0,0,.5); backdrop-filter:blur(4px); text-align:center; text-wrap:pretty;">{{ c.caption }}</div> <div style="font-size:15px; font-weight:600; padding:8px 14px; border-radius:16px; background:rgba(255,255,255,.94); color:#111111; text-align:center; text-wrap:pretty;">{{ c.caption }}</div>
</div> </div>
<div style="position:absolute; inset:auto 0 0 0; display:flex; align-items:center; gap:13px; padding:20px;"> <div style="position:absolute; inset:auto 0 0 0; display:flex; align-items:center; gap:12px; padding:18px; color:#ffffff;">
<div style="width:46px; height:46px; border-radius:50%; flex:none; background:{{ c.avatar }}; border:2px solid rgba(255,255,255,.3);"></div> <div style="width:44px; height:44px; border-radius:50%; flex:none; background:{{ c.avatar }};"></div>
<div style="min-width:0;"> <div style="min-width:0;">
<div style="font-size:19px; font-weight:700; letter-spacing:-.01em;">{{ c.name }}</div> <div style="font-size:18px; font-weight:700; letter-spacing:-.01em;">{{ c.name }}</div>
<div style="font-size:14px; color:rgba(255,255,255,.72);">{{ c.sub }}</div> <div style="font-size:14px; color:rgba(255,255,255,.82);">{{ c.sub }}</div>
</div> </div>
</div> </div>
@@ -105,95 +113,97 @@
</sc-for> </sc-for>
<sc-if value="{{ stackEmpty }}" hint-placeholder-val="{{ false }}"> <sc-if value="{{ stackEmpty }}" hint-placeholder-val="{{ false }}">
<div style="position:absolute; inset:0; border:1px dashed rgba(255,255,255,.2); border-radius:26px; display:flex; flex-direction:column; align-items:center; justify-content:center; gap:16px; text-align:center; padding:40px; animation:fadeIn .3s ease;"> <div style="position:absolute; inset:0; border-radius:16px; background:#f5f5f5; display:flex; flex-direction:column; align-items:center; justify-content:center; gap:14px; text-align:center; padding:40px; animation:fadeIn .3s ease;">
<div style="font-size:20px; font-weight:600;">Alles gesichtet</div> <div style="font-size:20px; font-weight:700;">Alles gesichtet</div>
<div style="font-size:14px; color:rgba(242,239,233,.6); max-width:230px; text-wrap:pretty;">Für heute sind keine neuen Karten mehr in dieser Ansicht.</div> <div style="font-size:15px; color:#5f5f5f; max-width:240px; text-wrap:pretty;">Für heute sind keine neuen Karten mehr in dieser Ansicht.</div>
<button onClick="{{ reset }}" style="padding:11px 20px; border-radius:999px; border:1px solid rgba(126,226,184,.6); background:transparent; color:#8fe3c1; font-family:inherit; font-size:14px; font-weight:600; cursor:pointer; transition:background .18s ease;" style-hover="background:rgba(126,226,184,.14);">Stapel neu laden</button> <button onClick="{{ reset }}" style="padding:12px 20px; border:none; border-radius:24px; background:#e60023; color:#ffffff; font-family:inherit; font-size:15px; font-weight:700; cursor:pointer; transition:background .12s ease;" style-hover="background:#ad081b;">Stapel neu laden</button>
</div> </div>
</sc-if> </sc-if>
</div> </div>
<div style="display:flex; align-items:center; gap:18px;"> <div style="display:flex; align-items:center; gap:16px;">
<button onClick="{{ onNope }}" title="Ablehnen — Pfeil links" style="width:62px; height:62px; border-radius:50%; border:1px solid rgba(255,255,255,.16); background:rgba(14,10,12,.72); color:#f2efe9; font-size:22px; cursor:pointer; display:grid; place-items:center; transition:transform .18s ease, border-color .18s ease, background .18s ease;" style-hover="transform:translateY(-3px) scale(1.05); border-color:rgba(230,120,120,.75); background:rgba(120,32,38,.5);" style-active="transform:scale(.95);"></button> <button onClick="{{ onNope }}" title="Ablehnen — Pfeil links" style="width:56px; height:56px; border:none; border-radius:50%; background:#efefef; color:#111111; font-size:20px; cursor:pointer; display:grid; place-items:center; transition:background .12s ease, transform .12s ease;" style-hover="background:#e2e2e2; transform:scale(1.04);" style-active="transform:scale(.96);"></button>
<button onClick="{{ onUndo }}" title="Zurück — Pfeil hoch" style="width:52px; height:52px; border-radius:50%; border:1px solid rgba(255,255,255,.16); background:rgba(14,10,12,.72); color:#f2efe9; font-size:17px; cursor:pointer; display:grid; place-items:center; transition:transform .18s ease, border-color .18s ease;" style-hover="transform:translateY(-3px) scale(1.05); border-color:rgba(255,255,255,.4);" style-active="transform:scale(.95);"></button> <button onClick="{{ onUndo }}" title="Zurück — Pfeil hoch" style="width:48px; height:48px; border:none; border-radius:50%; background:#efefef; color:#111111; font-size:17px; cursor:pointer; display:grid; place-items:center; transition:background .12s ease, transform .12s ease;" style-hover="background:#e2e2e2; transform:scale(1.04);" style-active="transform:scale(.96);"></button>
<button onClick="{{ onLike }}" title="Merken — Pfeil rechts" style="width:70px; height:70px; border-radius:50%; border:1px solid rgba(126,226,184,.45); background:rgba(14,10,12,.72); color:#8fe3c1; font-size:26px; cursor:pointer; display:grid; place-items:center; transition:transform .18s ease, background .18s ease;" style-hover="transform:translateY(-3px) scale(1.06); background:rgba(20,88,68,.6);" style-active="transform:scale(.95);"></button> <button onClick="{{ onLike }}" title="Merken — Pfeil rechts" style="height:56px; padding:0 30px; border:none; border-radius:28px; background:#e60023; color:#ffffff; font-family:inherit; font-size:17px; font-weight:700; cursor:pointer; display:grid; place-items:center; transition:background .12s ease, transform .12s ease;" style-hover="background:#ad081b; transform:scale(1.04);" style-active="transform:scale(.96);">Merken</button>
</div> </div>
<sc-if value="{{ showInfo }}" hint-placeholder-val="{{ false }}"> <sc-if value="{{ showInfo }}" hint-placeholder-val="{{ false }}">
<div style="position:absolute; right:26px; top:26px; width:250px; border:1px solid rgba(255,255,255,.14); border-radius:14px; background:rgba(10,26,22,.9); backdrop-filter:blur(12px); padding:16px; font-size:13px; line-height:1.8; animation:riseIn .22s ease;"> <div style="position:absolute; right:26px; top:26px; width:250px; border-radius:16px; background:#ffffff; box-shadow:0 1px 20px rgba(0,0,0,.16); padding:16px; font-size:14px; line-height:1.7; animation:riseIn .22s ease;">
<div style="font-weight:700; margin-bottom:8px;">Bedienung</div> <div style="font-weight:700; margin-bottom:8px;">Bedienung</div>
<div style="display:grid; grid-template-columns:auto 1fr; gap:6px 12px; color:rgba(242,239,233,.72);"> <div style="display:grid; grid-template-columns:auto 1fr; gap:6px 12px; color:#5f5f5f;">
<span style="font-family:'JetBrains Mono',monospace;"></span><span>Ablehnen</span> <span style="font-weight:600; color:#111111;"></span><span>Ablehnen</span>
<span style="font-family:'JetBrains Mono',monospace;"></span><span>Merken</span> <span style="font-weight:600; color:#111111;"></span><span>Merken</span>
<span style="font-family:'JetBrains Mono',monospace;"></span><span>Zurücknehmen</span> <span style="font-weight:600; color:#111111;"></span><span>Zurücknehmen</span>
<span style="font-family:'JetBrains Mono',monospace;">Maus</span><span>Karte ziehen</span> <span style="font-weight:600; color:#111111;">Maus</span><span>Karte ziehen</span>
</div> </div>
</div> </div>
</sc-if> </sc-if>
</div> </div>
<aside style="border-left:1px solid rgba(255,255,255,.07); background:rgba(8,22,19,.34); padding:30px 28px; display:flex; flex-direction:column; gap:24px; overflow:auto;"> <aside style="border-left:1px solid #efefef; background:#ffffff; padding:28px; display:flex; flex-direction:column; gap:22px; overflow:auto;">
<div> <div>
<div style="font-size:11px; letter-spacing:.16em; text-transform:uppercase; color:rgba(242,239,233,.45);">Aktuelle Karte</div> <div style="font-size:12px; font-weight:600; color:#767676;">Aktuelle Karte</div>
<div style="font-size:26px; font-weight:600; letter-spacing:-.02em; margin-top:10px; text-wrap:pretty;">{{ detailTitle }}</div> <div style="font-size:28px; font-weight:700; letter-spacing:-.02em; margin-top:8px; text-wrap:pretty;">{{ detailTitle }}</div>
<div style="font-size:14px; color:rgba(242,239,233,.62); margin-top:6px;">{{ detailSub }}</div> <div style="font-size:15px; color:#5f5f5f; margin-top:6px;">{{ detailSub }}</div>
</div> </div>
<div style="height:1px; background:rgba(255,255,255,.09);"></div> <div style="height:1px; background:#efefef;"></div>
<div style="display:grid; grid-template-columns:repeat(3,1fr); gap:12px;"> <div style="display:grid; grid-template-columns:repeat(3,1fr); gap:10px;">
<sc-for list="{{ detailStats }}" as="s" hint-placeholder-count="3"> <sc-for list="{{ detailStats }}" as="s" hint-placeholder-count="3">
<div style="border:1px solid rgba(255,255,255,.1); border-radius:12px; padding:12px;"> <div style="border-radius:16px; background:#f5f5f5; padding:14px;">
<div style="font-family:'JetBrains Mono',monospace; font-size:17px; font-variant-numeric:tabular-nums;">{{ s.value }}</div> <div style="font-size:18px; font-weight:700; font-variant-numeric:tabular-nums;">{{ s.value }}</div>
<div style="font-size:11px; color:rgba(242,239,233,.5); margin-top:3px;">{{ s.label }}</div> <div style="font-size:12px; color:#767676; margin-top:3px;">{{ s.label }}</div>
</div> </div>
</sc-for> </sc-for>
</div> </div>
<div> <div>
<div style="font-size:11px; letter-spacing:.16em; text-transform:uppercase; color:rgba(242,239,233,.45); margin-bottom:10px;">Beschreibung</div> <div style="font-size:12px; font-weight:600; color:#767676; margin-bottom:8px;">Beschreibung</div>
<p style="margin:0; font-size:14px; line-height:1.75; color:rgba(242,239,233,.78); text-wrap:pretty;">{{ detailBody }}</p> <p style="margin:0; font-size:15px; line-height:1.7; color:#333333; text-wrap:pretty;">{{ detailBody }}</p>
</div> </div>
<div> <div>
<div style="font-size:11px; letter-spacing:.16em; text-transform:uppercase; color:rgba(242,239,233,.45); margin-bottom:10px;">Tags</div> <div style="font-size:12px; font-weight:600; color:#767676; margin-bottom:10px;">Tags</div>
<div style="display:flex; flex-wrap:wrap; gap:8px;"> <div style="display:flex; flex-wrap:wrap; gap:8px;">
<sc-for list="{{ detailTags }}" as="tag" hint-placeholder-count="4"> <sc-for list="{{ detailTags }}" as="tag" hint-placeholder-count="4">
<span style="padding:6px 12px; border-radius:999px; border:1px solid rgba(255,255,255,.18); font-size:12px; color:rgba(242,239,233,.8);">{{ tag }}</span> <span style="padding:8px 14px; border-radius:18px; background:#efefef; font-size:13px; font-weight:600; color:#111111;">{{ tag }}</span>
</sc-for> </sc-for>
</div> </div>
</div> </div>
<div style="margin-top:auto; display:flex; flex-direction:column; gap:10px;"> <div style="margin-top:auto; display:flex; flex-direction:column; gap:8px;">
<button onClick="{{ onLike }}" style="padding:13px; border-radius:12px; border:1px solid rgba(126,226,184,.55); background:transparent; color:#8fe3c1; font-family:inherit; font-size:14px; font-weight:600; cursor:pointer; transition:background .18s ease;" style-hover="background:rgba(126,226,184,.14);">Zur Sammlung hinzufügen</button> <button onClick="{{ onLike }}" style="padding:14px; border:none; border-radius:24px; background:#e60023; color:#ffffff; font-family:inherit; font-size:15px; font-weight:700; cursor:pointer; transition:background .12s ease;" style-hover="background:#ad081b;">Zur Sammlung hinzufügen</button>
<button onClick="{{ onNope }}" style="padding:13px; border-radius:12px; border:1px solid rgba(255,255,255,.16); background:transparent; color:rgba(242,239,233,.8); font-family:inherit; font-size:14px; cursor:pointer; transition:background .18s ease;" style-hover="background:rgba(255,255,255,.07);">Überspringen</button> <button onClick="{{ onNope }}" style="padding:14px; border:none; border-radius:24px; background:#efefef; color:#111111; font-family:inherit; font-size:15px; font-weight:700; cursor:pointer; transition:background .12s ease;" style-hover="background:#e2e2e2;">Überspringen</button>
</div> </div>
</aside> </aside>
</section> </section>
</sc-if> </sc-if>
<sc-if value="{{ isProfile }}" hint-placeholder-val="{{ false }}"> <sc-if value="{{ isProfile }}" hint-placeholder-val="{{ false }}">
<section style="flex:1; padding:38px 44px; overflow:auto; animation:riseIn .26s ease;"> <section style="flex:1; padding:32px 24px 40px; overflow:auto; animation:riseIn .26s ease;">
<div style="display:flex; align-items:flex-end; gap:22px; padding-bottom:26px; border-bottom:1px solid rgba(255,255,255,.09);"> <div style="display:flex; flex-direction:column; align-items:center; gap:10px; padding-bottom:28px;">
<div style="width:96px; height:96px; border-radius:50%; background:linear-gradient(140deg,#b2323c,#5d1620); flex:none; border:2px solid rgba(255,255,255,.2);"></div> <div style="width:120px; height:120px; border-radius:50%; background:linear-gradient(140deg,#b2323c,#5d1620); flex:none;"></div>
<div style="flex:1;"> <div style="font-size:36px; font-weight:700; letter-spacing:-.02em; text-align:center;">Jonas K.</div>
<div style="font-size:32px; font-weight:600; letter-spacing:-.02em;">Jonas K.</div> <div style="font-size:15px; color:#5f5f5f;">Studio-Konto · seit März 2026</div>
<div style="font-size:14px; color:rgba(242,239,233,.6); margin-top:4px;">Studio-Konto · seit März 2026</div> <div style="display:flex; gap:28px; margin-top:6px;">
</div>
<div style="display:flex; gap:34px; padding-bottom:6px;">
<sc-for list="{{ profileStats }}" as="s" hint-placeholder-count="3"> <sc-for list="{{ profileStats }}" as="s" hint-placeholder-count="3">
<div> <div style="text-align:center;">
<div style="font-family:'JetBrains Mono',monospace; font-size:22px; font-variant-numeric:tabular-nums;">{{ s.value }}</div> <div style="font-size:18px; font-weight:700; font-variant-numeric:tabular-nums;">{{ s.value }}</div>
<div style="font-size:12px; color:rgba(242,239,233,.5);">{{ s.label }}</div> <div style="font-size:13px; color:#767676;">{{ s.label }}</div>
</div> </div>
</sc-for> </sc-for>
</div> </div>
</div> </div>
<div style="display:grid; grid-template-columns:repeat(4,1fr); gap:18px; margin-top:28px;"> <div style="columns:5; column-gap:16px;">
<sc-for list="{{ gallery }}" as="g" hint-placeholder-count="8"> <sc-for list="{{ gallery }}" as="g" hint-placeholder-count="8">
<div style="{{ g.tile }}" style-hover="transform:translateY(-4px); border-color:rgba(126,226,184,.5);"> <div class="pin" style="{{ g.tile }}">
<div style="position:absolute; inset:0; background:{{ g.stripes }}; opacity:.45;"></div> <div style="position:absolute; inset:0; background:{{ g.stripes }}; opacity:.45;"></div>
<div style="position:absolute; inset:auto 0 0 0; padding:14px; background:linear-gradient(0deg, rgba(0,0,0,.7), transparent); font-size:13px; font-weight:600;">{{ g.title }}</div> <div class="pin-scrim" style="position:absolute; inset:0; background:rgba(0,0,0,.24);"></div>
<div style="position:absolute; right:12px; top:12px;">
<button class="pin-save" onClick="{{ onLike }}" style="padding:11px 16px; border:none; border-radius:22px; background:#e60023; color:#ffffff; font-family:inherit; font-size:15px; font-weight:700; cursor:pointer;" style-hover="background:#ad081b;">Merken</button>
</div>
<div style="position:absolute; inset:auto 0 0 0; padding:14px; background:linear-gradient(0deg, rgba(0,0,0,.62), transparent); color:#ffffff; font-size:14px; font-weight:600;">{{ g.title }}</div>
</div> </div>
</sc-for> </sc-for>
</div> </div>
@@ -201,47 +211,47 @@
</sc-if> </sc-if>
<sc-if value="{{ isCreate }}" hint-placeholder-val="{{ false }}"> <sc-if value="{{ isCreate }}" hint-placeholder-val="{{ false }}">
<section style="flex:1; overflow:auto; padding:38px 44px; animation:riseIn .26s ease;"> <section style="flex:1; overflow:auto; padding:32px 24px 40px; animation:riseIn .26s ease;">
<div style="max-width:920px; display:grid; grid-template-columns:1fr 320px; gap:34px;"> <div style="max-width:920px; margin:0 auto; border-radius:24px; box-shadow:0 1px 20px rgba(0,0,0,.12); padding:32px; display:grid; grid-template-columns:1fr 320px; gap:32px;">
<div style="display:flex; flex-direction:column; gap:22px;"> <div style="display:flex; flex-direction:column; gap:20px;">
<div> <div>
<div style="font-size:30px; font-weight:600; letter-spacing:-.02em;">Neuen Post erstellen</div> <div style="font-size:30px; font-weight:700; letter-spacing:-.02em;">Neuen Post erstellen</div>
<div style="font-size:14px; color:rgba(242,239,233,.6); margin-top:6px;">Material hochladen, beschriften, in den Feed geben.</div> <div style="font-size:15px; color:#5f5f5f; margin-top:6px;">Material hochladen, beschriften, in den Feed geben.</div>
</div> </div>
<div style="border:1px dashed rgba(255,255,255,.24); border-radius:18px; height:220px; display:grid; place-items:center; text-align:center; gap:8px; background:rgba(255,255,255,.03); cursor:pointer; transition:border-color .18s ease, background .18s ease;" style-hover="border-color:rgba(126,226,184,.6); background:rgba(126,226,184,.06);"> <div style="border:2px dashed #cdcdcd; border-radius:16px; height:220px; display:grid; place-items:center; text-align:center; background:#f5f5f5; cursor:pointer; transition:border-color .12s ease, background .12s ease;" style-hover="border-color:#e60023; background:#fafafa;">
<div> <div>
<div style="font-size:15px; font-weight:600;">Dateien hierher ziehen</div> <div style="font-size:16px; font-weight:700;">Dateien hierher ziehen</div>
<div style="font-size:13px; color:rgba(242,239,233,.55); margin-top:6px;">MP4, MOV oder bis zu 10 Bilder</div> <div style="font-size:14px; color:#767676; margin-top:6px;">MP4, MOV oder bis zu 10 Bilder</div>
</div> </div>
</div> </div>
<div style="display:flex; flex-direction:column; gap:16px;"> <div style="display:flex; flex-direction:column; gap:16px;">
<label style="display:flex; flex-direction:column; gap:8px; font-size:12px; letter-spacing:.14em; text-transform:uppercase; color:rgba(242,239,233,.5);">Titel <label style="display:flex; flex-direction:column; gap:8px; font-size:13px; font-weight:600; color:#111111;">Titel
<input value="{{ draftTitle }}" onChange="{{ setTitle }}" style="padding:13px 15px; border-radius:11px; border:1px solid rgba(255,255,255,.16); background:rgba(0,0,0,.24); color:#f2efe9; font-family:inherit; font-size:15px; letter-spacing:normal; text-transform:none;"> <input value="{{ draftTitle }}" onChange="{{ setTitle }}" style="padding:14px 16px; border-radius:16px; border:2px solid #cdcdcd; background:#ffffff; color:#111111; font-family:inherit; font-size:15px; font-weight:400;">
</label> </label>
<label style="display:flex; flex-direction:column; gap:8px; font-size:12px; letter-spacing:.14em; text-transform:uppercase; color:rgba(242,239,233,.5);">Untertitel <label style="display:flex; flex-direction:column; gap:8px; font-size:13px; font-weight:600; color:#111111;">Untertitel
<input value="{{ draftSub }}" onChange="{{ setSub }}" style="padding:13px 15px; border-radius:11px; border:1px solid rgba(255,255,255,.16); background:rgba(0,0,0,.24); color:#f2efe9; font-family:inherit; font-size:15px; letter-spacing:normal; text-transform:none;"> <input value="{{ draftSub }}" onChange="{{ setSub }}" style="padding:14px 16px; border-radius:16px; border:2px solid #cdcdcd; background:#ffffff; color:#111111; font-family:inherit; font-size:15px; font-weight:400;">
</label> </label>
<div style="display:flex; flex-direction:column; gap:10px; font-size:12px; letter-spacing:.14em; text-transform:uppercase; color:rgba(242,239,233,.5);">Art <div style="display:flex; flex-direction:column; gap:10px; font-size:13px; font-weight:600; color:#111111;">Art
<div style="display:flex; gap:8px;"> <div style="display:flex; gap:8px;">
<sc-for list="{{ kindOptions }}" as="k" hint-placeholder-count="3"> <sc-for list="{{ kindOptions }}" as="k" hint-placeholder-count="3">
<button onClick="{{ k.go }}" style="{{ k.style }}" style-hover="border-color:rgba(126,226,184,.6);">{{ k.label }}</button> <button onClick="{{ k.go }}" style="{{ k.style }}">{{ k.label }}</button>
</sc-for> </sc-for>
</div> </div>
</div> </div>
</div> </div>
<div style="display:flex; gap:12px;"> <div style="display:flex; gap:8px;">
<button onClick="{{ publish }}" style="padding:13px 24px; border-radius:12px; border:1px solid rgba(126,226,184,.6); background:transparent; color:#8fe3c1; font-family:inherit; font-size:14px; font-weight:600; cursor:pointer; transition:background .18s ease;" style-hover="background:rgba(126,226,184,.14);">Veröffentlichen</button> <button onClick="{{ publish }}" style="padding:14px 24px; border:none; border-radius:24px; background:#e60023; color:#ffffff; font-family:inherit; font-size:15px; font-weight:700; cursor:pointer; transition:background .12s ease;" style-hover="background:#ad081b;">Veröffentlichen</button>
<button style="padding:13px 24px; border-radius:12px; border:1px solid rgba(255,255,255,.16); background:transparent; color:rgba(242,239,233,.8); font-family:inherit; font-size:14px; cursor:pointer; transition:background .18s ease;" style-hover="background:rgba(255,255,255,.07);">Als Entwurf sichern</button> <button style="padding:14px 24px; border:none; border-radius:24px; background:#efefef; color:#111111; font-family:inherit; font-size:15px; font-weight:700; cursor:pointer; transition:background .12s ease;" style-hover="background:#e2e2e2;">Als Entwurf sichern</button>
</div> </div>
</div> </div>
<div> <div>
<div style="font-size:11px; letter-spacing:.16em; text-transform:uppercase; color:rgba(242,239,233,.45); margin-bottom:12px;">Vorschau</div> <div style="font-size:12px; font-weight:600; color:#767676; margin-bottom:12px;">Vorschau</div>
<div style="position:relative; height:420px; border-radius:22px; overflow:hidden; border:1px solid rgba(255,255,255,.12); background:linear-gradient(155deg,#1f8163 0%,#0d4034 100%);"> <div style="position:relative; height:420px; border-radius:16px; overflow:hidden; background:linear-gradient(155deg,#1f8163 0%,#0d4034 100%);">
<div style="position:absolute; inset:0; background:repeating-linear-gradient(48deg, rgba(255,255,255,.07) 0 2px, transparent 2px 15px); opacity:.5;"></div> <div style="position:absolute; inset:0; background:repeating-linear-gradient(48deg, rgba(255,255,255,.07) 0 2px, transparent 2px 15px); opacity:.5;"></div>
<div style="position:absolute; inset:auto 0 0 0; padding:18px; background:linear-gradient(0deg, rgba(0,0,0,.72), transparent);"> <div style="position:absolute; inset:auto 0 0 0; padding:18px; background:linear-gradient(0deg, rgba(0,0,0,.68), transparent); color:#ffffff;">
<div style="font-size:18px; font-weight:700;">{{ draftTitle }}</div> <div style="font-size:18px; font-weight:700;">{{ draftTitle }}</div>
<div style="font-size:13px; color:rgba(255,255,255,.7);">{{ draftSub }}</div> <div style="font-size:14px; color:rgba(255,255,255,.8);">{{ draftSub }}</div>
</div> </div>
</div> </div>
</div> </div>
@@ -250,15 +260,15 @@
</sc-if> </sc-if>
<sc-if value="{{ isSettings }}" hint-placeholder-val="{{ false }}"> <sc-if value="{{ isSettings }}" hint-placeholder-val="{{ false }}">
<section style="flex:1; overflow:auto; padding:38px 44px; animation:riseIn .26s ease;"> <section style="flex:1; overflow:auto; padding:32px 24px 40px; animation:riseIn .26s ease;">
<div style="max-width:660px;"> <div style="max-width:660px; margin:0 auto;">
<div style="font-size:30px; font-weight:600; letter-spacing:-.02em;">Einstellungen</div> <div style="font-size:30px; font-weight:700; letter-spacing:-.02em;">Einstellungen</div>
<div style="margin-top:26px; border:1px solid rgba(255,255,255,.1); border-radius:16px; overflow:hidden;"> <div style="margin-top:24px; border-radius:16px; overflow:hidden; box-shadow:0 1px 20px rgba(0,0,0,.12);">
<sc-for list="{{ settings }}" as="s" hint-placeholder-count="4"> <sc-for list="{{ settings }}" as="s" hint-placeholder-count="4">
<div style="display:flex; align-items:center; justify-content:space-between; gap:20px; padding:18px 20px; border-bottom:1px solid rgba(255,255,255,.07); background:rgba(255,255,255,.02);"> <div style="display:flex; align-items:center; justify-content:space-between; gap:20px; padding:20px; border-bottom:1px solid #efefef; background:#ffffff;">
<div> <div>
<div style="font-size:15px; font-weight:600;">{{ s.label }}</div> <div style="font-size:16px; font-weight:700;">{{ s.label }}</div>
<div style="font-size:13px; color:rgba(242,239,233,.55); margin-top:3px; text-wrap:pretty;">{{ s.hint }}</div> <div style="font-size:14px; color:#767676; margin-top:3px; text-wrap:pretty;">{{ s.hint }}</div>
</div> </div>
<button onClick="{{ s.toggle }}" style="{{ s.track }}"> <button onClick="{{ s.toggle }}" style="{{ s.track }}">
<span style="{{ s.knob }}"></span> <span style="{{ s.knob }}"></span>
@@ -271,7 +281,7 @@
</sc-if> </sc-if>
<sc-if value="{{ toast }}" hint-placeholder-val="{{ false }}"> <sc-if value="{{ toast }}" hint-placeholder-val="{{ false }}">
<div style="position:fixed; left:50%; bottom:34px; transform:translateX(-50%); padding:12px 22px; border-radius:999px; border:1px solid rgba(255,255,255,.16); background:rgba(10,24,20,.92); backdrop-filter:blur(10px); font-size:14px; animation:riseIn .2s ease; z-index:40;">{{ toast }}</div> <div style="position:fixed; left:50%; bottom:28px; transform:translateX(-50%); padding:16px 22px; border-radius:16px; background:#ffffff; box-shadow:0 1px 20px rgba(0,0,0,.22); color:#111111; font-size:15px; font-weight:600; animation:riseIn .2s ease; z-index:40;">{{ toast }}</div>
</sc-if> </sc-if>
</main> </main>
</div> </div>
@@ -386,8 +396,8 @@ class Component extends DCLogic {
} }
const shell = { const shell = {
position: 'absolute', inset: 0, zIndex: 10 - k, opacity, transform, transition, position: 'absolute', inset: 0, zIndex: 10 - k, opacity, transform, transition,
borderRadius: '26px', overflow: 'hidden', boxShadow: '0 26px 60px rgba(0,0,0,.45)', borderRadius: '16px', overflow: 'hidden', boxShadow: '0 1px 20px rgba(0,0,0,.18)',
border: '1px solid rgba(255,255,255,.14)', touchAction: 'none', touchAction: 'none',
cursor: top ? (s.drag && s.drag.active ? 'grabbing' : 'grab') : 'default', cursor: top ? (s.drag && s.drag.active ? 'grabbing' : 'grab') : 'default',
pointerEvents: top ? 'auto' : 'none', userSelect: 'none' pointerEvents: top ? 'auto' : 'none', userSelect: 'none'
}; };
@@ -402,12 +412,16 @@ class Component extends DCLogic {
stripes: this.stripes(s.idx + k), stripes: this.stripes(s.idx + k),
kind: card.kind, assets: card.assets, caption: card.caption, avatar: card.avatar, kind: card.kind, assets: card.assets, caption: card.caption, avatar: card.avatar,
name: card.name, sub: card.sub, name: card.name, sub: card.sub,
save: {
display: top ? 'block' : 'none', padding: '11px 16px', border: 'none', borderRadius: '22px',
background: '#e60023', color: '#ffffff', font: '700 15px/1 Manrope, system-ui, sans-serif',
cursor: 'pointer', transition: 'background .12s ease'
},
badgeText: liked ? 'GEMERKT' : 'SKIP', badgeText: liked ? 'GEMERKT' : 'SKIP',
badge: { badge: {
position: 'absolute', top: '86px', left: liked ? '24px' : 'auto', right: liked ? 'auto' : '24px', position: 'absolute', top: '86px', left: liked ? '24px' : 'auto', right: liked ? 'auto' : '24px',
padding: '8px 16px', borderRadius: '10px', fontWeight: 800, fontSize: '15px', letterSpacing: '.12em', padding: '10px 18px', borderRadius: '22px', fontWeight: 700, fontSize: '15px', letterSpacing: '.08em',
border: '2px solid ' + (liked ? 'rgba(126,226,184,.9)' : 'rgba(232,120,120,.9)'), background: liked ? '#e60023' : '#111111', color: '#ffffff',
color: liked ? '#a9f0d0' : '#f0a8a8',
transform: 'rotate(' + (liked ? -10 : 10) + 'deg)', transform: 'rotate(' + (liked ? -10 : 10) + 'deg)',
opacity: showBadge ? Math.min(1, Math.abs(dx) / 110 || 1) : 0, opacity: showBadge ? Math.min(1, Math.abs(dx) / 110 || 1) : 0,
transition: 'opacity .16s ease', pointerEvents: 'none' transition: 'opacity .16s ease', pointerEvents: 'none'
@@ -415,40 +429,41 @@ class Component extends DCLogic {
}); });
} }
const navDef = [['feed','Feed','◧'],['profile','Profil','◍'],['create','Erstellen',''],['settings','Einstellungen','⚙']]; // Rail-Zustaende liegen in .rail/.rail.active (helmet), nicht inline: das Runtime setzt
const navItems = navDef.map(([id, label, glyph]) => ({ // style-hover mit !important und wuerde den aktiven Button sonst beim Hover aufhellen.
const railStyle = {
width: '48px', height: '48px', display: 'grid', placeItems: 'center', border: 'none',
borderRadius: '50%', cursor: 'pointer', fontSize: '20px',
transition: 'background .12s ease, color .12s ease'
};
const navDef = [['feed','Feed','⌂'],['profile','Profil','◍'],['create','Erstellen',''],['settings','Einstellungen','⚙']];
const navAll = navDef.map(([id, label, glyph]) => ({
label, glyph, go: () => this.setState({ screen: id }), label, glyph, go: () => this.setState({ screen: id }),
style: { cls: s.screen === id ? 'rail active' : 'rail', style: railStyle
display: 'flex', alignItems: 'center', gap: '11px', padding: '11px 13px', borderRadius: '11px',
border: '1px solid ' + (s.screen === id ? 'rgba(126,226,184,.4)' : 'transparent'),
background: s.screen === id ? 'rgba(126,226,184,.12)' : 'transparent',
color: s.screen === id ? '#d6fbec' : 'rgba(242,239,233,.7)',
font: '500 14px/1 Manrope, system-ui, sans-serif', cursor: 'pointer',
transition: 'background .18s ease, color .18s ease', textAlign: 'left'
}
})); }));
const navTop = navAll.slice(0, 3);
const navBottom = navAll.slice(3);
const tabDef = [['videos','Videos'],['following','Folge ich'],['posts','Posts']]; const tabDef = [['videos','Videos'],['following','Folge ich'],['posts','Posts']];
const tabs = tabDef.map(([id, label]) => ({ const tabs = tabDef.map(([id, label]) => ({
label, go: () => this.setState({ tab: id, idx: 0, history: [], drag: null, exiting: null, screen: 'feed' }), label, go: () => this.setState({ tab: id, idx: 0, history: [], drag: null, exiting: null, screen: 'feed' }),
style: { style: {
padding: '9px 18px', borderRadius: '999px', border: '1px solid ' + (s.tab === id ? 'rgba(255,255,255,.22)' : 'transparent'), padding: '0 2px 12px', border: 'none', background: 'transparent',
background: s.tab === id ? 'rgba(255,255,255,.08)' : 'transparent', borderBottom: '3px solid ' + (s.tab === id ? '#111111' : 'transparent'),
color: s.tab === id ? '#ffffff' : 'rgba(242,239,233,.58)', color: s.tab === id ? '#111111' : '#767676',
font: (s.tab === id ? '600' : '500') + ' 16px/1 Manrope, system-ui, sans-serif', font: (s.tab === id ? '700' : '500') + ' 16px/1 Manrope, system-ui, sans-serif',
cursor: 'pointer', transition: 'background .18s ease, color .18s ease' cursor: 'pointer', transition: 'color .12s ease, border-color .12s ease'
} }
})); }));
const kindOptions = ['Produkt','Video','Serie'].map(k => ({ const kindOptions = ['Produkt','Video','Serie'].map(k => ({
label: k, go: () => this.setState({ draftKind: k }), label: k, go: () => this.setState({ draftKind: k }),
style: { style: {
padding: '10px 18px', borderRadius: '999px', cursor: 'pointer', padding: '11px 18px', borderRadius: '22px', cursor: 'pointer', border: 'none',
border: '1px solid ' + (s.draftKind === k ? 'rgba(126,226,184,.6)' : 'rgba(255,255,255,.16)'), background: s.draftKind === k ? '#111111' : '#efefef',
background: s.draftKind === k ? 'rgba(126,226,184,.12)' : 'transparent', color: s.draftKind === k ? '#ffffff' : '#111111',
color: s.draftKind === k ? '#a9f0d0' : 'rgba(242,239,233,.75)', font: '700 15px/1 Manrope, system-ui, sans-serif',
font: '500 14px/1 Manrope, system-ui, sans-serif', letterSpacing: 'normal', textTransform: 'none', transition: 'background .12s ease, color .12s ease'
transition: 'border-color .18s ease, background .18s ease'
} }
})); }));
@@ -462,22 +477,22 @@ class Component extends DCLogic {
label, hint, label, hint,
toggle: () => this.setState(st => ({ settings: { ...st.settings, [id]: !st.settings[id] } })), toggle: () => this.setState(st => ({ settings: { ...st.settings, [id]: !st.settings[id] } })),
track: { position: 'relative', width: '52px', height: '30px', flex: 'none', borderRadius: '999px', cursor: 'pointer', track: { position: 'relative', width: '52px', height: '30px', flex: 'none', borderRadius: '999px', cursor: 'pointer',
border: '1px solid ' + (on ? 'rgba(126,226,184,.6)' : 'rgba(255,255,255,.2)'), border: 'none', background: on ? '#e60023' : '#cdcdcd', transition: 'background .2s ease' },
background: on ? 'rgba(126,226,184,.18)' : 'rgba(255,255,255,.05)', transition: 'background .2s ease, border-color .2s ease' }, knob: { position: 'absolute', top: '3px', left: on ? '25px' : '3px', width: '24px', height: '24px', borderRadius: '50%',
knob: { position: 'absolute', top: '3px', left: on ? '25px' : '3px', width: '22px', height: '22px', borderRadius: '50%', background: '#ffffff', boxShadow: '0 1px 3px rgba(0,0,0,.25)', transition: 'left .2s cubic-bezier(.22,.9,.3,1)' }
background: on ? '#8fe3c1' : 'rgba(242,239,233,.55)', transition: 'left .2s cubic-bezier(.22,.9,.3,1), background .2s ease' }
}; };
}); });
const tileHeights = [320, 240, 400, 280, 360, 220, 300, 260];
const gallery = DATA.posts.concat(DATA.videos).map((c, i) => ({ const gallery = DATA.posts.concat(DATA.videos).map((c, i) => ({
title: c.caption, stripes: this.stripes(i), title: c.caption, stripes: this.stripes(i),
tile: { position: 'relative', height: '230px', borderRadius: '16px', overflow: 'hidden', cursor: 'pointer', tile: { position: 'relative', height: tileHeights[i % tileHeights.length] + 'px', borderRadius: '16px',
border: '1px solid rgba(255,255,255,.12)', transition: 'transform .2s ease, border-color .2s ease', overflow: 'hidden', cursor: 'zoom-in', breakInside: 'avoid', marginBottom: '16px',
background: 'linear-gradient(155deg,' + c.hue[0] + ' 0%,' + c.hue[1] + ' 100%)' } background: 'linear-gradient(155deg,' + c.hue[0] + ' 0%,' + c.hue[1] + ' 100%)' }
})); }));
return { return {
navItems, tabs, stack, kindOptions, settings, gallery, navTop, navBottom, tabs, stack, kindOptions, settings, gallery,
isFeed: s.screen === 'feed', isProfile: s.screen === 'profile', isCreate: s.screen === 'create', isSettings: s.screen === 'settings', isFeed: s.screen === 'feed', isProfile: s.screen === 'profile', isCreate: s.screen === 'create', isSettings: s.screen === 'settings',
stackEmpty: stack.length === 0, stackEmpty: stack.length === 0,
showInfo: s.showInfo, toggleInfo: () => this.setState(st => ({ showInfo: !st.showInfo })), showInfo: s.showInfo, toggleInfo: () => this.setState(st => ({ showInfo: !st.showInfo })),