Compare commits

..

1 Commits

Author SHA1 Message Date
a6b3a42f2a fix: Branchen-Lock aus P7/P8 entfernen, Verbotslisten angleichen, Doku-Lücken schließen
Hintergrund: Review aller 22 Prompt-Templates (P1-P18 + P8-Adapter) auf
Konsistenz. BrandLoop ist als branchenneutrales Multi-Tenant-System angelegt
(README: "Team-Permission der jeweiligen Brand") - vier Stellen wichen davon ab
oder waren nur implizit dokumentiert.

1. Branchen-Lock entfernt (P07-bild-prompt-generator.md, P08-video-kern.md)
   Beide öffneten mit "... einer Werbe-Produktion fuer Kosmetik-Brands."
   Einzige Branchen-Festlegung im gesamten Prompt-Set - alle anderen 20
   Dateien, das DB-Schema und die Quelldokumente (docs/banana-pro-director-2.0.md)
   sind branchenneutral. Vermutlich ein Rest aus einer Kosmetik-Beispieldomäne
   beim urspruenglichen Schreiben. Zusaetzlich in P07 "Flakons" (Kosmetik-/
   Parfuembegriff) durch "Behaeltern" ersetzt, damit die Anleitung fuer
   transparente Fluessigkeiten branchenunabhaengig bleibt (Getraenke,
   Reinigungsprodukte etc., nicht nur Parfuem/Serum).

2. Altersbezeichnungen-Verbot auf die Vision-/Merkmal-Quellen ausgeweitet
   (P01-upload-analyst.md, P16-referenz-interpreter.md)
   P07/P08/alle vier Adapter verbieten Altersbezeichnungen im Output - aber
   P01 und P16 sind die Stellen, die die `merkmale`-Texte ERZEUGEN, welche
   per Token-Locking spaeter woertlich in genau diese Prompts uebernommen
   werden (P01 Regel 6: "sie werden spaeter exakt so in Prompts eingesetzt").
   Ohne das Verbot an der Quelle haette sich eine Altersbezeichnung durch
   die ganze Kette bis in den finalen Bild-/Video-Prompt durchschmuggeln
   koennen, wo sie eigentlich untersagt ist.

3. Tiebreak-Regel fuer exakten Score-Gleichstand (P04-knowledge-selector.md)
   Regel 2 sagte bereits, wie ein Unter-Attribut ein Eltern-Attribut bei
   hoeherem Score schlaegt, aber nicht, was bei einem exakten Gleichstand
   zwischen zwei Attributen der gleichen Ebene passiert (anders als bei
   Video-Gleichstand, wo P10 explizit dafuer existiert). Ergaenzt: bei
   exaktem Gleichstand gewinnt das mit dem hoeheren `used_count` (das
   breiter getestete Attribut) - vermeidet, dass diese Randfaelle
   zufaellig/uneinheitlich im Code gehandhabt werden.

4. Zwei bisher nur implizite Konzepte in prompts/README.md dokumentiert:
   - "Die 8 Grundkategorien": P15 erwaehnt "die 8 Grundkategorien" als
     feststehenden Begriff, ohne sie je aufzulisten - bisher nur aus den
     JSON-Schema-Keys von P09 rekonstruierbar (location, licht, farben,
     kamera, voice, geraeusche, texte_hooks, handlungen). Jetzt als
     eigene Tabelle festgehalten, da praktisch jeder Prompt darauf aufbaut.
   - "Script-Felder <-> Kategorien": P05 gibt Shots in sechs Feldern aus
     (Bild/Aktion/Kamera/Licht/Audio/Text im Bild), die nicht 1:1 den acht
     Kategorie-Slugs entsprechen (z.B. buendelt "Audio" voice+geraeusche,
     "Bild" buendelt location+farben). Diese Zuordnung war nirgends
     schriftlich festgehalten, obwohl Code, der Script-Inhalte spaeter
     Kategorien zuordnen muss (z.B. fuer P6-Diffs), sie braucht.

Kein Prompt-Body enthaelt neue Erklaer-Kommentare - die Bodies werden
woertlich an die KI-Modelle geschickt (static_core_md), Meta-Kommentare
darin wuerden mit in den Prompt wandern. Die Begruendungen stehen daher
ausschliesslich hier in der Commit-Message und in prompts/README.md
(reine Doku, wird nicht geseedet).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-22 07:56:46 +02:00
7 changed files with 91 additions and 162 deletions

View File

@@ -16,7 +16,7 @@ Du bist der Vision-Analyst des Onboardings. Der Nutzer hat Logo, Produktbilder u
4. Pro Attribut: prägnanter deutscher Name, URL-tauglicher `slug` (kleinbuchstaben-mit-bindestrichen), 25 `tags`, eine kurze deutsche `beschreibung` (13 Sätze: was ist das, wie wirkt es für DIESE Brand), 13 englische `prompt_bausteine` (direkt in Bild-/Video-Prompts einsetzbar, z. B. "warm sandstone tones, golden hour side light") und optional englische `negativ_prompts`. Dazu `quelle`: welcher Upload die Beobachtung belegt. 4. Pro Attribut: prägnanter deutscher Name, URL-tauglicher `slug` (kleinbuchstaben-mit-bindestrichen), 25 `tags`, eine kurze deutsche `beschreibung` (13 Sätze: was ist das, wie wirkt es für DIESE Brand), 13 englische `prompt_bausteine` (direkt in Bild-/Video-Prompts einsetzbar, z. B. "warm sandstone tones, golden hour side light") und optional englische `negativ_prompts`. Dazu `quelle`: welcher Upload die Beobachtung belegt.
5. Wiederkehrendes vor Einmaligem: Ein Attribut nur vorschlagen, wenn es die Brand erkennbar prägt (in mehreren Uploads oder dominant in einem zentralen). Maximal 12 Attribute insgesamt. 5. Wiederkehrendes vor Einmaligem: Ein Attribut nur vorschlagen, wenn es die Brand erkennbar prägt (in mehreren Uploads oder dominant in einem zentralen). Maximal 12 Attribute insgesamt.
6. Asset-Kandidaten: wiederkehrende Gesichter, Produkte und das Logo als `assets` mit `typ` (gesicht/produkt/logo/sonstiges), Name, sichtbaren `merkmale` (präzise, wörtlich wiederverwendbar formuliert — sie werden später exakt so in Prompts eingesetzt) und `quelle`. Bei Gesichtern zusätzlich `einwilligung_noetig: true`. 6. Asset-Kandidaten: wiederkehrende Gesichter, Produkte und das Logo als `assets` mit `typ` (gesicht/produkt/logo/sonstiges), Name, sichtbaren `merkmale` (präzise, wörtlich wiederverwendbar formuliert — sie werden später exakt so in Prompts eingesetzt) und `quelle`. Bei Gesichtern zusätzlich `einwilligung_noetig: true`.
7. Keine echten Personennamen, keine Fremd-Markennamen in Namen/Tags/Bausteinen. Startwerte/Scores vergibst du NICHT — das macht der Code. 7. Keine echten Personennamen, keine Fremd-Markennamen, keine Altersbezeichnungen — weder in Namen/Tags/Bausteinen noch in `merkmale`. Startwerte/Scores vergibst du NICHT — das macht der Code.
8. Sprache: Deutsch, außer `prompt_bausteine`/`negativ_prompts` (Englisch). 8. Sprache: Deutsch, außer `prompt_bausteine`/`negativ_prompts` (Englisch).
## Output (NUR dieses JSON, keine Erklärungen) ## Output (NUR dieses JSON, keine Erklärungen)

View File

@@ -11,7 +11,7 @@ Du bist der Knowledge-Selector. Auf Basis des verstandenen Intents wählst du au
## Auswahl-Regeln (in dieser Rangfolge) ## Auswahl-Regeln (in dieser Rangfolge)
1. **Fixierte Nutzerwünsche schlagen alles.** Für jede in {{INTENT}} fixierte Kategorie: das Attribut wählen, das dem Nutzerwunsch entspricht (existiert keins → `neu_anlegen: true` mit Name/Slug-Vorschlag). Auch wenn sein Score niedrig ist. Grund: `"fixiert"`. 1. **Fixierte Nutzerwünsche schlagen alles.** Für jede in {{INTENT}} fixierte Kategorie: das Attribut wählen, das dem Nutzerwunsch entspricht (existiert keins → `neu_anlegen: true` mit Name/Slug-Vorschlag). Auch wenn sein Score niedrig ist. Grund: `"fixiert"`.
2. **Offene relevante Kategorien:** das Attribut mit dem höchsten Score wählen, Status aktiv. Grund: `"top"`. Existiert ein Unter-Attribut mit höherem Score als sein Eltern-Attribut, das Unter-Attribut nehmen. 2. **Offene relevante Kategorien:** das Attribut mit dem höchsten Score wählen, Status aktiv. Grund: `"top"`. Existiert ein Unter-Attribut mit höherem Score als sein Eltern-Attribut, das Unter-Attribut nehmen. Bei exaktem Gleichstand zwischen zwei Attributen bleibt das mit dem höheren `used_count` vorn (breiter getestet).
3. **Genau EIN Explorations-Slot pro Szene:** In einer offenen (nie einer fixierten) Kategorie statt des Top-Attributs bewusst ein ungetestetes oder wenig genutztes wählen (niedriger `used_count`, lange nicht verwendet, oder frische Onboarding-Hypothese). Grund: `"exploration"` + kurze Begründung. Ohne Exploration lernt das System nichts Neues. Wähle dafür die Kategorie, in der neues Wissen aktuell am wertvollsten ist (viele ungetestete Einträge, enge Score-Abstände). Gibt es nirgends Ungetestetes → Explorations-Slot weglassen. 3. **Genau EIN Explorations-Slot pro Szene:** In einer offenen (nie einer fixierten) Kategorie statt des Top-Attributs bewusst ein ungetestetes oder wenig genutztes wählen (niedriger `used_count`, lange nicht verwendet, oder frische Onboarding-Hypothese). Grund: `"exploration"` + kurze Begründung. Ohne Exploration lernt das System nichts Neues. Wähle dafür die Kategorie, in der neues Wissen aktuell am wertvollsten ist (viele ungetestete Einträge, enge Score-Abstände). Gibt es nirgends Ungetestetes → Explorations-Slot weglassen.
4. **Assets:** Aus {{ASSETS}} die freigegebenen Versionen wählen, die {{INTENT}}.`genannte_elemente` abdecken (genanntes Produkt → dessen Asset; Person gefordert → Brand-Gesicht). Nur freigegebene Versionen, exakt mit Versions-ID. 4. **Assets:** Aus {{ASSETS}} die freigegebenen Versionen wählen, die {{INTENT}}.`genannte_elemente` abdecken (genanntes Produkt → dessen Asset; Person gefordert → Brand-Gesicht). Nur freigegebene Versionen, exakt mit Versions-ID.
5. **Fokus:** Maximal 68 Attribute gesamt. Nicht jede Kategorie erzwingen — nur was die Szene laut Intent braucht. Für jedes gewählte Attribut die `prompt_bausteine`/`negativ_prompts` mitgeben (wörtlich aus {{ATTRIBUTE}}). 5. **Fokus:** Maximal 68 Attribute gesamt. Nicht jede Kategorie erzwingen — nur was die Szene laut Intent braucht. Für jedes gewählte Attribut die `prompt_bausteine`/`negativ_prompts` mitgeben (wörtlich aus {{ATTRIBUTE}}).

View File

@@ -6,7 +6,7 @@ modell_hinweis: claude
aktiv: true aktiv: true
--- ---
Du bist der Bild-Prompt-Regisseur einer Werbe-Produktion für Kosmetik-Brands. Du übersetzt einen Auftrag plus das injizierte Brand-Wissen in EINEN fertigen englischen Bild-Prompt für ein Foto-Generierungsmodell — für Referenzbilder von Gesichtern, Produkten und Szenen-Plates, die später in Videogenerierungen verwendet werden. Du bist der Bild-Prompt-Regisseur einer Werbe-Produktion. Du übersetzt einen Auftrag plus das injizierte Brand-Wissen in EINEN fertigen englischen Bild-Prompt für ein Foto-Generierungsmodell — für Referenzbilder von Gesichtern, Produkten und Szenen-Plates, die später in Videogenerierungen verwendet werden.
## Grundphilosophie ## Grundphilosophie
@@ -15,7 +15,7 @@ Jedes Bild muss wie eine echte Fotografie lesen — echte Kamera, echtes Licht,
## Auftragstypen (wähle anhand des Auftrags) ## Auftragstypen (wähle anhand des Auftrags)
1. **Gesicht neu (Face-Lock):** Identität pur — mid-gray seamless, Baseline-Top (plain black thin-strap camisole für Frauen, plain black ribbed tank für Männer), kein Schmuck, kein Styling, 3:4-Headshot, neutral entspannter Ausdruck. Nur Identitätsmerkmale beschreiben. 1. **Gesicht neu (Face-Lock):** Identität pur — mid-gray seamless, Baseline-Top (plain black thin-strap camisole für Frauen, plain black ribbed tank für Männer), kein Schmuck, kein Styling, 3:4-Headshot, neutral entspannter Ausdruck. Nur Identitätsmerkmale beschreiben.
2. **Produkt-Hero:** Produkt auf ruhiger Fläche, weiches gerichtetes Fensterlicht, scharfer Fokus aufs Produkt. Bei transparenten Flakons/Flüssigkeiten immer: "meniscus, refraction, liquid clarity". Immer: "no text, no logos" (Label-Text kommt aus dem freigegebenen Asset, nie generiert). 2. **Produkt-Hero:** Produkt auf ruhiger Fläche, weiches gerichtetes Fensterlicht, scharfer Fokus aufs Produkt. Bei transparenten Behältern/Flüssigkeiten immer: "meniscus, refraction, liquid clarity". Immer: "no text, no logos" (Label-Text kommt aus dem freigegebenen Asset, nie generiert).
3. **Asset-Verbesserung:** Es existiert eine freigegebene Version als Referenz. Baue den Prompt nach **Lock → Change → Scope**: erst was identisch bleibt, dann die EINE Änderung, dann der Geltungsbereich ("edit X only"). Liegt ein Anweisungsblock aus der Referenz-Analyse vor ({{ANWEISUNGEN}}), übernimm dessen Lock/Change/Scope wörtlich. 3. **Asset-Verbesserung:** Es existiert eine freigegebene Version als Referenz. Baue den Prompt nach **Lock → Change → Scope**: erst was identisch bleibt, dann die EINE Änderung, dann der Geltungsbereich ("edit X only"). Liegt ein Anweisungsblock aus der Referenz-Analyse vor ({{ANWEISUNGEN}}), übernimm dessen Lock/Change/Scope wörtlich.
4. **Szenen-Plate:** Charakter(e) in realer Umgebung oder reine Umgebung. Fünf Prosa-Absätze: Eröffnung (Framing, Kamera, Stimmung) → Charakter → Welt/Atmosphäre → Fokus-Anker → Kamera-Spezifikation mit Realismus-Schluss. Positionen als Prosa ("anchored on the left third"), nie Koordinaten. 4. **Szenen-Plate:** Charakter(e) in realer Umgebung oder reine Umgebung. Fünf Prosa-Absätze: Eröffnung (Framing, Kamera, Stimmung) → Charakter → Welt/Atmosphäre → Fokus-Anker → Kamera-Spezifikation mit Realismus-Schluss. Positionen als Prosa ("anchored on the left third"), nie Koordinaten.

View File

@@ -6,7 +6,7 @@ modell_hinweis: claude
aktiv: true aktiv: true
--- ---
Du bist der Regie-Assistent einer Werbe-Produktion für Kosmetik-Brands. Du übersetzt das vom Nutzer bestätigte Script in EINE modellneutrale, englische Shot-Struktur — ein Produktionsdokument, das anschließend von Modell-Adaptern in den Dialekt des jeweiligen Videomodells übersetzt wird. Du veränderst die Absicht des Scripts nie, du strukturierst und präzisierst sie. Du bist der Regie-Assistent einer Werbe-Produktion. Du übersetzt das vom Nutzer bestätigte Script in EINE modellneutrale, englische Shot-Struktur — ein Produktionsdokument, das anschließend von Modell-Adaptern in den Dialekt des jeweiligen Videomodells übersetzt wird. Du veränderst die Absicht des Scripts nie, du strukturierst und präzisierst sie.
## Grundphilosophie ## Grundphilosophie

View File

@@ -16,6 +16,7 @@ Du bist der Referenz-Interpreter. Beim Verbessern eines Assets hat der Nutzer ei
4. Enthält das Bild ein erkennbares echtes Gesicht und der Nutzer will Gesichtsmerkmale übernehmen → `einwilligung_hinweis: true` (die App klärt die dokumentierte Einwilligung; rein KI-generierte Gesichter sind der Standard). 4. Enthält das Bild ein erkennbares echtes Gesicht und der Nutzer will Gesichtsmerkmale übernehmen → `einwilligung_hinweis: true` (die App klärt die dokumentierte Einwilligung; rein KI-generierte Gesichter sind der Standard).
5. Erkennbare Logos, Markennamen oder geschützte Designs benennst du generisch und nimmst sie NICHT in die Merkmale auf, auch wenn der Nutzer sie nennt — stattdessen Hinweis in `hinweise`. 5. Erkennbare Logos, Markennamen oder geschützte Designs benennst du generisch und nimmst sie NICHT in die Merkmale auf, auch wenn der Nutzer sie nennt — stattdessen Hinweis in `hinweise`.
6. **Anweisungsblock** für den Bild-Prompt-Generator nach Lock → Change → Scope, englisch, direkt einsetzbar. Lock: was am bestehenden Asset (siehe {{ASSETS}}) unverändert bleibt. Change: die extrahierten Merkmale als Änderung. Scope: worauf sich die Änderung beschränkt. Muster: „Keep face, identity, skin tone, and body unchanged. Change only the hairstyle: soft shoulder-length layered cut with curtain bangs, warm chestnut brown. Edit hair only." 6. **Anweisungsblock** für den Bild-Prompt-Generator nach Lock → Change → Scope, englisch, direkt einsetzbar. Lock: was am bestehenden Asset (siehe {{ASSETS}}) unverändert bleibt. Change: die extrahierten Merkmale als Änderung. Scope: worauf sich die Änderung beschränkt. Muster: „Keep face, identity, skin tone, and body unchanged. Change only the hairstyle: soft shoulder-length layered cut with curtain bangs, warm chestnut brown. Edit hair only."
7. Keine echten Personennamen, keine Altersbezeichnungen in den Beschreibungen — Personen nur über visuelle Merkmale (analog P7/P8).
## Output (NUR dieses JSON) ## Output (NUR dieses JSON)

View File

@@ -43,6 +43,23 @@ aktiv: true
`version` vergibt das Seed-Skript: neue Zeile bei Inhaltsänderung (version+1), alte `version` vergibt das Seed-Skript: neue Zeile bei Inhaltsänderung (version+1), alte
Zeilen werden `aktiv: false`. Zeilen werden `aktiv: false`.
## Die 8 Grundkategorien
Jede Brand startet mit denselben 8 Kategorien (das ist die Grundlage von `{{KATEGORIEN}}` überall
und der Enum-Felder in P9). Neue Kategorien entstehen nur über P15 (`neue_kategorie`) — absolute
Ausnahme, siehe dort.
| Slug | Inhalt |
|---|---|
| `location` | Drehort/Umgebung, innen/außen, Tageszeit |
| `licht` | Lichtstimmung, Lichtrichtung |
| `farben` | Farbwelt, dominante Farben, Farbstimmung |
| `kamera` | Shot-Typen, Kamerabewegung |
| `voice` | Sprache/Stimme, Sprecher-Typ |
| `geraeusche` | Musik, Sound-Effekte |
| `texte_hooks` | Eingeblendeter Text, Hooks, CTAs |
| `handlungen` | Aktionen/Handlungen im Bild |
## Slot-Registry ## Slot-Registry
| Slot | Inhalt (füllt der Code) | genutzt von | | Slot | Inhalt (füllt der Code) | genutzt von |
@@ -74,6 +91,20 @@ Zeilen werden `aktiv: false`.
\* P2 erzeugt das Brand-Profil, nutzt aber `BRAND_PROFIL` nicht als Input. \* P2 erzeugt das Brand-Profil, nutzt aber `BRAND_PROFIL` nicht als Input.
## Script-Felder ↔ Kategorien (P5)
P5 gibt Shots in sechs Feldern aus, die sich wie folgt auf die 8 Grundkategorien beziehen — relevant
für Code, der Script-Inhalte den Kategorien zuordnen muss (z. B. P6-Diff-Zuordnung):
| Script-Feld | Kategorie(n) |
|---|---|
| Bild | `location`, `farben` |
| Aktion | `handlungen` |
| Kamera | `kamera` |
| Licht | `licht` |
| Audio | `voice`, `geraeusche` |
| Text im Bild | `texte_hooks` |
## Pipeline-Überblick ## Pipeline-Überblick
``` ```

View File

@@ -175,22 +175,18 @@
.tut .sub{font-size:14.5px;} .tut .sub{font-size:14.5px;}
/* ================= EXPLORE (Feed) ================= */ /* ================= EXPLORE (Feed) ================= */
#explore{padding:0; --xs:max(env(safe-area-inset-top, 0px), 64px);} #explore{padding:0;}
#explore.active{display:flex; flex-direction:column; height:100dvh; overflow:hidden;} #explore.active{display:flex; flex-direction:column; height:100dvh; overflow:hidden;}
.x-blur{position:absolute; inset:-50px; pointer-events:none; z-index:0; .x-blur{position:absolute; inset:-50px; pointer-events:none; z-index:0;
filter:blur(36px) brightness(.78) saturate(1.15); transform:scale(1.08); transition:background .3s;} filter:blur(36px) brightness(.78) saturate(1.15); transform:scale(1.08); transition:background .3s;}
.x-hood{position:absolute; inset:0; pointer-events:none; z-index:2;} .x-hood{position:absolute; inset:0; pointer-events:none; z-index:2;}
.x-hood svg{position:absolute; top:calc(var(--xs) - 14px); left:0; display:block; width:100%; height:120px;} .x-hood svg{position:absolute; top:calc(44px + env(safe-area-inset-top)); left:0; display:block; width:100%; height:60px;}
.x-hood .solid{position:absolute; top:calc(var(--xs) + 105px); left:0; right:0; bottom:0; background:var(--bg);} .x-hood .solid{position:absolute; top:calc(103px + env(safe-area-inset-top)); left:0; right:0; bottom:0; background:var(--bg);}
.x-hood .glowred, .glowred-screen{background:radial-gradient(125% 125% at 50% 150%, rgba(241,99,99,.21) 40%, transparent 100%);} .x-tabs{position:absolute; top:calc(60px + env(safe-area-inset-top)); left:0; right:0; height:24px; z-index:6;}
.x-hood .glowred{position:absolute; inset:0;} .x-tab{position:absolute; top:0; transform:translateX(-50%); white-space:nowrap; padding:4px 6px;
.x-tabs{position:absolute; top:calc(var(--xs) + 12px); left:0; right:0; height:24px; z-index:6;
display:flex; justify-content:center; align-items:center; gap:96px;}
.x-tab{position:relative; white-space:nowrap; padding:4px 6px;
font-size:14.5px; font-weight:600; cursor:pointer; color:rgba(255,255,255,.42); transition:color .25s;} font-size:14.5px; font-weight:600; cursor:pointer; color:rgba(255,255,255,.42); transition:color .25s;}
.x-tab::before{content:''; position:absolute; inset:-18px -30px;}
.x-tab.on{color:#fff; text-shadow:0 1px 10px rgba(0,0,0,.45);} .x-tab.on{color:#fff; text-shadow:0 1px 10px rgba(0,0,0,.45);}
.x-deck{flex:1; position:relative; margin:calc(var(--xs) + 144px) 22px 10px; min-height:0; z-index:3;} .x-deck{flex:1; position:relative; margin:calc(114px + env(safe-area-inset-top)) 22px 10px; min-height:0; z-index:3;}
.x-card{position:absolute; inset:0; border-radius:26px; overflow:hidden; background:#141414; .x-card{position:absolute; inset:0; border-radius:26px; overflow:hidden; background:#141414;
touch-action:none; box-shadow:0 18px 44px rgba(0,0,0,.45);} touch-action:none; box-shadow:0 18px 44px rgba(0,0,0,.45);}
.x-card .img{position:absolute; inset:0; pointer-events:none;} .x-card .img{position:absolute; inset:0; pointer-events:none;}
@@ -224,8 +220,6 @@
.x-act.like{background:var(--accent); border:none; box-shadow:0 0 22px rgba(255,92,57,.27);} .x-act.like{background:var(--accent); border:none; box-shadow:0 0 22px rgba(255,92,57,.27);}
/* ================= DETAIL & CREATE ================= */ /* ================= DETAIL & CREATE ================= */
#detail.active, #create.active{min-height:100dvh;
background:radial-gradient(125% 125% at 50% 150%, rgba(241,99,99,.21) 40%, transparent 100%);}
.d-strip{display:flex; gap:10px; overflow-x:auto; margin:0 -18px; padding:0 18px; scrollbar-width:none; scroll-snap-type:x mandatory;} .d-strip{display:flex; gap:10px; overflow-x:auto; margin:0 -18px; padding:0 18px; scrollbar-width:none; scroll-snap-type:x mandatory;}
.d-strip::-webkit-scrollbar{display:none;} .d-strip::-webkit-scrollbar{display:none;}
.d-img{flex:none; width:272px; height:330px; border-radius:20px; position:relative; display:grid; place-items:center; scroll-snap-align:center;} .d-img{flex:none; width:272px; height:330px; border-radius:20px; position:relative; display:grid; place-items:center; scroll-snap-align:center;}
@@ -251,21 +245,10 @@
.p-hero p{color:var(--mut); font-size:13px; margin-top:3px;} .p-hero p{color:var(--mut); font-size:13px; margin-top:3px;}
/* ================= Bottom-Navigation ================= */ /* ================= Bottom-Navigation ================= */
/* Liquid-Glass-Nav (aus BrandLoop.dc.html): Turbulenz-Filter als backdrop,
blur(20px) als Fallback falls der Browser url()-backdrop-filter nicht kann */
.nav{position:fixed; bottom:14px; left:50%; transform:translateX(-50%); z-index:30; .nav{position:fixed; bottom:14px; left:50%; transform:translateX(-50%); z-index:30;
display:flex; align-items:center; gap:6px; padding:8px 10px; border-radius:100px; display:flex; align-items:center; gap:6px; padding:8px 10px; border-radius:100px;
margin-bottom:env(safe-area-inset-bottom);} background:rgba(18,18,18,.82); backdrop-filter:blur(20px); -webkit-backdrop-filter:blur(20px);
.nav::before{content:''; position:absolute; inset:0; z-index:-1; border-radius:100px; border:1px solid rgba(255,255,255,.08); margin-bottom:env(safe-area-inset-bottom);}
background:rgba(10,10,10,.18);
-webkit-backdrop-filter:blur(20px); backdrop-filter:blur(20px);
-webkit-backdrop-filter:url(#bl-liquid-glass); backdrop-filter:url(#bl-liquid-glass);}
.nav::after{content:''; position:absolute; inset:0; z-index:2; border-radius:100px; pointer-events:none;
box-shadow:0 0 8px rgba(0,0,0,.03), 0 2px 6px rgba(0,0,0,.08),
inset 3px 3px .5px -3.5px rgba(255,255,255,.09), inset -3px -3px .5px -3.5px rgba(255,255,255,.85),
inset 1px 1px 1px -.5px rgba(255,255,255,.6), inset -1px -1px 1px -.5px rgba(255,255,255,.6),
inset 0 0 6px 6px rgba(255,255,255,.12), inset 0 0 2px 2px rgba(255,255,255,.06),
0 0 12px rgba(0,0,0,.15);}
.nav button{display:flex; align-items:center; gap:8px; background:none; border:none; font-family:inherit; .nav button{display:flex; align-items:center; gap:8px; background:none; border:none; font-family:inherit;
width:40px; height:40px; justify-content:center; border-radius:100px; color:rgba(255,255,255,.6); width:40px; height:40px; justify-content:center; border-radius:100px; color:rgba(255,255,255,.6);
font-size:13.5px; font-weight:600; cursor:pointer; transition:background .2s, color .2s, width .2s;} font-size:13.5px; font-weight:600; cursor:pointer; transition:background .2s, color .2s, width .2s;}
@@ -296,16 +279,9 @@
</head> </head>
<body> <body>
<!-- Lucide-Icon-Sprite + Liquid-Glass-Filter --> <!-- Lucide-Icon-Sprite -->
<svg width="0" height="0" style="position:absolute" aria-hidden="true"> <svg width="0" height="0" style="position:absolute" aria-hidden="true">
<defs> <defs>
<filter id="bl-liquid-glass" x="0%" y="0%" width="100%" height="100%" color-interpolation-filters="sRGB">
<feTurbulence type="fractalNoise" baseFrequency="0.05 0.05" numOctaves="1" seed="1" result="turbulence"/>
<feGaussianBlur in="turbulence" stdDeviation="2" result="blurredNoise"/>
<feDisplacementMap in="SourceGraphic" in2="blurredNoise" scale="70" xChannelSelector="R" yChannelSelector="B" result="displaced"/>
<feGaussianBlur in="displaced" stdDeviation="4" result="finalBlur"/>
<feComposite in="finalBlur" in2="finalBlur" operator="over"/>
</filter>
<symbol id="i-back" viewBox="0 0 24 24"><path d="m15 18-6-6 6-6"/></symbol> <symbol id="i-back" viewBox="0 0 24 24"><path d="m15 18-6-6 6-6"/></symbol>
<symbol id="i-chev" viewBox="0 0 24 24"><path d="m9 18 6-6-6-6"/></symbol> <symbol id="i-chev" viewBox="0 0 24 24"><path d="m9 18 6-6-6-6"/></symbol>
<symbol id="i-home" viewBox="0 0 24 24"><path d="M4 11 L12 4 L20 11 V20 H14 V15 H10 V20 H4 Z"/></symbol> <symbol id="i-home" viewBox="0 0 24 24"><path d="M4 11 L12 4 L20 11 V20 H14 V15 H10 V20 H4 Z"/></symbol>
@@ -335,15 +311,14 @@
<div class="screen active" id="explore"> <div class="screen active" id="explore">
<div class="x-blur" id="xBlur"></div> <div class="x-blur" id="xBlur"></div>
<div class="x-hood"> <div class="x-hood">
<svg viewBox="0 0 430 120" preserveAspectRatio="none"><path id="xHoodPath" d="" fill="#0a0a0a"></path></svg> <svg viewBox="0 0 430 60" preserveAspectRatio="none"><path id="xHoodPath" d="" fill="#0a0a0a"></path></svg>
<div class="solid"></div> <div class="solid"></div>
<div class="glowred"></div>
</div> </div>
<button class="iconbtn" style="position:absolute; top:calc(var(--xs) - 54px); right:14px; z-index:8;" onclick="toggleInfo(true)" aria-label="Info zum Prototyp"><svg class="ic"><use href="#i-info"/></svg></button> <button class="iconbtn" style="position:absolute; top:calc(12px + env(safe-area-inset-top)); right:14px; z-index:8;" onclick="toggleInfo(true)" aria-label="Info zum Prototyp"><svg class="ic"><use href="#i-info"/></svg></button>
<div class="x-tabs"> <div class="x-tabs">
<div class="x-tab" data-tab="videos">Videos</div> <div class="x-tab" data-tab="videos" style="left:14.4%;">Videos</div>
<div class="x-tab" data-tab="folge">Folge ich</div> <div class="x-tab" data-tab="folge" style="left:50%;">Folge ich</div>
<div class="x-tab on" data-tab="posts">Posts</div> <div class="x-tab on" data-tab="posts" style="left:85.6%;">Posts</div>
</div> </div>
<div class="x-deck" id="xDeck"></div> <div class="x-deck" id="xDeck"></div>
<div class="x-actions"> <div class="x-actions">
@@ -358,16 +333,20 @@
<div class="topbar"> <div class="topbar">
<button class="backpill" onclick="go('explore')"><span class="ar"></span> Zurück</button> <button class="backpill" onclick="go('explore')"><span class="ar"></span> Zurück</button>
<div style="margin-left:auto; display:flex; align-items:center; gap:10px;"> <div style="margin-left:auto; display:flex; align-items:center; gap:10px;">
<div id="dAvatar" style="width:34px; height:34px; border-radius:100px; background:linear-gradient(140deg,oklch(.72 .13 40),oklch(.55 .13 40)); display:grid; place-items:center; font-size:13px; font-weight:700;">M</div> <div style="width:34px; height:34px; border-radius:100px; background:linear-gradient(140deg,oklch(.72 .13 40),oklch(.55 .13 40)); display:grid; place-items:center; font-size:13px; font-weight:700;">M</div>
<div><div style="font-size:13.5px; font-weight:700;">Mara S.</div><div style="font-size:11px; color:rgba(255,255,255,.5);">1.2k Remakes</div></div> <div><div style="font-size:13.5px; font-weight:700;">Mara S.</div><div style="font-size:11px; color:rgba(255,255,255,.5);">1.2k Remakes</div></div>
</div> </div>
</div> </div>
<div class="d-strip" id="dStrip"></div> <div class="d-strip">
<div class="d-img" style="background:linear-gradient(rgba(0,0,0,.12),rgba(0,0,0,.12)),repeating-linear-gradient(135deg,transparent 0 14px,rgba(255,255,255,.05) 14px 28px),linear-gradient(160deg,oklch(.62 .09 150),oklch(.34 .09 190));"><span class="lbl">[ki-bild 1/3: serum auf stein]</span><span class="num">1/3</span></div>
<div class="d-img" style="background:linear-gradient(rgba(0,0,0,.12),rgba(0,0,0,.12)),repeating-linear-gradient(135deg,transparent 0 14px,rgba(255,255,255,.05) 14px 28px),linear-gradient(160deg,oklch(.62 .09 175),oklch(.34 .09 215));"><span class="lbl">[ki-bild 2/3: anwendung]</span><span class="num">2/3</span></div>
<div class="d-img" style="background:linear-gradient(rgba(0,0,0,.12),rgba(0,0,0,.12)),repeating-linear-gradient(135deg,transparent 0 14px,rgba(255,255,255,.05) 14px 28px),linear-gradient(160deg,oklch(.62 .09 200),oklch(.34 .09 240));"><span class="lbl">[ki-bild 3/3: close-up haut]</span><span class="num">3/3</span></div>
</div>
<div class="d-title">Skincare Routine 3er Kette</div> <div class="d-title">Skincare Routine 3er Kette</div>
<div class="d-sub">Bild-Kette · Produkt-Modell · Format 4:5</div> <div class="d-sub">Bild-Kette · Produkt-Modell · Format 4:5</div>
<div class="promptbox"> <div class="promptbox">
<div class="k">PROMPT</div> <div class="k">PROMPT</div>
<div class="v" id="detailPrompt">Roter Toner im rosa Bogenfenster, weiches Gegenlicht, Editorial-Look — Kette: Fenster-Szene → Dropper auf Glaskante → Poster-Ad in Orange</div> <div class="v" id="detailPrompt">Serum-Flasche auf nassem Stein, weiches Morgenlicht, Wassertropfen, Editorial-Look, cleaner Hintergrund in Salbeigrün — Kette: Produkt solo → Anwendung → Ergebnis-Close-up</div>
</div> </div>
<div style="margin-bottom:22px;"> <div style="margin-bottom:22px;">
<span class="tag">Modell · Glow Serum v3</span><span class="tag">4:5</span><span class="tag">Editorial</span><span class="tag">3 Bilder</span> <span class="tag">Modell · Glow Serum v3</span><span class="tag">4:5</span><span class="tag">Editorial</span><span class="tag">3 Bilder</span>
@@ -885,38 +864,16 @@ function toggleInfo(open){
var ACCENT='#ff5c39'; var ACCENT='#ff5c39';
var HUES={Produkt:40, Mensch:260, Szene:170}; var HUES={Produkt:40, Mensch:260, Szene:170};
var POSTS=[ var POSTS=[
{id:1, creator:'Lena M.', cat:'Produkt', model:'Sneaker Studio v2', video:true, h:250, imgs:['[ki-video: sneaker spot]'], {id:1, creator:'Lena M.', cat:'Produkt', model:'Sneaker Studio v2', video:true, h:250, imgs:['[ki-video: sneaker spot]']},
pics:['uploads/pasted-1784663207742-0.png']}, {id:2, creator:'Mara S.', cat:'Produkt', model:'Glow Serum v3', video:false, h:150, imgs:['[ki-bild 1/3: serum auf stein]','[ki-bild 2/3: anwendung]','[ki-bild 3/3: close-up haut]']},
{id:2, creator:'Mara S.', cat:'Produkt', model:'Glow Serum v3', video:false, h:150, imgs:['Toner im Bogenfenster','Age Decoder Essence','Poster-Ad'], {id:3, creator:'Jonas K.', cat:'Mensch', model:'Coach Avatar', video:true, h:40, imgs:['[ki-video: gym coaching ad]']},
pics:['uploads/pasted-1784643149542-0.png','uploads/pasted-1784643165286-0.png','uploads/pasted-1784643111353-0.png'], {id:4, creator:'Tom B.', cat:'Mensch', model:'Barber Ich v1', video:false, h:90, imgs:['[ki-bild 1/4: portrait]','[ki-bild 2/4: fade cut]','[ki-bild 3/4: shop szene]','[ki-bild 4/4: logo karte]']},
avatar:'uploads/pasted-1784663200459-0.png'}, {id:5, creator:'Feld & Co',cat:'Szene', model:'Café Interieur', video:false, h:190, imgs:['[ki-bild 1/3: tresen]','[ki-bild 2/3: latte art]','[ki-bild 3/3: fassade]']},
{id:3, creator:'Jonas K.', cat:'Mensch', model:'Coach Avatar', video:true, h:40, imgs:['[ki-video: gym coaching ad]'], {id:6, creator:'Nina R.', cat:'Produkt', model:'Schmuck Makro', video:false, h:310, imgs:['[ki-bild: ring makro]']},
pics:['uploads/pasted-1784663200459-0.png']}, {id:7, creator:'Kai W.', cat:'Szene', model:'Immo Walkthrough', video:true, h:210, imgs:['[ki-video: wohnungs-tour]']}
{id:4, creator:'Tom B.', cat:'Mensch', model:'Barber Ich v1', video:false, h:90, imgs:['[ki-bild 1/4: portrait]','[ki-bild 2/4: fade cut]','[ki-bild 3/4: shop szene]','[ki-bild 4/4: logo karte]'],
pics:['uploads/pasted-1784663261701-0.png','uploads/pasted-1784663207742-0.png','uploads/pasted-1784663200459-0.png','uploads/pasted-1784663233931-0.png']},
{id:5, creator:'Feld & Co',cat:'Szene', model:'Café Interieur', video:false, h:190, imgs:['[ki-bild 1/3: tresen]','[ki-bild 2/3: latte art]','[ki-bild 3/3: fassade]'],
pics:['uploads/pasted-1784663233931-0.png','uploads/pasted-1784663383522-0.png','uploads/pasted-1784663261701-0.png']},
{id:6, creator:'Nina R.', cat:'Produkt', model:'Schmuck Makro', video:false, h:310, imgs:['[ki-bild: ring makro]'],
pics:['uploads/pasted-1784663383522-0.png']},
{id:7, creator:'Kai W.', cat:'Szene', model:'Immo Walkthrough', video:true, h:210, imgs:['[ki-video: wohnungs-tour]'],
pics:['uploads/pasted-1784663233931-0.png']}
]; ];
var X={tab:'posts', idx:0, dx:0, imgIdx:0, liked:[], anim:false, hoodCx:null, hoodHalf:null, hoodRaf:null, centers:null, halves:null}; var X={tab:'posts', idx:0, dx:0, imgIdx:0, liked:[], anim:false, hoodCx:null, hoodRaf:null};
var TAB_CENTERS={videos:40, folge:205, posts:366}; var TAB_CENTERS={videos:62, folge:215, posts:368};
var TAB_HALVES={videos:40, folge:46, posts:35};
/* Bilder vorladen: nur geladene Dateien werden benutzt, sonst Dummy-Verlauf */
var PICS_OK={};
function pic(p,i){ var u=p.pics&&p.pics[i]; return (u&&PICS_OK[u])?u:null; }
function preloadPics(){
var all={};
POSTS.forEach(function(p){ (p.pics||[]).forEach(function(u){ all[u]=1; }); if(p.avatar) all[p.avatar]=1; });
Object.keys(all).forEach(function(u){
var im=new Image();
im.onload=function(){ PICS_OK[u]=true; renderDeck(); renderDetailStrip(); };
im.src=u;
});
}
function dummy(h){ function dummy(h){
return 'linear-gradient(rgba(0,0,0,.12),rgba(0,0,0,.12)),'+ return 'linear-gradient(rgba(0,0,0,.12),rgba(0,0,0,.12)),'+
@@ -929,60 +886,32 @@ function deck(){
return POSTS.filter(function(p){return !p.video;}); return POSTS.filter(function(p){return !p.video;});
} }
/* Hood-Kurve unter dem aktiven Tab (Geometrie 1:1 aus BrandLoop.dc.html): /* Hood-Kurve unter dem aktiven Tab (Geometrie aus dem Design übernommen) */
pro Tab eigene Breite (nh = halbe Plateau-Breite), am Bildschirmrand function hoodPath(cx){
verschmilzt die Hood mit der Kante statt eine Schulter zu zeichnen */ var W=430, top=6, floor=54, hw=66, sw=36, r=16;
function hoodPath(cx, nh){ var xl=cx-hw, xr=cx+hw;
var W=430, top=8, floor=76, r=40, sw=80, bt=.6, tt=.6, H=120; function blend(edge){ return edge<=0 ? floor : (edge<r+10 ? top+(floor-top)*(1-edge/(r+10)) : top); }
var xl=cx-nh, xr=cx+nh, lStart=xl-sw, rEnd=xr+sw; var lY=blend(xl-sw), rY=blend(W-xr-sw);
var rl=Math.max(8,Math.min(r,lStart-2)), rr=Math.max(8,Math.min(r,W-rEnd-2)); var d='M0 60 L0 '+Math.min(lY+r,58)+' Q0 '+lY+' '+r+' '+lY+' ';
var d; if(lY<floor-1) d+='L'+(xl-sw)+' '+lY+' C'+(xl-sw*.45)+' '+lY+' '+(xl-sw*.55)+' '+floor+' '+xl+' '+floor+' ';
if(lStart<=24){ else d+='L'+Math.max(xl,r)+' '+floor+' ';
d='M0 '+H+' L0 '+Math.min(floor+r,H-4)+' Q0 '+floor+' '+Math.min(Math.max(xl,r),xr-10)+' '+floor+' '; d+='L'+Math.min(xr,W-r)+' '+floor+' ';
}else{ if(rY<floor-1) d+='C'+(xr+sw*.55)+' '+floor+' '+(xr+sw*.45)+' '+rY+' '+(xr+sw)+' '+rY+' L'+(W-r)+' '+rY+' ';
d='M0 '+H+' L0 '+(top+rl)+' Q0 '+top+' '+rl+' '+top+' '; d+='Q'+W+' '+rY+' '+W+' '+Math.min(rY+r,58)+' L'+W+' 60 Z';
d+='L'+lStart+' '+top+' C'+(lStart+sw*tt)+' '+top+' '+(xl-sw*bt)+' '+floor+' '+xl+' '+floor+' ';
}
d+='L'+xr+' '+floor+' ';
if(rEnd>=W-24){
d+='L'+Math.max(Math.min(xr,W-r),xl+10)+' '+floor+' Q'+W+' '+floor+' '+W+' '+Math.min(floor+r,H-4)+' L'+W+' '+H+' Z';
}else{
d+='C'+(xr+sw*bt)+' '+floor+' '+(rEnd-sw*tt)+' '+top+' '+rEnd+' '+top+' ';
d+='L'+(W-rr)+' '+top+' Q'+W+' '+top+' '+W+' '+(top+rr)+' L'+W+' '+H+' Z';
}
return d; return d;
} }
function centerOf(k){ return (X.centers&&X.centers[k]!=null)?X.centers[k]:TAB_CENTERS[k]; } function setHood(cx){
function halfOf(k){ return (X.halves&&X.halves[k]!=null)?X.halves[k]:TAB_HALVES[k]; } X.hoodCx=cx;
/* Tab-Positionen aus dem DOM messen (in 430er-viewBox-Koordinaten) */ document.getElementById('xHoodPath').setAttribute('d',hoodPath(cx));
function measureTabs(){
var cont=document.querySelector('.x-tabs');
if(!cont||!cont.offsetWidth) return;
var scale=430/cont.offsetWidth;
var els={};
['videos','folge','posts'].forEach(function(k){ els[k]=cont.querySelector('.x-tab[data-tab="'+k+'"]'); });
if(!els.videos||!els.folge||!els.posts) return;
function m(el){ return {L:el.offsetLeft*scale, R:(el.offsetLeft+el.offsetWidth)*scale}; }
var v=m(els.videos), f=m(els.folge), p=m(els.posts);
var sw=80, m1=(v.R+f.L)/2, m2=(f.R+p.L)/2;
var x={videos:[v.L-12, m1-sw/2], folge:[m1+sw/2, m2-sw/2], posts:[m2+sw/2, p.R+12]};
var c={}, h={};
for(var k in x){ c[k]=(x[k][0]+x[k][1])/2; h[k]=(x[k][1]-x[k][0])/2; }
X.centers=c; X.halves=h;
} }
function setHood(cx,nh){ function animateHood(target){
X.hoodCx=cx; X.hoodHalf=nh;
document.getElementById('xHoodPath').setAttribute('d',hoodPath(cx,nh));
}
function animateHood(target,targetHalf){
if(X.hoodRaf) cancelAnimationFrame(X.hoodRaf); if(X.hoodRaf) cancelAnimationFrame(X.hoodRaf);
var from=(X.hoodCx==null)?centerOf(X.tab):X.hoodCx; var from=(X.hoodCx==null)?TAB_CENTERS[X.tab]:X.hoodCx;
var fromH=(X.hoodHalf==null)?halfOf(X.tab):X.hoodHalf;
var t0=performance.now(), dur=460, c1=1.3; var t0=performance.now(), dur=460, c1=1.3;
function step(now){ function step(now){
var t=Math.min((now-t0)/dur,1); var t=Math.min((now-t0)/dur,1);
var e=1+(c1+1)*Math.pow(t-1,3)+c1*Math.pow(t-1,2); var e=1+(c1+1)*Math.pow(t-1,3)+c1*Math.pow(t-1,2);
setHood(from+(target-from)*e, fromH+(targetHalf-fromH)*e); setHood(from+(target-from)*e);
if(t<1) X.hoodRaf=requestAnimationFrame(step); if(t<1) X.hoodRaf=requestAnimationFrame(step);
} }
X.hoodRaf=requestAnimationFrame(step); X.hoodRaf=requestAnimationFrame(step);
@@ -996,9 +925,7 @@ function renderTabs(){
function renderBlur(){ function renderBlur(){
var d=deck(), top=d[X.idx]; var d=deck(), top=d[X.idx];
var el=document.getElementById('xBlur'); var el=document.getElementById('xBlur');
if(!top){ el.style.background='var(--bg)'; return; } el.style.background = top ? dummy(top.h + Math.min(X.imgIdx, top.imgs.length-1)*25) : 'var(--bg)';
var imgI=Math.min(X.imgIdx, top.imgs.length-1), u=pic(top,imgI);
el.style.background = u ? '#0a0a0a url("'+u+'") center / cover no-repeat' : dummy(top.h+imgI*25);
} }
function esc(s){ return s.replace(/&/g,'&amp;').replace(/</g,'&lt;'); } function esc(s){ return s.replace(/&/g,'&amp;').replace(/</g,'&lt;'); }
@@ -1021,16 +948,14 @@ function renderDeck(){
for(var i=slice.length-1; i>=0; i--){ for(var i=slice.length-1; i>=0; i--){
var p=slice[i], top=(i===0); var p=slice[i], top=(i===0);
var imgI=top?Math.min(X.imgIdx,p.imgs.length-1):0; var imgI=top?Math.min(X.imgIdx,p.imgs.length-1):0;
var u=pic(p,imgI);
var hue=p.h+imgI*25; var hue=p.h+imgI*25;
var tf=top var tf=top
? 'translateX('+X.dx+'px) rotate('+(X.dx/22)+'deg)' ? 'translateX('+X.dx+'px) rotate('+(X.dx/22)+'deg)'
: 'scale('+(1-i*.06)+') translateY('+(-i*34)+'px)'; : 'scale('+(1-i*.06)+') translateY('+(-i*34)+'px)';
html+='<div class="x-card'+(top?' top':'')+'" data-i="'+i+'" style="z-index:'+(4-i)+'; transform:'+tf+'; transition:transform .28s ease; cursor:'+(top?'grab':'default')+';">'; html+='<div class="x-card'+(top?' top':'')+'" data-i="'+i+'" style="z-index:'+(4-i)+'; transform:'+tf+'; transition:transform .28s ease; cursor:'+(top?'grab':'default')+';">';
var bg=u ? '#141414 url(&quot;'+u+'&quot;) center / cover no-repeat' : dummy(hue); html+='<div class="img" style="background:'+dummy(top?hue:(d[X.idx].h+X.imgIdx*25))+';'+(top?'':'inset:-24px; filter:blur(16px) brightness(.8);')+'"></div>';
html+='<div class="img" style="background:'+bg+';'+(top?'':' filter:brightness(.85);')+'"></div>';
if(top){ if(top){
if(!u) html+='<div class="imglabel"><span>'+esc(p.imgs[imgI])+'</span></div>'; html+='<div class="imglabel"><span>'+esc(p.imgs[imgI])+'</span></div>';
if(p.video){ if(p.video){
html+='<div class="playwrap"><div class="play"><svg width="22" height="22" viewBox="0 0 24 24" fill="#fff"><path d="M8 5 L19 12 L8 19 Z"/></svg></div></div>'; html+='<div class="playwrap"><div class="play"><svg width="22" height="22" viewBox="0 0 24 24" fill="#fff"><path d="M8 5 L19 12 L8 19 Z"/></svg></div></div>';
} }
@@ -1040,11 +965,8 @@ function renderDeck(){
} }
html+='</div>'; html+='</div>';
html+='<div class="x-chips"><div>'+esc(p.cat)+'</div>'+(p.imgs.length>1?'<div>⧉ '+p.imgs.length+' Bilder</div>':'')+'</div>'; html+='<div class="x-chips"><div>'+esc(p.cat)+'</div>'+(p.imgs.length>1?'<div>⧉ '+p.imgs.length+' Bilder</div>':'')+'</div>';
var avOk=p.avatar&&PICS_OK[p.avatar];
var avBg=avOk ? '#141414 url(&quot;'+p.avatar+'&quot;) 50% 16% / 300% no-repeat'
: 'linear-gradient(140deg,oklch(.72 .13 '+HUES[p.cat]+'),oklch(.5 .13 '+HUES[p.cat]+'))';
html+='<div class="x-meta">'+ html+='<div class="x-meta">'+
'<div class="av" style="background:'+avBg+';">'+(avOk?'':esc(p.creator[0]))+'</div>'+ '<div class="av" style="background:linear-gradient(140deg,oklch(.72 .13 '+HUES[p.cat]+'),oklch(.5 .13 '+HUES[p.cat]+'));">'+esc(p.creator[0])+'</div>'+
'<div style="min-width:0;"><div class="nm">'+esc(p.creator)+'</div><div class="md">Modell · '+esc(p.model)+'</div></div>'+ '<div style="min-width:0;"><div class="nm">'+esc(p.creator)+'</div><div class="md">Modell · '+esc(p.model)+'</div></div>'+
'</div>'; '</div>';
html+='<div class="x-badge like" id="xLikeBadge">LIKE</div><div class="x-badge skip" id="xSkipBadge">SKIP</div>'; html+='<div class="x-badge like" id="xLikeBadge">LIKE</div><div class="x-badge skip" id="xSkipBadge">SKIP</div>';
@@ -1109,35 +1031,13 @@ function onDown(e){
document.querySelectorAll('.x-tab').forEach(function(el){ document.querySelectorAll('.x-tab').forEach(function(el){
el.addEventListener('click',function(){ el.addEventListener('click',function(){
X.tab=el.dataset.tab; X.idx=0; X.dx=0; X.imgIdx=0; X.tab=el.dataset.tab; X.idx=0; X.dx=0; X.imgIdx=0;
renderTabs(); renderDeck(); animateHood(centerOf(X.tab), halfOf(X.tab)); renderTabs(); renderDeck(); animateHood(TAB_CENTERS[X.tab]);
}); });
}); });
window.addEventListener('resize',function(){
measureTabs(); setHood(centerOf(X.tab), halfOf(X.tab));
});
document.getElementById('btnSkip').onclick=function(){ fling(-1); }; document.getElementById('btnSkip').onclick=function(){ fling(-1); };
document.getElementById('btnLike').onclick=function(){ fling(1); }; document.getElementById('btnLike').onclick=function(){ fling(1); };
document.getElementById('btnDetail').onclick=function(){ go('detail'); }; document.getElementById('btnDetail').onclick=function(){ go('detail'); };
/* ---------- Detail: Bild-Strip (Maras Kette, echte Bilder mit Fallback) ---------- */
function renderDetailStrip(){
var host=document.getElementById('dStrip');
if(!host) return;
var p=POSTS[1];
var av=document.getElementById('dAvatar');
if(av && p.avatar && PICS_OK[p.avatar]){
av.style.background='#141414 url("'+p.avatar+'") 50% 16% / 300% no-repeat';
av.textContent='';
}
host.innerHTML=p.imgs.map(function(label,i){
var u=pic(p,i);
var bg=u ? '#141414 url(&quot;'+u+'&quot;) center / cover no-repeat' : dummy(150+i*25);
return '<div class="d-img" style="background:'+bg+';">'+
(u?'':'<span class="lbl">'+esc(label)+'</span>')+
'<span class="num">'+(i+1)+'/'+p.imgs.length+'</span></div>';
}).join('');
}
/* ---------- Detail: Modell wählen + Nachmachen ---------- */ /* ---------- Detail: Modell wählen + Nachmachen ---------- */
var MODELS=['Mensch Ich (Avatar)','Produkt Kerze Studio','Szene Showroom']; var MODELS=['Mensch Ich (Avatar)','Produkt Kerze Studio','Szene Showroom'];
var D={model:1, remade:false}; var D={model:1, remade:false};
@@ -1198,12 +1098,9 @@ document.getElementById('generateBtn').onclick=function(){
}; };
/* ---------- Init ---------- */ /* ---------- Init ---------- */
setHood(centerOf(X.tab), halfOf(X.tab)); setHood(TAB_CENTERS[X.tab]);
requestAnimationFrame(function(){ measureTabs(); setHood(centerOf(X.tab), halfOf(X.tab)); });
renderTabs(); renderTabs();
renderDeck(); renderDeck();
renderDetailStrip();
preloadPics();
renderDetailModels(); renderDetailModels();
renderCreateModels(); renderCreateModels();
renderFrames(); renderFrames();