diff --git a/.gitignore b/.gitignore index c2ddf69..3318b0c 100644 --- a/.gitignore +++ b/.gitignore @@ -13,3 +13,8 @@ promo/clips/ # ios/.gitignore already keeps the copied web payload, Pods and Xcode's per-user junk out of it. node_modules/ www/ + +# design hand-offs: read, built, and recorded in PROJECT.md — the prototype, its copy of the +# assets and the zip do not belong in the image or the history (they are ~1 MB of duplicate art) +design_handoff_*/ +*.zip diff --git a/Menüführung_ Text reduzieren.zip b/Menüführung_ Text reduzieren.zip deleted file mode 100644 index 9ca055e..0000000 Binary files a/Menüführung_ Text reduzieren.zip and /dev/null differ diff --git a/design_handoff_menu_text_reduction/Menues.dc.html b/design_handoff_menu_text_reduction/Menues.dc.html deleted file mode 100644 index afbd654..0000000 --- a/design_handoff_menu_text_reduction/Menues.dc.html +++ /dev/null @@ -1,1420 +0,0 @@ - - - - - - - - - - - - - - - - -
-
-
5 · GESETZT — ALLE SCREENS IN JERSEY 15
-
Regel für alle: Jersey 15 benennt (Screen-Titel, Räume, Karten), Bangers ruft genau einmal pro Screen (das Fixture, der Primär-Button, die Modal-Headline), Silkscreen bleibt für Zahlen und Labels. Sätze nur, wo sie eine Entscheidung tragen — Regeln wandern ins Modal, Werte in Balken und Pips. Die Wand selbst ist 2c.
-
- -
- -
-
SPLASH — OHNE ZIEL-SATZ, OHNE STRAPS
-
-
- Splatter Tennis -

Fifteen. Thirty. Forty. Dead.

-
- -
-
- OLIVER - LEVEL 12 -
- - - 13 - -
- > -
-
-
-
- LEAGUE 3 · THE CUP - ROUND ONE: THE STRAWBERRY SELLER -
-
OTHER GAMES
-
-
STATS4 812
-
BOARDBEST 612
-
-

BUILD 341

-
-
-
- -
-
MATCH SETUP — PLÄTZE OHNE BESCHREIBUNG, BALKEN SAGEN ES
-
-
-
<
-
MATCH SETUP
-
-
-
PLAYER
-
-
LIVE
PLAYER
SPRITE
-
-
-
MEN'S
-
WOMEN'S
-
- KITCLARET -
-
-
-
-
-
-
-
- SKINOLIVE -
-
-
-
-
-
-
-
-
-
- COURT - BOUNCE · PACE -
-
- -
- TERRE ROUGE -
-
-
-
-
- -
- THE SLAB -
-
-
-
-
- -
- CENTRE COURT -
-
-
-
-
- -
- THE NIGHT COURT - LEAGUE 4 -
-
-
-
-
SERVE UP
-
-
-
- -
-
DIFFICULTY — PIPS STATT ERKLÄRUNG
-
-
-
<
-
DIFFICULTY
-
-
-
- EASY - - - - - -
-
- MEDIUM - - - - - - LAST -
-
- HARD - - - - - -
-
-
-
- -
-
TRAINING — REVANCHE OBEN, SONST NUR NAME UND RANG
-
-
-
<
-
TRAINING
-
-
-
- -
- THE STRAWBERRY SELLER - PUT YOU OUT -
-
>
-
-
-
- - THE SHAMBLER - 01 -
-
- - THE BALL BOY - 02 -
-
- - THE SPECTATOR - 03 -
-
- - THE GROUNDSKEEPER - 04 -
-
- - THE ROLLER MAN - 05 -
-
- - THE STEWARD - 06 -
-
- - THE MASCOT - 07 -
-
- - THE RUNNER - 08 -
-
- - THE STRINGER - 10 -
-
- - THE LINE JUDGE - 11 -
-
- - THE CHAIR UMPIRE - 12 -
-
- LEAGUE 4 - 13 — 15 -
-
-
-
-
- -
-
VS A FRIEND — DREI ZEILEN STATT DREI ABSÄTZE
-
-
-
<
-
VS A FRIEND
-
-
-
-
YOU
-
OLIVER
-
-
-
HOST
-
-
CLASSIC
-
DEATH MATCH
-
-
-
-
JOIN
-
-
CODE
-
GO
-
-
-
-
-
- -
-
MODALS — EINE REGEL PRO ZEILE
-
-
-
-
-
-
C
-
-
THE TOURNAMENT
-
LEAGUE 3 · 4 ROUNDS
-
-
-
-
WIN IT ALL — MOVE UP A LEAGUE
-
-
-
POINTS DRAW BLOOD
-
- - - - - -
-
-
-
WALK ON
-
-
-
- -
-
THE STRAWBERRY SELLER
-
CREAM WITH THAT?
-
-
-
-
-
LEVEL 14
-
YOURS 12
-
-
-
WALK ON
-
-
-
LEAVE THE MATCH?
-
BACK TO THE MENU
-
PLAY ON
-
-
-
-
-
- - -
-
-
4 · ERSATZ FÜR BUNGEE — DREI KANDIDATEN AN EINEM SCREEN
-
Gesucht: aufrecht (Bangers ist geneigt), Großbuchstaben, leichter und weniger blockig als Bungee, aber im Sport-/Club-Milieu zuhause. Alle drei sind Google Fonts unter SIL OFL, also genauso bündelbar und subsetbar wie die vier Schriften, die schon im Repo liegen. Layout, Grössen-Wirkung und der Bangers-Hero sind in allen drei identisch — nur Screen-Titel, Raum-Label und Poster-Namen wechseln.
-
- -
- -
-
- 4a - BEBAS NEUE — CLUB-BESCHILDERUNG -
-
Hoch, schmal, dünne Striche — die Schrift von Anzeigetafeln und Turnierplakaten. Nimmt viel weniger Fläche als Bungee, bleibt aber laut, weil sie hoch ist. Nur Grossbuchstaben, was hier ohnehin gilt.
-
-
-
<
-
PICK A GAME
-
-
-
-
SEASON
-
-
-
THE WAY UP
-
-
THE TOURNAMENT
-
LEAGUE 3 · ROUND ONE: THE STRAWBERRY SELLER
-
-
-
-
-
TRAINING
-
-
-
-
ARCADE
-
-
-
-
18 RUNGS
-
TRAINING
-
-
-
-
-
CASUAL
-
-
-
-
SPLATTER GORE
-
-
-
-
LAST
-
CLASSIC TENNIS
-
-
-
-
VS A FRIEND
-
-
-
-
-
-
- -
-
- 4b - OSWALD 500 — PLAKAT, MITTLERES GEWICHT -
-
Leicht schmalgelaufene Gothic — Turnierplakat statt Neon-Schild. Hat echte Schnitte, also lässt sich das Gewicht regeln (hier 500), was Bungee als Single-Weight nie konnte. Ruhigste der drei.
-
-
-
<
-
PICK A GAME
-
-
-
-
SEASON
-
-
-
THE WAY UP
-
-
THE TOURNAMENT
-
LEAGUE 3 · ROUND ONE: THE STRAWBERRY SELLER
-
-
-
-
-
TRAINING
-
-
-
-
ARCADE
-
-
-
-
18 RUNGS
-
TRAINING
-
-
-
-
-
CASUAL
-
-
-
-
SPLATTER GORE
-
-
-
-
LAST
-
CLASSIC TENNIS
-
-
-
-
VS A FRIEND
-
-
-
-
-
-
- -
-
- 4c - JERSEY 15 — PIXELRASTER, TRIKOTNUMMER -
-
Sitzt auf demselben Raster wie Silkscreen, ist aber proportional und viel dünner — die Schrift auf einem Trikot, nicht auf einem Terminal. Bleibt am nächsten an der Pixel-Art, ist aber die klar leiseste Wahl.
-
-
-
<
-
PICK A GAME
-
-
-
-
SEASON
-
-
-
THE WAY UP
-
-
THE TOURNAMENT
-
LEAGUE 3 · ROUND ONE: THE STRAWBERRY SELLER
-
-
-
-
-
TRAINING
-
-
-
-
ARCADE
-
-
-
-
18 RUNGS
-
TRAINING
-
-
-
-
-
CASUAL
-
-
-
-
SPLATTER GORE
-
-
-
-
LAST
-
CLASSIC TENNIS
-
-
-
-
VS A FRIEND
-
-
-
-
-
-
-
-
- -
-
-
3a · ALLE FUNKTIONALEN SCREENS IN DER GEWÄHLTEN SPRACHE
-
Regel für alle: Bebas Neue benennt (Screen-Titel, Räume, Karten), Bangers ruft genau einmal pro Screen (das Fixture, der Primär-Button, die Modal-Headline), Silkscreen bleibt für Zahlen und Labels. Sätze nur, wo sie eine Entscheidung tragen — Regeln wandern ins Modal, Werte in Balken und Pips. Die Wand selbst ist 2c.
-
- -
- -
-
SPLASH — OHNE ZIEL-SATZ, OHNE STRAPS
-
-
- Splatter Tennis -

Fifteen. Thirty. Forty. Dead.

-
- -
-
- OLIVER - LEVEL 12 -
- - - 13 - -
- > -
-
-
-
- LEAGUE 3 · THE CUP - ROUND ONE: THE STRAWBERRY SELLER -
-
OTHER GAMES
-
-
STATS4 812
-
BOARDBEST 612
-
-

BUILD 341

-
-
-
- -
-
MATCH SETUP — PLÄTZE OHNE BESCHREIBUNG, BALKEN SAGEN ES
-
-
-
<
-
MATCH SETUP
-
-
-
PLAYER
-
-
LIVE
PLAYER
SPRITE
-
-
-
MEN'S
-
WOMEN'S
-
- KITCLARET -
-
-
-
-
-
-
-
- SKINOLIVE -
-
-
-
-
-
-
-
-
-
- COURT - BOUNCE · PACE -
-
- -
- TERRE ROUGE -
-
-
-
-
- -
- THE SLAB -
-
-
-
-
- -
- CENTRE COURT -
-
-
-
-
- -
- THE NIGHT COURT - LEAGUE 4 -
-
-
-
-
SERVE UP
-
-
-
- -
-
DIFFICULTY — PIPS STATT ERKLÄRUNG
-
-
-
<
-
DIFFICULTY
-
-
-
- EASY - - - - - -
-
- MEDIUM - - - - - - LAST -
-
- HARD - - - - - -
-
-
-
- -
-
TRAINING — REVANCHE OBEN, SONST NUR NAME UND RANG
-
-
-
<
-
TRAINING
-
-
-
- -
- THE STRAWBERRY SELLER - PUT YOU OUT -
-
>
-
-
-
- - THE SHAMBLER - 01 -
-
- - THE BALL BOY - 02 -
-
- - THE SPECTATOR - 03 -
-
- - THE GROUNDSKEEPER - 04 -
-
- - THE ROLLER MAN - 05 -
-
- - THE STEWARD - 06 -
-
- - THE MASCOT - 07 -
-
- - THE RUNNER - 08 -
-
- - THE STRINGER - 10 -
-
- - THE LINE JUDGE - 11 -
-
- - THE CHAIR UMPIRE - 12 -
-
- LEAGUE 4 - 13 — 15 -
-
-
-
-
- -
-
VS A FRIEND — DREI ZEILEN STATT DREI ABSÄTZE
-
-
-
<
-
VS A FRIEND
-
-
-
-
YOU
-
OLIVER
-
-
-
HOST
-
-
CLASSIC
-
DEATH MATCH
-
-
-
-
JOIN
-
-
CODE
-
GO
-
-
-
-
-
- -
-
MODALS — EINE REGEL PRO ZEILE
-
-
-
-
-
-
C
-
-
THE TOURNAMENT
-
LEAGUE 3 · 4 ROUNDS
-
-
-
-
WIN IT ALL — MOVE UP A LEAGUE
-
-
-
POINTS DRAW BLOOD
-
- - - - - -
-
-
-
WALK ON
-
-
-
- -
-
THE STRAWBERRY SELLER
-
CREAM WITH THAT?
-
-
-
-
-
LEVEL 14
-
YOURS 12
-
-
-
WALK ON
-
-
-
LEAVE THE MATCH?
-
BACK TO THE MENU
-
PLAY ON
-
-
-
-
-
- -
-
-
1a · WELCHE SCHRIFT TRAGEN DIE POSTER-HEADLINES?
-
Gleiche Wand, gleiche Grössenwirkung, nur die Headline-Schrift getauscht. Bangers ist geneigt und schmaler, braucht also mehr Punkte für dieselbe Präsenz.
-
- -
- -
-
- 2a - BUNGEE — AUFRECHT, SIGNAGE -
-
Dieselbe Stimme wie der Screen-Titel. Steht still, liest sich als Beschilderung — „welcher Raum ist das“.
-
-
-
<
-
PICK A GAME
-
-
-
-
SEASON
-
-
-
THE WAY UP
-
-
THE TOURNAMENT
-
LEAGUE 3 · ROUND ONE: THE STRAWBERRY SELLER
-
-
-
-
-
TRAINING
-
-
-
-
ARCADE
-
-
-
-
18 RUNGS
-
TRAINING
-
-
-
-
-
CASUAL
-
-
-
-
SPLATTER GORE
-
-
-
-
LAST
-
CLASSIC TENNIS
-
-
-
-
VS A FRIEND
-
-
-
-
-
-
- -
-
- 2c - GEMISCHT — HERO RUFT, REGALE BESCHILDERN -
-
Das Fixture ist eine Ankündigung, also Bangers. Die fünf Regal-Poster bleiben Bungee — sie beantworten „welcher Raum“, und fünf geneigte Rufe untereinander nehmen der Liste die Ruhe.
-
-
-
<
-
PICK A GAME
-
-
-
-
SEASON
-
-
-
THE WAY UP
-
-
THE TOURNAMENT
-
LEAGUE 3 · ROUND ONE: THE STRAWBERRY SELLER
-
-
-
-
-
TRAINING
-
-
-
-
ARCADE
-
-
-
-
18 RUNGS
-
TRAINING
-
-
-
-
-
CASUAL
-
-
-
-
SPLATTER GORE
-
-
-
-
LAST
-
CLASSIC TENNIS
-
-
-
-
VS A FRIEND
-
-
-
-
-
-
- -
-
- 2b - BANGERS — GENEIGT, ANKÜNDIGUNG -
-
Die Stimme, die im Spiel ruft (SET, CHAPTER, SERVE UP). Auf der Wand klingt jeder Modus wie eine Ankündigung — lauter, aber die Neigung nimmt der Liste die Ruhe.
-
-
-
<
-
PICK A GAME
-
-
-
-
SEASON
-
-
-
THE WAY UP
-
-
THE TOURNAMENT
-
LEAGUE 3 · ROUND ONE: THE STRAWBERRY SELLER
-
-
-
-
-
TRAINING
-
-
-
-
ARCADE
-
-
-
-
18 RUNGS
-
TRAINING
-
-
-
-
-
CASUAL
-
-
-
-
SPLATTER GORE
-
-
-
-
LAST
-
CLASSIC TENNIS
-
-
-
-
VS A FRIEND
-
-
-
-
-
-
-
-
- -
-
-
VORSCHLAG · WENIGER TEXT IN DEN MENÜS
-
Zwei Richtungen für „PICK A GAME“. Beide behalten Palette, Fonts und Poster-Art; sie unterscheiden sich darin, wo die Regeln hingehen.
-
- -
- -
-
- 1a - RÄUME BLEIBEN, SÄTZE GEHEN -
-
Ein Wort pro Raum, ein Name pro Poster. Regeln nur noch als Chip, wenn sie wirklich anders sind — der Rest wandert in das Modal, das beim ersten Start sowieso kommt.
-
- -
-
-
<
-
PICK A GAME
-
-
- -
-
SEASON
-
-
-
THE WAY UP
-
-
THE TOURNAMENT
-
LEAGUE 3 · ROUND ONE: THE STRAWBERRY SELLER
-
-
-
- -
-
TRAINING
-
-
-
-
ARCADE
-
-
-
-
18 RUNGS
-
TRAINING
-
-
-
- -
-
CASUAL
-
-
-
-
SPLATTER GORE
-
-
-
-
LAST
-
CLASSIC TENNIS
-
-
-
-
VS A FRIEND
-
-
-
-
-
- -
-
-
-
-
-
C
-
-
THE TOURNAMENT
-
LEAGUE 3 · 4 ROUNDS
-
-
-
-
-
WIN IT ALL — MOVE UP A LEAGUE
-
-
-
-
ITS POINTS DRAW BLOOD
-
- - - - - -
-
-
-
WALK ON
-
-
MODAL: EINE REGEL PRO ZEILE, KEINE ZWEITE ZEILE
-
-
-
- -
-
- 1b - EIN HELD, DER REST IST LISTE -
-
Keine Überschriften. Das Fixture ist das eine große Poster mit dem einen Satz; alles andere ist eine ruhige Zeilenliste mit Thumbnail. Ein Screen, kein Scrollen.
-
-
-
<
-
PICK A GAME
-
-
-
-
-
THE WAY UP
-
-
THE TOURNAMENT
-
LEAGUE 3 · ROUND ONE: THE STRAWBERRY SELLER
-
-
-
-
-
-
ARCADE
-
>
-
-
-
-
TRAINING
-
18 RUNGS
-
>
-
-
-
-
SPLATTER GORE
-
>
-
-
-
-
CLASSIC TENNIS
-
LAST
-
>
-
-
-
-
VS A FRIEND
-
>
-
-
-
-
-
-
-
- -
-
- IST - STAND HEUTE — NACHGEBAUT AUS index.html / config.MENU -
-
- -
-
SPLASH
-
-
- Splatter Tennis -

Fifteen. Thirty. Forty. Dead.

-
- - OLIVER - LEVEL 12 · LEAGUE 3 - - 12 - - 13 - - WIN THE CUP TO REACH LEAGUE 4 - > -
-
-
-
- LEAGUE 3 · THE CUP - 4 ROUNDS · ROUND ONE: THE STRAWBERRY SELLER -
-
OTHER GAMESCLIMB · TRAINING · GORE · A FRIEND
-
-
STATS4 812 KILLS
-
RANKINGSWORLD BOARD
-
-

BUILD 341

-
-
-
- -
-
PICK A GAME — SCROLLT ÜBER ZWEI BILDSCHIRME
-
-
-
<
-
PICK A GAME
-
-
-
-
- THE SEASON - THE ONLY WAY UP THE CLUB -
-
-
-
THE WAY UP
-
- THE TOURNAMENT - YOUR LEAGUE'S ROUNDS · WIN IT TO RISE -
-
-
-
-
- THE TRAINING - SHARPEN UP · NOTHING HERE MOVES YOUR LEAGUE -
-
-
-
- ARCADE - THE WHOLE CLUB, BOTTOM UP · BLOOD ONLY -
-
-
-
-
- TRAINING - PICK A BODY · ONE GAME · GO AGAIN -
-
-
-
-
- OUT OF COMPETITION - NO LEAGUE, NO RECORD, NO STAKES -
-
-
-
- SPLATTER GORE - NO WARM-UP · SURVIVE THE DEAD CLUB -
-
-
-
-
↓ WEITER UNTEN: CLASSIC TENNIS · VS A FRIEND
-
-
- -
-
MATCH SETUP
-
-
-
<
-
MATCH SETUP
-
-
-

01 · PLAYER

-
-
LIVE
PLAYER
SPRITE
-
-
-
MEN'S SINGLES
-
WOMEN'S SINGLES
-
- KITCRIMSON -
-
-
-
-
-
-
- SKINTAN -
-
-
-
-
-
-
-
-
-

02 · COURT

-
- -
- THE RED CLAY - SLOWEST · HIGHEST BOUNCE -
BOUNCE
-
PACE
-
-
-
- -
- THE HARD COURT - TRUE BOUNCE · MEDIUM PACE -
BOUNCE
-
PACE
-
-
-
- -
- THE LAWN - FAST · LOW SKID -
BOUNCE
-
PACE
-
-
-
-
-

CLASSIC TENNIS · MEDIUM · THE RED CLAY

-
SERVE UP
-
-
-
- -
-
MODAL IM SPIEL · THE TOURNAMENT
-
-
-
-
-
-
C
-
-
THE TOURNAMENT
-
YOUR LEAGUE, ROUND BY ROUND — WIN IT ALL, MOVE UP A LEAGUE
-
-
-
-
-
YOUR LEAGUE, ROUND BY ROUND
-
WIN IT ALL — MOVE UP A LEAGUE
-
-
-
-
ITS POINTS DRAW BLOOD
-
AT ZERO YOU ARE EATEN
-
- - - - - -
-
-
-
GOT IT
-
-
-
-
-
- -
- - diff --git a/design_handoff_menu_text_reduction/README.md b/design_handoff_menu_text_reduction/README.md deleted file mode 100644 index 7dbcd6b..0000000 --- a/design_handoff_menu_text_reduction/README.md +++ /dev/null @@ -1,242 +0,0 @@ -# Handoff: Menüführung — Text reduzieren (Splatter Tennis) - -## Overview -Die funktionalen Screens von Splatter Tennis (Splash, PICK A GAME, MATCH SETUP, DIFFICULTY, -TRAINING, VS A FRIEND und die In-Game-Modals) tragen heute mehr Text als eine Entscheidung -braucht — auf `PICK A GAME` rund 60 Wörter, verteilt auf drei Rubriken mit Straps plus sechs -Poster mit je einer Regelzeile. Das Ergebnis dieser Session: eine reduzierte Fassung aller -funktionalen Screens (~12–18 Wörter auf der Wand) und ein Wechsel der aufrechten Display-Schrift -von **Bungee** auf **Jersey 15**. - -Gameplay, Stats-Screens und Canvas-HUD sind bewusst NICHT Teil dieses Pakets. - -## About the Design Files -`Menues.dc.html` ist eine **Design-Referenz in HTML** — ein Prototyp, der Aussehen und Struktur -zeigt, kein Produktionscode zum Kopieren. Splatter Tennis ist Vanilla JS ohne Build-Step -(`CLAUDE.md`, Punkt 5: „Zero dependencies, no build step, no framework"), also wird die Fassung -in der bestehenden Umgebung nachgebaut: DOM-Menüs in `index.html` + `src/main.js`, Canvas-Modals -in `src/render.js`, Texte in `src/config.js`. Kein neues Framework, keine neuen Dateien ausser -der Schriftdatei. - -Die Datei enthält mehrere Runden untereinander, neueste oben. Verbindlich ist **Runde 5** -(„GESETZT — ALLE SCREENS IN JERSEY 15"). Darunter liegen als Historie: Runde 4 (Schrift-Kandidaten -Bebas Neue / Oswald / Jersey 15), Runde 3a (derselbe Satz in Bebas), Runde 2 (Bungee vs. Bangers), -Runde 1 (die zwei Grundrichtungen) und ganz unten der **IST-Stand**, pixelgenau aus dem heutigen -`index.html` nachgebaut — nützlich als Vor/Nach-Vergleich. - -## Fidelity -**High-fidelity.** Palette, Masse, Schriftgrössen und Copy sind aus dem Repo übernommen -(`index.html` CSS, `config.MODES` / `MENU` / `COURTS` / `LEAGUES` / `OPPONENTS`, `beats.js` -Modal-Payloads). Alle Sprites/Poster im Prototyp sind echte Repo-Assets. Übernahme darf 1:1 -erfolgen. - -## Die drei Register (die eigentliche Entscheidung) -| Rolle | heute | neu | -|---|---|---| -| benennen — Screen-Titel, Rubriken, Karten- und Poster-Namen | Bungee | **Jersey 15** | -| rufen — einmal pro Screen: Fixture, Primär-Button, Modal-Headline | Bangers | Bangers (unverändert) | -| informieren — Zahlen, Labels, Chips | Silkscreen | Silkscreen (unverändert) | - -Begründung Bungee → Jersey 15: Bungee ist Single-Weight und sehr fett/blockig; Jersey 15 sitzt -auf demselben Pixelraster wie Silkscreen (wirkt als Schriftfamilie mit der Sprite-Art), läuft -proportional und deutlich leichter. Beide sind SIL OFL. - -**Grössen-Umrechnung Bungee → Jersey 15: ca. +40 %** (Jersey sitzt tiefer im Kasten und läuft -leichter). Konkrete Werte im Abschnitt „Design Tokens". - -## Schrift einbauen -1. `Jersey15-Regular.ttf` von Google Fonts holen, wie Bungee subsetten (`pyftsubset`, Befehl - steht in `PROJECT.md`) → `assets/fonts/Jersey15-Subset.ttf`. -2. In `index.html` das `@font-face` von Bungee ersetzen (`?v=` mitziehen wie bei den anderen drei): - ```css - @font-face { font-family: 'Jersey 15'; font-weight: 400; font-display: block; - src: url('assets/fonts/Jersey15-Subset.ttf?v=87') format('truetype'); } - ``` -3. Alle `Bungee`-Vorkommen in `index.html` auf `'Jersey 15'` umstellen (Liste unten), - `Bungee-Subset.ttf` und `Bungee-OFL.txt` löschen, `Jersey15-OFL.txt` daneben legen. -4. `main.js?v=N` in `index.html` bumpen (`CLAUDE.md`, Punkt 4). - -## Screens / Views - -### 1 — Splash (`#splash` in index.html) -**Zweck:** Eintritt; die Plakette zeigt wer du bist, der Primär-Button das nächste Fixture. - -Änderungen: -- **Plakette (`#plaque`)** wird eine Zeile statt vier Grid-Areas: Wappen links (50px), rechts - Name + `LEVEL 12` in einer Baseline-Zeile, darunter der XP-Balken mit der Ziel-Zahl rechts. - **`#plGoal` entfällt komplett** — „WIN THE CUP TO REACH LEAGUE 4" sagt der Fixture-Button - darunter schon. Grid-Areas `name/lv/bar/goal` werden zu einem Flex-Row mit zwei Spalten. - - Name: Jersey 15 21px, `#ccd7ca`, `letter-spacing:0.5px` (war Silkscreen 700 13px) - - `LEVEL 12`: Silkscreen 700 10px, `#bcd455` (war Bangers 26px als `#plLv` — der Ruf gehört - dem Button unten, nicht der Plakette) - - Balken unverändert (6px, `#2c4238` Rahmen, Gradient `#3f9560 → #bcd455 55% → #e8b23a`) -- **Fixture-Button (`#mStart`)** bleibt Bangers 26px; die zweite Zeile (`#msLine`) verliert das - Rundenpräfix: `ROUND ONE: THE STRAWBERRY SELLER` statt - `4 ROUNDS · ROUND ONE: THE STRAWBERRY SELLER`. → in `config.cupFixture` die `line` kürzen - (`rounds.length + ' ROUNDS · '` fällt weg; die Rundenzahl steht im Modal). -- **`#mOther`** verliert seinen Strap (`CLIMB · TRAINING · GORE · A FRIEND` löschen), - Label auf Jersey 15 19px. -- **`#mStats` / `#mRanks`**: Straps ersetzt durch je eine Zahl aus lokalem Stand — - `STATS / 4 812` (Kill-Count, wie heute) und `BOARD / BEST 612` (eigener Bestwert; **kein** - Weltrang, der Splash lädt `/data/scores.json` nicht). Label Jersey 15 16px, Zahl - Silkscreen 700 10px `#bcd455`. -- Logo, Slogan (`Fifteen. Thirty. Forty. Dead.`), `#build` unverändert. - -### 2 — PICK A GAME (`#stmode` / `#modeList`, gebaut in `main.buildTops`) -**Zweck:** einen Modus wählen. Das ist der Screen, der „erschlägt". - -- **Rubriken** (`#modeList .mgroup > header`) verlieren ihren Strap (``) und werden ein Wort: - `THE SEASON → SEASON`, `THE TRAINING → TRAINING`, `OUT OF COMPETITION → CASUAL`. - Jersey 15 17px, `#bcd455`, `letter-spacing:2px`. → `config.MENU`: `sub` der drei `group`-Rows - löschen, `group`-Text kürzen; in `main.buildTops` das `` aus dem `header`-Template nehmen. -- **Poster** tragen nur noch den Namen — `${card.sub}` fällt in `main.banner()` weg. - Damit werden die `sub`-Felder in `config.MODES` für die Wand irrelevant (Aufbewahren lohnt: - die Modals nutzen dieselben Sätze). -- **Hero-Poster** (das Fixture, `mode:'cup'`) ist der einzige mit einer zweiten Zeile und dem - einzigen Ruf: Name in **Bangers 32px**, darunter die Fixture-Zeile in Silkscreen 10px - `#f0cfc4`. Höhe 142px. `.way`-Ribbon („THE WAY UP") unverändert. -- **Übrige Poster** in Jersey 15, Höhe gestaffelt statt einheitlich 148px: - - TRAINING-Raum: zwei Poster **nebeneinander**, je `flex:1`, Höhe 106px, Name 27px - - CASUAL-Raum: drei Poster gestapelt, Höhe 84px, Name 30px -- **Regeln, die wirklich anders sind, bleiben als Chip** (`.last`-Optik, Silkscreen 8px, acid, - `rgba(0,0,0,0.72)` Grund): `18 RUNGS` auf TRAINING, `LAST` auf dem zuletzt gespielten. -- Ergebnis: der ganze Wall passt in 844px (heute zwei Bildschirme). Gerechnet mit - Header 64 + Padding 14 + 142/106/84 + Gaps 17/8. - -### 3 — MATCH SETUP (`#setup`) -- `

01 · PLAYER

` / `02 · COURT` → Rubrik-Zeile wie auf der Wand: - Jersey 15 17px acid, Text `PLAYER` / `COURT`. Die Nummerierung entfällt (es sind zwei Blöcke - auf einem Screen, keine Schritte). -- Über der Platzliste steht die Legende **einmal**: rechts in der Rubrik-Zeile - `BOUNCE · PACE` (Silkscreen 8px, dim). -- **Platz-Zeilen** (`#courtList button.court`) verlieren `${court.sub}` — Name - (Jersey 15 19px) plus die zwei Meter, die dasselbe wortlos sagen. Bildhöhe 66 → 60px. -- **Gesperrte Plätze** (`.court.lock`) behalten Bild und Namen, statt einer Erklärung nur - `LEAGUE 4` in `#cf5a4e`, Silkscreen 9px. -- **Tour-Tiles**: `MEN'S SINGLES` / `WOMEN'S SINGLES` → `MEN'S` / `WOMEN'S`, Jersey 15 16px. -- **`#sSum`** (die Zusammenfassungszeile über SERVE UP) entfällt — alle drei genannten Werte - sind auf dem Screen sichtbar markiert. -- `SERVE UP` bleibt Bangers 26px auf `#a4231f`. - -### 4 — DIFFICULTY (`#stsub` / `#modeSub button.chip`, aus `config.DIFFS`) -- Chip wird zweispaltig: Name links (Jersey 15 30px), **drei Pips** rechts (je 10×10px, kein - Radius) — 1/2/3 gefüllt. EASY/MEDIUM acid `#bcd455`, HARD `#cf5a4e`; leere Pips - `rgba(255,255,255,0.12)`. -- Kein Erklärtext. Padding 26px/16px, `gap:10px`. -- `LAST`-Chip bleibt oben rechts im gewählten. - -### 5 — TRAINING (`#train` / `#trainList`, gebaut in `main.buildTrain`) -- `

PICK YOUR OPPONENTONE GAME · GO AGAIN

` entfällt — der Screen-Titel - `TRAINING` genügt. -- **Revanche-Tile** (`profile.prof.out`) wird eine **breite Zeile über dem Raster**: Kopf 52px - links, Name (Jersey 15 24px), darunter `PUT YOU OUT` (statt `PUT YOU OUT OF THE CUP`) in - `#e08a7c`, Chevron rechts. Rahmen `#a4231f`, Grund `rgba(164,35,31,0.16)`. -- **Übrige Rungs** bleiben das 2-spaltige Raster: Kopf 44px, Name Jersey 15 17px zentriert, - Rangnummer zweistellig (`01`, `02`, …) statt `No. 1` — Silkscreen 8px dim. -- **Letzte Kachel** ist ein Ziel, keine Liste: gestrichelter Rahmen `#2c4238`, `LEAGUE 4` und - `13 — 15` in `#4a5a52`. (Heute endet die Liste einfach am Band-Cap.) - -### 6 — VS A FRIEND (`#vs` / `#vsPick`) -- Die drei `

` mit Straps (`01 · YOUR NAME / ON THEIR SCOREBOARD`, `02 · HOST A MATCH / - THEY COME TO YOU`, `OR JOIN THEIRS / WITH THEIR CODE`) werden drei Wörter: - **`YOU` / `HOST` / `JOIN`**, Jersey 15 17px acid. -- Host-Buttons: `CLASSIC TENNIS` / `DEATH MATCH` → `CLASSIC` / `DEATH MATCH` nebeneinander, - Jersey 15 19px, Padding 20px. -- Join-Zeile: Code-Feld plus Button `GO` (statt `JOIN` — daneben steht die Rubrik JOIN), - Feld-Letterspacing 6px. -- `#vsErr`-Meldungen unverändert (`THE CODE IS FOUR LETTERS`). - -### 7 — In-Game-Modals (`render.drawModal`, Payloads aus `beats.js`) -Die Box-Sprache aus dem 2026-08-31-Pass bleibt (`MODAL_PAD 14`, `MODAL_GAP 13`, `MODAL_BTN 30`, -`MODAL_FOOT 8`, Breite `min(W-44, 244)`, Fill `rgba(8,22,16,0.93)`, Edge `rgba(120,150,130,0.45)`, -Rules `rgba(120,150,130,0.22)`). Reduziert wird der **Inhalt einer Row**: - -- **Eine Regel pro Row, keine Unterzeile.** Heute rendert `modalRows` pro `m.rules`-Eintrag - `lead` + `sub`; der `sub` formuliert in fast allen Fällen den `lead` nur um - (`YOUR LEAGUE, ROUND BY ROUND` / `WIN IT ALL — MOVE UP A LEAGUE`). Neu: **eine** Zeile, die - das Ergebnis nennt — `WIN IT ALL — MOVE UP A LEAGUE`. Also in `beats.js` die `rules`-Arrays - auf je einen String reduzieren und `modalRows` entsprechend vereinfachen; `m.body` bleibt als - Fallback unverändert. -- **Blut ist Pips, kein Satz**: Row `POINTS DRAW BLOOD` (gekürzt von `ITS POINTS DRAW BLOOD`) - plus `TUNE.matchPlayerHp` Pips **in derselben Zeile rechts**, statt Zeile + Unterzeile - (`AT ZERO YOU ARE EATEN`) + Pips darunter. -- **Masthead**: Titel Bangers 27px in der Modal-Farbe, darunter EINE Silkscreen-10px-Zeile. - Beim Rules-Modal ist das die Sachzeile (`LEAGUE 3 · 4 ROUNDS`) — hier landet die Rundenzahl, - die aus `cupFixture.line` genommen wurde. Beim Walk-on bleibt es `foe.line` - (`CREAM WITH THAT?`). -- **Walk-on** (Gegner): Kopf-Sprite 56px, Name, `foe.line`, dann EINE Row mit den zwei Leveln - gegenüber (`LEVEL 14` hot links, `YOURS 12` dim rechts) — statt zwei Textzeilen plus - Split-Figur. -- Footer-Button unverändert (`WALK ON` / `GOT IT` / `PLAY ON`, `m.btn` überschreibt). - -### 8 — Quit-Modal (`#qAsk` / `.qBox`) -Unverändert übernehmen — es ist schon lean. Nur `LEAVE THE MATCH?` von Silkscreen 12px auf -**Bangers 20px** (es ist eine Frage, die ruft), `PLAY ON` auf Jersey 15 17px. - -## Interactions & Behavior -Keine Änderung an Navigation oder Zustandslogik. Alles Bestehende bleibt: -- `go(stage)` / `#intro[data-stage]`, ein Overlay, vier Panels -- Tap auf ein Poster antwortet UND geht weiter (`main.buildTops`), `pickMode` schreibt - `localStorage.mode` -- Die Deal-in-Animation (`#intro.deal`, `dealIn` 0.28s `cubic-bezier(0.34,1.56,0.64,1)`, - Delays 0.02/0.09/0.16/0.23/0.30s, `.mgroup`-Staffel 0.09/0.17/0.25s) gilt weiter — die - Rubriken bleiben `
`, damit sie greift -- `prefers-reduced-motion: reduce` schaltet sie weiter ab -- Modal-Dismiss-Regeln unverändert (`m.in < 1` schluckt die Gesture, Tap überall entlässt, - `G.seen.` gated) -- Swipe-links-zurück im Shell bleibt unberührt - -## State Management -Nichts Neues. Zwei Werte werden neu GELESEN, beide existieren schon: -- `profile.prof.out` (der Rung, der dich aus dem Cup geworfen hat) → Revanche-Zeile; wird - bereits von `buildTrain` genutzt -- der eigene Bestwert für die BOARD-Kachel → aus `stats` (lokal), **nicht** vom Server - -## Design Tokens -Unverändert aus `index.html`: -``` ---ink #0e1714 --panel rgba(42,62,54,0.42) --line #2c4238 ---acid #bcd455 --cream #ccd7ca --dim #6c7f74 ---red #a4231f --bad #cf5a4e --solid #16211d -Poster-Veil: linear-gradient(0deg, rgba(6,10,8,0.94) 0%, rgba(6,10,8,0.86) 22%, - rgba(6,10,8,0.5) 58%, rgba(6,10,8,0) 100%) — Höhe 52–62 % je Kachelhöhe -Modal: fill rgba(8,22,16,0.93) · edge rgba(120,150,130,0.45) · rule rgba(120,150,130,0.22) -Rahmen: 2px überall in den Menüs, 1px im Canvas-Modal -``` - -Typografie neu (Bungee-Wert → Jersey-15-Wert): -``` -Screen-Titel (.hdr h2) 23px → 33px/1 letter-spacing 1px -Rubrik / Raum-Label 15px → 17px letter-spacing 2px -Poster-Name, Hero — → Bangers 32px/0.95 (der eine Ruf) -Poster-Name, TRAINING-Paar 17px → 27px/1 -Poster-Name, CASUAL-Stapel 19px → 30px/1 -Karten-/Platz-Name 15px → 22px -Difficulty-Chip 22px → 30px -Training-Tile-Name 12px → 17px/1 -Sekundär-Button 13px → 18px -Zahlen, Labels, Chips Silkscreen 8–10px (unverändert) -Primär-Button Bangers 26px (unverändert) -``` - -## Assets -Alle Assets im Prototyp stammen aus dem Repo, unverändert — nichts muss neu gezeichnet werden: -`assets/logo.png`, `menu_bg.png`, `mode_cup|arcade|train|gore|classic|vs.png`, -`court_clay|hard|grass|gloom.png`, `badge_l3.png`, `z_{sh,bb,sp,gk,rm,st,ma,ru,sb,sg,lj,cu}head.png`, -`fonts/{Silkscreen-Regular,Silkscreen-Bold,Bangers-Regular}.ttf`. -NEU nötig: `fonts/Jersey15-Subset.ttf` (Google Fonts, SIL OFL) — ersetzt `Bungee-Subset.ttf`. - -## Files -- `Menues.dc.html` — der Prototyp. Runde 5 oben ist verbindlich; darunter Runde 4 (Schriftwahl), - 3a (Bebas-Fassung), 2 (Bungee vs. Bangers), 1 (Grundrichtungen), IST-Stand. -- `support.js` — Runtime des Prototyps, für die Implementierung irrelevant. -- `assets/` — die Repo-Assets, die der Prototyp lädt. - -Betroffene Repo-Dateien: `index.html` (CSS + Markup der Panels), `src/main.js` -(`banner`, `buildTops`, `buildSub`, `buildTrain`, `refreshSplash`), `src/config.js` -(`MENU`-Gruppen, `cupFixture.line`, ggf. `MODES.sub`), `src/render.js` (`modalRows`, -`modalLayout`), `src/beats.js` (die `rules`-Payloads). - -## Vor dem Commit -`node test.js` muss grün sein (`CLAUDE.md`, Punkt 2) — die Fixture-Deskriptor-Tests hängen an -`cupFixture`, also die `line`-Kürzung dort mit anpassen. `main.js?v=N` bumpen. diff --git a/design_handoff_menu_text_reduction/assets/badge_l1.png b/design_handoff_menu_text_reduction/assets/badge_l1.png deleted file mode 100644 index d550140..0000000 Binary files a/design_handoff_menu_text_reduction/assets/badge_l1.png and /dev/null differ diff --git a/design_handoff_menu_text_reduction/assets/badge_l3.png b/design_handoff_menu_text_reduction/assets/badge_l3.png deleted file mode 100644 index 90b73f0..0000000 Binary files a/design_handoff_menu_text_reduction/assets/badge_l3.png and /dev/null differ diff --git a/design_handoff_menu_text_reduction/assets/badge_l4.png b/design_handoff_menu_text_reduction/assets/badge_l4.png deleted file mode 100644 index e849bdd..0000000 Binary files a/design_handoff_menu_text_reduction/assets/badge_l4.png and /dev/null differ diff --git a/design_handoff_menu_text_reduction/assets/court_clay.png b/design_handoff_menu_text_reduction/assets/court_clay.png deleted file mode 100644 index a0e7c10..0000000 Binary files a/design_handoff_menu_text_reduction/assets/court_clay.png and /dev/null differ diff --git a/design_handoff_menu_text_reduction/assets/court_gloom.png b/design_handoff_menu_text_reduction/assets/court_gloom.png deleted file mode 100644 index 700727a..0000000 Binary files a/design_handoff_menu_text_reduction/assets/court_gloom.png and /dev/null differ diff --git a/design_handoff_menu_text_reduction/assets/court_grass.png b/design_handoff_menu_text_reduction/assets/court_grass.png deleted file mode 100644 index 6ee82fa..0000000 Binary files a/design_handoff_menu_text_reduction/assets/court_grass.png and /dev/null differ diff --git a/design_handoff_menu_text_reduction/assets/court_hard.png b/design_handoff_menu_text_reduction/assets/court_hard.png deleted file mode 100644 index 27f0481..0000000 Binary files a/design_handoff_menu_text_reduction/assets/court_hard.png and /dev/null differ diff --git a/design_handoff_menu_text_reduction/assets/fonts/Bangers-Regular.ttf b/design_handoff_menu_text_reduction/assets/fonts/Bangers-Regular.ttf deleted file mode 100644 index df7d5dc..0000000 Binary files a/design_handoff_menu_text_reduction/assets/fonts/Bangers-Regular.ttf and /dev/null differ diff --git a/design_handoff_menu_text_reduction/assets/fonts/Bungee-Subset.ttf b/design_handoff_menu_text_reduction/assets/fonts/Bungee-Subset.ttf deleted file mode 100644 index 050943a..0000000 Binary files a/design_handoff_menu_text_reduction/assets/fonts/Bungee-Subset.ttf and /dev/null differ diff --git a/design_handoff_menu_text_reduction/assets/fonts/Silkscreen-Bold.ttf b/design_handoff_menu_text_reduction/assets/fonts/Silkscreen-Bold.ttf deleted file mode 100644 index 6771252..0000000 Binary files a/design_handoff_menu_text_reduction/assets/fonts/Silkscreen-Bold.ttf and /dev/null differ diff --git a/design_handoff_menu_text_reduction/assets/fonts/Silkscreen-Regular.ttf b/design_handoff_menu_text_reduction/assets/fonts/Silkscreen-Regular.ttf deleted file mode 100644 index 8caff81..0000000 Binary files a/design_handoff_menu_text_reduction/assets/fonts/Silkscreen-Regular.ttf and /dev/null differ diff --git a/design_handoff_menu_text_reduction/assets/logo.png b/design_handoff_menu_text_reduction/assets/logo.png deleted file mode 100644 index 29b214b..0000000 Binary files a/design_handoff_menu_text_reduction/assets/logo.png and /dev/null differ diff --git a/design_handoff_menu_text_reduction/assets/menu_bg.png b/design_handoff_menu_text_reduction/assets/menu_bg.png deleted file mode 100644 index 9426b91..0000000 Binary files a/design_handoff_menu_text_reduction/assets/menu_bg.png and /dev/null differ diff --git a/design_handoff_menu_text_reduction/assets/mode_arcade.png b/design_handoff_menu_text_reduction/assets/mode_arcade.png deleted file mode 100644 index efdd97b..0000000 Binary files a/design_handoff_menu_text_reduction/assets/mode_arcade.png and /dev/null differ diff --git a/design_handoff_menu_text_reduction/assets/mode_classic.png b/design_handoff_menu_text_reduction/assets/mode_classic.png deleted file mode 100644 index b5590fc..0000000 Binary files a/design_handoff_menu_text_reduction/assets/mode_classic.png and /dev/null differ diff --git a/design_handoff_menu_text_reduction/assets/mode_cup.png b/design_handoff_menu_text_reduction/assets/mode_cup.png deleted file mode 100644 index 4ab2300..0000000 Binary files a/design_handoff_menu_text_reduction/assets/mode_cup.png and /dev/null differ diff --git a/design_handoff_menu_text_reduction/assets/mode_gore.png b/design_handoff_menu_text_reduction/assets/mode_gore.png deleted file mode 100644 index 7d6e4c6..0000000 Binary files a/design_handoff_menu_text_reduction/assets/mode_gore.png and /dev/null differ diff --git a/design_handoff_menu_text_reduction/assets/mode_train.png b/design_handoff_menu_text_reduction/assets/mode_train.png deleted file mode 100644 index 6a6066b..0000000 Binary files a/design_handoff_menu_text_reduction/assets/mode_train.png and /dev/null differ diff --git a/design_handoff_menu_text_reduction/assets/mode_vs.png b/design_handoff_menu_text_reduction/assets/mode_vs.png deleted file mode 100644 index 1bfa033..0000000 Binary files a/design_handoff_menu_text_reduction/assets/mode_vs.png and /dev/null differ diff --git a/design_handoff_menu_text_reduction/assets/z_bbhead.png b/design_handoff_menu_text_reduction/assets/z_bbhead.png deleted file mode 100644 index b2374f7..0000000 Binary files a/design_handoff_menu_text_reduction/assets/z_bbhead.png and /dev/null differ diff --git a/design_handoff_menu_text_reduction/assets/z_cuhead.png b/design_handoff_menu_text_reduction/assets/z_cuhead.png deleted file mode 100644 index 9262881..0000000 Binary files a/design_handoff_menu_text_reduction/assets/z_cuhead.png and /dev/null differ diff --git a/design_handoff_menu_text_reduction/assets/z_gkhead.png b/design_handoff_menu_text_reduction/assets/z_gkhead.png deleted file mode 100644 index d2cbdcc..0000000 Binary files a/design_handoff_menu_text_reduction/assets/z_gkhead.png and /dev/null differ diff --git a/design_handoff_menu_text_reduction/assets/z_ljhead.png b/design_handoff_menu_text_reduction/assets/z_ljhead.png deleted file mode 100644 index 8105887..0000000 Binary files a/design_handoff_menu_text_reduction/assets/z_ljhead.png and /dev/null differ diff --git a/design_handoff_menu_text_reduction/assets/z_mahead.png b/design_handoff_menu_text_reduction/assets/z_mahead.png deleted file mode 100644 index 2877e4d..0000000 Binary files a/design_handoff_menu_text_reduction/assets/z_mahead.png and /dev/null differ diff --git a/design_handoff_menu_text_reduction/assets/z_rmhead.png b/design_handoff_menu_text_reduction/assets/z_rmhead.png deleted file mode 100644 index 19f9a01..0000000 Binary files a/design_handoff_menu_text_reduction/assets/z_rmhead.png and /dev/null differ diff --git a/design_handoff_menu_text_reduction/assets/z_ruhead.png b/design_handoff_menu_text_reduction/assets/z_ruhead.png deleted file mode 100644 index e73de25..0000000 Binary files a/design_handoff_menu_text_reduction/assets/z_ruhead.png and /dev/null differ diff --git a/design_handoff_menu_text_reduction/assets/z_sbhead.png b/design_handoff_menu_text_reduction/assets/z_sbhead.png deleted file mode 100644 index b4831bf..0000000 Binary files a/design_handoff_menu_text_reduction/assets/z_sbhead.png and /dev/null differ diff --git a/design_handoff_menu_text_reduction/assets/z_sghead.png b/design_handoff_menu_text_reduction/assets/z_sghead.png deleted file mode 100644 index 341ed5b..0000000 Binary files a/design_handoff_menu_text_reduction/assets/z_sghead.png and /dev/null differ diff --git a/design_handoff_menu_text_reduction/assets/z_shhead.png b/design_handoff_menu_text_reduction/assets/z_shhead.png deleted file mode 100644 index 6cc0c05..0000000 Binary files a/design_handoff_menu_text_reduction/assets/z_shhead.png and /dev/null differ diff --git a/design_handoff_menu_text_reduction/assets/z_sphead.png b/design_handoff_menu_text_reduction/assets/z_sphead.png deleted file mode 100644 index b1d7db2..0000000 Binary files a/design_handoff_menu_text_reduction/assets/z_sphead.png and /dev/null differ diff --git a/design_handoff_menu_text_reduction/assets/z_sthead.png b/design_handoff_menu_text_reduction/assets/z_sthead.png deleted file mode 100644 index 390b437..0000000 Binary files a/design_handoff_menu_text_reduction/assets/z_sthead.png and /dev/null differ diff --git a/design_handoff_menu_text_reduction/support.js b/design_handoff_menu_text_reduction/support.js deleted file mode 100644 index cb009b6..0000000 --- a/design_handoff_menu_text_reduction/support.js +++ /dev/null @@ -1,1911 +0,0 @@ -// GENERATED from dc-runtime/src/*.ts — do not edit. Rebuild with `cd dc-runtime && bun run build`. -"use strict"; -(() => { - var __defProp = Object.defineProperty; - var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value; - var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value); - - // src/react.ts - function getReact() { - const R = window.React; - if (!R) throw new Error("dc-runtime: window.React is not available yet"); - return R; - } - function getReactDOM() { - const RD = window.ReactDOM; - if (!RD) throw new Error("dc-runtime: window.ReactDOM is not available yet"); - return RD; - } - var h = ((...args) => getReact().createElement( - ...args - )); - - // src/parse.ts - function parseDcDocument(doc) { - const dc = doc.querySelector("x-dc"); - if (!dc) return null; - const scriptEl = doc.querySelector("script[data-dc-script]"); - const { props, preview } = parseDataProps( - scriptEl?.getAttribute("data-props") ?? null - ); - return { - template: dc.innerHTML, - js: scriptEl ? scriptEl.textContent || "" : "", - props, - preview - }; - } - function parseDcText(src) { - const openMatch = /]*)?>/.exec(src); - if (!openMatch) return null; - const close = src.lastIndexOf(""); - if (close === -1 || close < openMatch.index) return null; - const template = src.slice(openMatch.index + openMatch[0].length, close); - const doc = new DOMParser().parseFromString(src, "text/html"); - const scriptEl = doc.querySelector("script[data-dc-script]"); - const { props, preview } = parseDataProps( - scriptEl?.getAttribute("data-props") ?? null - ); - return { - template, - js: scriptEl ? scriptEl.textContent || "" : "", - props, - preview - }; - } - function parseDataProps(raw) { - if (!raw) return { props: null, preview: null }; - let parsed; - try { - parsed = JSON.parse(raw); - } catch { - return { props: null, preview: null }; - } - if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) { - return { props: null, preview: null }; - } - const obj = parsed; - const preview = obj.$preview && typeof obj.$preview === "object" ? obj.$preview : null; - const rest = {}; - for (const k of Object.keys(obj)) { - if (k[0] !== "$") rest[k] = obj[k]; - } - return { props: Object.keys(rest).length ? rest : null, preview }; - } - function dcNameFromPath(pathname) { - let p = pathname || ""; - try { - p = decodeURIComponent(p); - } catch { - } - const base = p.split("/").pop() || "Root"; - return base.replace(/\.dc\.html$/, "").replace(/\.html?$/, "") || "Root"; - } - - // src/boot.ts - var BASE_CSS = ` - .sc-placeholder{background:color-mix(in srgb,currentColor 8%,transparent); - border:1px solid color-mix(in srgb,currentColor 50%,transparent); - border-radius:2px;box-sizing:border-box;overflow:hidden} - @keyframes sc-shine{0%{background-position:100% 50%}100%{background-position:0% 50%}} - html.sc-dc-streaming .sc-placeholder, - html.sc-dc-streaming .sc-interp.sc-missing{position:relative; - background:color-mix(in srgb,currentColor 5%,transparent); - border-color:transparent} - html.sc-dc-streaming .sc-placeholder::before, - html.sc-dc-streaming .sc-interp.sc-missing::before{content:''; - position:absolute;inset:0;pointer-events:none; - background:linear-gradient(90deg,rgba(217,119,87,0) 25%,rgba(247,225,211,.95) 37%,rgba(217,119,87,0) 63%); - background-size:400% 100%;animation:sc-shine 1.4s ease infinite} - html.sc-dc-streaming .sc-placeholder:nth-child(n+9 of .sc-placeholder)::before, - html.sc-dc-streaming .sc-interp.sc-missing:nth-child(n+9 of .sc-interp.sc-missing)::before{animation:none; - background:color-mix(in srgb,currentColor 8%,transparent)} - .sc-placeholder-error{padding:4px 8px;font:11px/1.4 ui-monospace,monospace; - color:color-mix(in srgb,currentColor 70%,transparent);word-break:break-word} - .sc-interp.sc-missing{display:inline-block;width:2em;height:1em;overflow:hidden; - vertical-align:text-bottom;background:rgba(255,255,255,.3);border:1px solid rgba(0,0,0,.5); - border-radius:2px;box-sizing:border-box;color:transparent; - user-select:none} - .sc-interp.sc-unresolved{font-family:ui-monospace,monospace;font-size:.85em; - color:color-mix(in srgb,currentColor 50%,transparent); - background:color-mix(in srgb,currentColor 10%,transparent);border-radius:3px; - padding:0 3px} - .sc-host.sc-has-error{position:relative} - .sc-logic-error{position:absolute;top:8px;left:8px;z-index:2147483647;max-width:60ch; - padding:6px 10px;background:#b00020;color:#fff;font:12px/1.4 ui-monospace,monospace; - border-radius:4px;white-space:pre-wrap;pointer-events:none} - /* Mirrors PRINT_BASELINE_CSS in apps/web deck-stage-export.ts \u2014 keep both - in sync until dc-runtime regains a build step. */ - @media print { - @page { margin: 0.5cm; } - figure, table { break-inside: avoid; } - #dc-root, #dc-root > .sc-host { height: auto; } - *, *::before, *::after { - print-color-adjust: exact; -webkit-print-color-adjust: exact; - backdrop-filter: none !important; -webkit-backdrop-filter: none !important; - animation-delay: -99s !important; animation-duration: .001s !important; - animation-iteration-count: 1 !important; animation-fill-mode: both !important; - animation-play-state: running !important; transition-duration: 0s !important; - } - } - `; - var FULL_PAGE_CSS = "html,body{height:100%;margin:0}#dc-root,#dc-root>.sc-host{height:100%}"; - function rootNameForDocument(doc, loc) { - let bootPath = loc.pathname || ""; - if (!/\.dc\.html?$/i.test(safeDecode(bootPath))) { - try { - bootPath = new URL(doc.baseURI || "/").pathname; - } catch { - } - } - return dcNameFromPath(bootPath); - } - function safeDecode(s) { - try { - return decodeURIComponent(s); - } catch { - return s; - } - } - function boot(runtime, doc = document) { - const parsed = parseDcDocument(doc); - if (!parsed) return null; - const React = getReact(); - const rootName = rootNameForDocument(doc, location); - runtime.markFetched(rootName); - runtime.setRootName(rootName); - runtime.adoptParsed(rootName, parsed); - if (!window.__resources) { - fetch(location.href).then((res) => res.ok ? res.text() : "").then((t) => { - const raw = t ? parseDcText(t) : null; - if (raw?.template) runtime.updateHtml(rootName, raw.template); - }).catch(() => { - }); - } - const dc = doc.querySelector("x-dc"); - const hostEl = doc.createElement("div"); - hostEl.id = "dc-root"; - dc.replaceWith(hostEl); - if (!parsed.preview) { - const s = doc.createElement("style"); - s.textContent = FULL_PAGE_CSS; - doc.head.appendChild(s); - } - const Root = runtime.getDC(rootName); - const entry = runtime.registry.get(rootName); - function StandaloneRoot() { - const [, setTick] = React.useState(0); - React.useEffect(() => { - const sub = () => setTick((n) => n + 1); - entry.subs.add(sub); - return () => { - entry.subs.delete(sub); - }; - }, []); - const defaults = React.useMemo(() => { - const d = {}; - for (const k in entry.propsMeta || {}) { - const v = entry.propsMeta?.[k]?.default; - if (v !== void 0) d[k] = v; - } - return d; - }, [entry.propsMeta]); - return h(Root, { ...defaults, ...entry.propOverrides || {} }); - } - const ReactDOM = getReactDOM(); - if (ReactDOM.createRoot) - ReactDOM.createRoot(hostEl).render(h(StandaloneRoot)); - else ReactDOM.render(h(StandaloneRoot), hostEl); - return rootName; - } - - // src/expr.ts - var IDENT_RE = /^[A-Za-z_$][A-Za-z0-9_$]*/; - var NUMBER_RE = /^-?\d+(\.\d+)?$/; - function resolve(vals, src) { - const expr = String(src).trim(); - if (!expr) return void 0; - if (expr[0] === "(" && expr[expr.length - 1] === ")" && parensWrapWhole(expr)) { - return resolve(vals, expr.slice(1, -1)); - } - const eq = findTopLevelEquality(expr); - if (eq) { - const lv = resolve(vals, expr.slice(0, eq.index)); - const rv = resolve(vals, expr.slice(eq.index + eq.op.length)); - switch (eq.op) { - case "===": - return lv === rv; - case "!==": - return lv !== rv; - case "==": - return lv == rv; - default: - return lv != rv; - } - } - if (expr[0] === "!") return !resolve(vals, expr.slice(1)); - if (expr === "true") return true; - if (expr === "false") return false; - if (expr === "null") return null; - if (expr === "undefined") return void 0; - if (NUMBER_RE.test(expr)) return Number(expr); - if (expr.length >= 2 && (expr[0] === '"' || expr[0] === "'") && expr[expr.length - 1] === expr[0]) { - return expr.slice(1, -1); - } - return resolvePath(vals, expr); - } - function parensWrapWhole(expr) { - let depth = 0; - for (let i = 0; i < expr.length - 1; i++) { - if (expr[i] === "(") depth++; - else if (expr[i] === ")") { - depth--; - if (depth === 0) return false; - } - } - return true; - } - function findTopLevelEquality(expr) { - let depth = 0; - for (let i = 0; i < expr.length; i++) { - const c = expr[i]; - if (c === "[" || c === "(") depth++; - else if (c === "]" || c === ")") depth--; - else if (depth === 0 && (c === "=" || c === "!") && expr[i + 1] === "=") { - if (i > 0 && (expr[i - 1] === "=" || expr[i - 1] === "!")) continue; - if (!expr.slice(0, i).trim()) continue; - const op = expr[i + 2] === "=" ? c + "==" : c + "="; - return { index: i, op }; - } - } - return null; - } - function resolvePath(vals, expr) { - const head = expr.match(IDENT_RE); - if (!head) return void 0; - let cur = vals == null ? void 0 : vals[head[0]]; - let i = head[0].length; - while (i < expr.length) { - if (expr[i] === ".") { - const m = expr.slice(i + 1).match(IDENT_RE) || expr.slice(i + 1).match(/^\d+/); - if (!m) return void 0; - cur = cur == null ? void 0 : cur[m[0]]; - i += 1 + m[0].length; - } else if (expr[i] === "[") { - let depth = 1; - let j = i + 1; - while (j < expr.length && depth > 0) { - if (expr[j] === "[") depth++; - else if (expr[j] === "]") { - depth--; - if (depth === 0) break; - } - j++; - } - if (depth !== 0) return void 0; - const key = resolve(vals, expr.slice(i + 1, j)); - cur = cur == null ? void 0 : cur[key]; - i = j + 1; - } else { - return void 0; - } - } - return cur; - } - - // src/encode.ts - var CAMEL_ATTR = "sc-camel-"; - var INLINE_TEXT_TAGS = new Set( - "a abbr b bdi bdo br cite code del dfn em i ins kbd mark q s samp small span strike strong sub sup u var wbr".split( - " " - ) - ); - var RAW_WRAP = { - select: "sc-raw-select", - table: "sc-raw-table", - tbody: "sc-raw-tbody", - thead: "sc-raw-thead", - tfoot: "sc-raw-tfoot", - tr: "sc-raw-tr", - td: "sc-raw-td", - th: "sc-raw-th", - caption: "sc-raw-caption" - }; - var RAW_UNWRAP = Object.fromEntries( - Object.entries(RAW_WRAP).map(([k, v]) => [v, k]) - ); - var EVENT_MAP = { - onclick: "onClick", - onchange: "onChange", - oninput: "onInput", - onsubmit: "onSubmit", - onkeydown: "onKeyDown", - onkeyup: "onKeyUp", - onkeypress: "onKeyPress", - onmousedown: "onMouseDown", - onmouseup: "onMouseUp", - onmouseenter: "onMouseEnter", - onmouseleave: "onMouseLeave", - onfocus: "onFocus", - onblur: "onBlur", - ondoubleclick: "onDoubleClick", - oncontextmenu: "onContextMenu", - onmousemove: "onMouseMove", - onmouseover: "onMouseOver", - onmouseout: "onMouseOut", - onpointerdown: "onPointerDown", - onpointerup: "onPointerUp", - onpointermove: "onPointerMove", - onpointerenter: "onPointerEnter", - onpointerleave: "onPointerLeave", - onpointercancel: "onPointerCancel", - onpointerover: "onPointerOver", - onpointerout: "onPointerOut", - ongotpointercapture: "onGotPointerCapture", - onlostpointercapture: "onLostPointerCapture", - ontouchstart: "onTouchStart", - ontouchend: "onTouchEnd", - ontouchmove: "onTouchMove", - ontouchcancel: "onTouchCancel", - ondragstart: "onDragStart", - ondragend: "onDragEnd", - ondragenter: "onDragEnter", - ondragleave: "onDragLeave", - ondragover: "onDragOver", - onanimationstart: "onAnimationStart", - onanimationend: "onAnimationEnd", - onanimationiteration: "onAnimationIteration", - ontransitionend: "onTransitionEnd" - }; - var ATTRS = `(?:[^>"']|"[^"]*"|'[^']*')*`; - var IMPORT_SELF_CLOSE_RE = new RegExp( - "<(x-import|dc-import)(" + ATTRS + ")/>", - "gi" - ); - var CAMEL_ATTR_RE = /(\s)([a-z]+[A-Z][A-Za-z0-9]*)(\s*=)/g; - function encodeCamelAttrs(html) { - return html.replace( - CAMEL_ATTR_RE, - (_, sp, name, eq) => sp + CAMEL_ATTR + name.replace(/[A-Z]/g, (c) => "-" + c.toLowerCase()) + eq - ); - } - function encodeCase(html) { - html = html.replace( - IMPORT_SELF_CLOSE_RE, - (_, t, a) => "<" + t + a + ">" - ); - html = html.replace(/)/gi, "/gi, ""); - html = encodeCamelAttrs(html); - for (const [real, alias] of Object.entries(RAW_WRAP)) { - html = html.replace( - new RegExp("(])", "gi"), - "$1" + alias - ); - } - return html; - } - function kebabToCamel(s) { - return s.replace(/-([a-z])/g, (_, c) => c.toUpperCase()); - } - function cssToObj(css) { - const o = {}; - for (const decl of css.split(";")) { - const i = decl.indexOf(":"); - if (i < 0) continue; - const prop = decl.slice(0, i).trim(); - o[prop.startsWith("--") ? prop : kebabToCamel(prop)] = decl.slice(i + 1).trim(); - } - return o; - } - function compileAttr(raw) { - const whole = raw.match(/^\s*\{\{([\s\S]+?)\}\}\s*$/); - if (whole) { - const path = whole[1]; - return (vals) => resolve(vals, path); - } - if (raw.includes("{{")) { - const parts = raw.split(/\{\{([\s\S]+?)\}\}/g); - return (vals) => parts.map((s, i) => i & 1 ? resolve(vals, s) ?? "" : s).join(""); - } - return () => raw; - } - - // src/compile.ts - function collectProps(node, kind, host) { - const propGetters = []; - const pseudoClasses = []; - let hintSize = null; - for (const { name, value } of [...node.attributes]) { - if (name === "sc-name" || name === "data-dc-tpl") continue; - let key = name; - if (key.startsWith(CAMEL_ATTR)) - key = kebabToCamel(key.slice(CAMEL_ATTR.length)); - if (key === "hint-size") { - hintSize = value; - continue; - } - if (key.startsWith("style-")) { - pseudoClasses.push(host.pseudoClass(key.slice(6), value)); - continue; - } - if (kind !== "dom") { - if (key.includes("-") && !(kind === "x-import" && (key.startsWith("aria-") || key.startsWith("data-")))) - key = kebabToCamel(key); - } else { - if (key === "class") key = "className"; - else if (key === "for") key = "htmlFor"; - else if (key.startsWith("on")) - key = EVENT_MAP[key] || "on" + key[2].toUpperCase() + key.slice(3); - } - propGetters.push([key, compileAttr(value)]); - } - return { propGetters, pseudoClasses, hintSize }; - } - var HOST_STYLE_PROPS = /* @__PURE__ */ new Set([ - "position", - "left", - "right", - "top", - "bottom", - "inset", - "width", - "height", - "z-index", - "transform" - ]); - function hostPositionStyle(style) { - const all = typeof style === "string" ? cssToObj(style) : style != null && typeof style === "object" ? style : null; - if (!all) return void 0; - const out = {}; - for (const [k, v] of Object.entries(all)) { - const kebab = k.replace(/[A-Z]/g, (c) => "-" + c.toLowerCase()); - if (HOST_STYLE_PROPS.has(kebab)) out[k] = v; - } - return Object.keys(out).length ? out : void 0; - } - function compileTemplate(html, host) { - const tpl = document.createElement("template"); - //! nosemgrep: direct-inner-html-assignment - tpl.innerHTML = encodeCase(html); - let tplN = 0; - (function stamp(node) { - if (node.nodeType === Node.ELEMENT_NODE) { - node.setAttribute("data-dc-tpl", String(tplN++)); - } - for (const c of node.childNodes) stamp(c); - })(tpl.content); - const builders = walkChildren(tpl.content, host); - const render = ((vals, ctx) => builders.map((b, i) => b(vals || {}, ctx, i))); - render.__annotated = tpl.innerHTML; - return render; - } - function walkChildren(node, host) { - return [...node.childNodes].map((c) => walk(c, host)).filter((b) => b != null); - } - var SLIDE_ID_VALUE_RE = /^[0-9a-f]{8}$/; - var DECK_CONTROL_FLOW_RE = /^(sc-if|sc-for|sc-else|dc-import|x-import)$/; - var DECK_AUX_RE = /^(template|script|style|sc-helmet|helmet)$/; - function isDeckMountTag(el) { - if (el.localName === "deck-stage") return true; - return el.localName === "x-import" && (el.getAttribute("component-from-global-scope") || "") === "deck-stage"; - } - function walkDeckChildren(el, host) { - const pairs = [...el.childNodes].map((c) => ({ c, b: walk(c, host) })).filter((p) => p.b !== null); - const kids = pairs.map((p) => p.b); - const seen = /* @__PURE__ */ new Set(); - const wsSeen = /* @__PURE__ */ new Map(); - const keys = []; - const nextSlideId = new Array(pairs.length); - { - let upcoming = null; - for (let j = pairs.length - 1; j >= 0; j--) { - const n = pairs[j].c; - if (n.nodeType === Node.ELEMENT_NODE) { - const t = n.localName; - upcoming = !DECK_AUX_RE.test(t) && !DECK_CONTROL_FLOW_RE.test(t) ? n.getAttribute("data-om-slide-id") : null; - } - nextSlideId[j] = upcoming; - } - } - for (let j = 0; j < pairs.length; j++) { - const { c } = pairs[j]; - if (c.nodeType === Node.TEXT_NODE) { - if ((c.nodeValue ?? "").trim() === "") { - const base = nextSlideId[j] ? "omid-ws:" + nextSlideId[j] : "omid-ws:aux"; - const n = wsSeen.get(base) ?? 0; - wsSeen.set(base, n + 1); - keys.push(n === 0 ? base : base + ":" + n); - continue; - } - return { kids, keys: null }; - } - if (c.nodeType !== Node.ELEMENT_NODE) { - keys.push(j); - continue; - } - const child = c; - const tag = child.localName; - if (DECK_AUX_RE.test(tag)) { - keys.push(j); - continue; - } - if (DECK_CONTROL_FLOW_RE.test(tag)) return { kids, keys: null }; - const v = child.getAttribute("data-om-slide-id"); - if (!v || !SLIDE_ID_VALUE_RE.test(v) || seen.has(v)) { - return { kids, keys: null }; - } - seen.add(v); - keys.push("omid:" + v); - } - return { kids, keys }; - } - function renderDeckKids(kids, kidKeys, vals, ctx) { - return kids.map((b, j) => { - const k = kidKeys ? kidKeys[j] : j; - const out = b(vals, ctx, k); - return kidKeys != null && typeof out === "string" ? h(getReact().Fragment, { key: k }, out) : out; - }); - } - function walk(node, host) { - if (node.nodeType === Node.TEXT_NODE) return walkText(node); - if (node.nodeType !== Node.ELEMENT_NODE) return null; - const el = node; - const tag = el.tagName.toLowerCase(); - if (tag === "sc-for") return walkFor(el, host); - if (tag === "sc-if") return walkIf(el, host); - if (tag === "x-import") return walkXImport(el, host); - if (tag === "sc-helmet") return host.helmet(el); - if (tag === "dc-import") return walkComponent(el, host); - return walkElement(el, host); - } - var warnedHoles = /* @__PURE__ */ new Set(); - function warnUnresolved(ctx, what) { - const key = (ctx?.__name || "?") + "\0" + what; - if (warnedHoles.has(key)) return; - warnedHoles.add(key); - console.warn("[dc-runtime] " + (ctx?.__name || "template") + ": " + what); - } - function walkText(node) { - const txt = node.nodeValue ?? ""; - if (!txt.includes("{{")) { - if (!txt.trim() && !txt.includes(" ")) return null; - return () => txt; - } - const parts = txt.split(/\{\{([\s\S]+?)\}\}/g); - return (vals, ctx, key) => h( - getReact().Fragment, - { key }, - ...parts.map((p, i) => { - if (!(i & 1)) return p; - const v = resolve(vals, p); - if (v === void 0) { - if (!ctx?.__streamingNow) { - if (document.body?.hasAttribute("data-dc-editor-on")) { - return h( - "span", - { key: i, className: "sc-interp sc-unresolved" }, - "{{ " + p.trim() + " }}" - ); - } - warnUnresolved( - ctx, - "{{ " + p.trim() + " }} never resolved \u2014 rendered as empty" - ); - return null; - } - return h( - "span", - { key: i, className: "sc-interp sc-missing" }, - p.trim() - ); - } - if (getReact().isValidElement(v) || Array.isArray(v)) { - return h(getReact().Fragment, { key: i }, v); - } - if (v === null || typeof v === "boolean") return null; - return h("span", { key: i, className: "sc-interp" }, String(v)); - }) - ); - } - function walkFor(el, host) { - const listGet = compileAttr(el.getAttribute("list") || ""); - const asName = el.getAttribute("as") || "item"; - const hintN = parseInt(el.getAttribute("hint-placeholder-count") || "0", 10); - const kids = walkChildren(el, host); - const listSrc = el.getAttribute("list") || ""; - return (vals, ctx, key) => { - let list = listGet(vals); - if (!Array.isArray(list)) { - if (!ctx?.__streamingNow) { - if (list !== void 0 && list !== null) { - warnUnresolved( - ctx, - 'sc-for list="' + listSrc + '" is not an array (' + typeof list + ")" - ); - } - list = []; - } else { - list = hintN > 0 ? Array(hintN).fill(void 0) : []; - } - } - return h( - getReact().Fragment, - { key }, - list.map((item, i) => { - const sub = { ...vals, [asName]: item, $index: i }; - return h( - getReact().Fragment, - { key: i }, - kids.map((b, j) => b(sub, ctx, j)) - ); - }) - ); - }; - } - function walkIf(el, host) { - const valGet = compileAttr(el.getAttribute("value") || ""); - const hintRaw = el.getAttribute("hint-placeholder-val"); - const hintGet = hintRaw != null ? compileAttr(hintRaw) : null; - const kids = walkChildren(el, host); - return (vals, ctx, key) => { - let v = valGet(vals); - if (v === void 0 && hintGet && ctx?.__streamingNow) v = hintGet(vals); - return v ? h( - getReact().Fragment, - { key }, - kids.map((b, j) => b(vals, ctx, j)) - ) : null; - }; - } - function walkComponent(el, host) { - const name = el.getAttribute("name") || el.getAttribute("component") || ""; - el.removeAttribute("name"); - el.removeAttribute("component"); - const tplId = el.getAttribute("data-dc-tpl"); - const styleRaw = el.getAttribute("style"); - el.removeAttribute("style"); - const styleGet = styleRaw != null ? compileAttr(styleRaw) : null; - const { propGetters, hintSize } = collectProps(el, "dc-import", host); - const kids = walkChildren(el, host); - return (vals, ctx, key) => { - const props = { - key, - __hintSize: hintSize, - __tplId: tplId, - __hostStyle: styleGet ? hostPositionStyle(styleGet(vals)) : void 0 - }; - for (const [k, g] of propGetters) { - const v = g(vals); - if (k === "dcProps") { - if (v && typeof v === "object") Object.assign(props, v); - continue; - } - props[k] = v; - } - if (kids.length) props.children = kids.map((b, j) => b(vals, ctx, j)); - return h(host.component(name), props); - }; - } - function walkXImport(el, host) { - const globalNameGet = compileAttr( - el.getAttribute("component-from-global-scope") || "" - ); - const exportNameGet = compileAttr( - el.getAttribute("component") || el.getAttribute("name") || "" - ); - const fromRaw = el.getAttribute("from") || (el.getAttribute("component-from-global-scope") ? "" : el.getAttribute("src") || el.getAttribute("import") || ""); - const urls = fromRaw.trim() ? fromRaw.trim().split(/\s+/) : []; - const url = urls.length ? urls[urls.length - 1] : ""; - const kindOf = (u) => /\.(jsx|tsx)(\?|#|$)/i.test(u) ? "jsx" : "js"; - const tplId = el.getAttribute("data-dc-tpl"); - const styleRaw = el.getAttribute("style"); - el.removeAttribute("style"); - const styleGet = styleRaw != null ? compileAttr(styleRaw) : null; - const wrap = tplId != null || styleGet != null; - const { propGetters, hintSize } = collectProps(el, "x-import", host); - const hasContent = el.children.length > 0 || !!(el.textContent || "").trim(); - const deckKeyed = hasContent && isDeckMountTag(el) ? walkDeckChildren(el, host) : null; - const kids = deckKeyed ? deckKeyed.kids : hasContent ? walkChildren(el, host) : []; - const kidKeys = deckKeyed?.keys ?? null; - const urlBindable = fromRaw.includes("{{"); - if (urls.length && !urlBindable) { - let prev; - for (const u of urls) prev = host.loadExternal(kindOf(u), u, prev); - } - const evalName = (g, vals) => { - const v = g(vals); - const s = v == null ? "" : String(v); - return s.includes("{{") ? "" : s; - }; - return (vals, ctx, key) => { - const globalName = evalName(globalNameGet, vals); - const name = globalName || evalName(exportNameGet, vals); - const C = !name || urlBindable ? null : globalName ? host.resolveExternalGlobal(url, globalName) : host.resolveExternal(url, name); - const hostStyle = styleGet ? hostPositionStyle(styleGet(vals)) : void 0; - const wrapper = wrap ? { - key, - className: "sc-host-x", - "data-dc-tpl": tplId, - style: hostStyle || { display: "contents" } - } : null; - if (!C) { - const error = urlBindable ? "x-import `from` cannot contain {{ \u2026 }} \u2014 module URLs are resolved at parse time; use a literal URL" : host.resolveExternalError(url, name); - const ph = host.placeholder({ - key: wrapper ? void 0 : key, - name, - hintSize, - error - }); - return wrapper ? h("div", wrapper, ph) : ph; - } - const props = wrapper ? {} : { key }; - let unresolvedHole = false; - for (const [k, g] of propGetters) { - if (k === "component" || k === "componentFromGlobalScope" || k === "from") { - continue; - } - const v = g(vals); - if (v === void 0) unresolvedHole = true; - if (k === "dcProps") { - if (v && typeof v === "object") Object.assign(props, v); - continue; - } - props[k] = v; - } - if (unresolvedHole && ctx?.__htmlStreamingNow) { - const ph = host.placeholder({ - key: wrapper ? void 0 : key, - name, - hintSize, - error: null - }); - return wrapper ? h("div", wrapper, ph) : ph; - } - if (kids.length) { - props.children = renderDeckKids(kids, kidKeys, vals, ctx); - } - return wrapper ? h("div", wrapper, h(C, props)) : h(C, props); - }; - } - function contentKey(el) { - const clone = el.cloneNode(true); - for (const d of clone.querySelectorAll("*")) { - while (d.attributes.length) d.removeAttribute(d.attributes[0].name); - } - const s = clone.innerHTML; - let h2 = 5381; - for (let i = 0; i < s.length; i++) h2 = (h2 << 5) + h2 + s.charCodeAt(i) | 0; - return s.length + "." + (h2 >>> 0).toString(36); - } - var NEVER_CONTENT_KEYED = new Set( - "script style textarea option title select canvas iframe video audio".split( - " " - ) - ); - var NOT_INLINE_SELECTOR = ":not(" + [...INLINE_TEXT_TAGS].join(",") + ")"; - function walkElement(el, host) { - const realTag = RAW_UNWRAP[el.localName] || el.localName; - const tplId = el.getAttribute("data-dc-tpl"); - const inlineOnly = el.childNodes.length > 0 && !NEVER_CONTENT_KEYED.has(realTag) && el.querySelector(NOT_INLINE_SELECTOR) === null; - const keySuffix = inlineOnly ? "|" + contentKey(el) : ""; - const { propGetters, pseudoClasses } = collectProps(el, "dom", host); - const deckKeyed = isDeckMountTag(el) ? walkDeckChildren(el, host) : null; - const kids = deckKeyed ? deckKeyed.kids : walkChildren(el, host); - const kidKeys = deckKeyed?.keys ?? null; - return (vals, ctx, key) => { - const props = { - key: key + keySuffix, - "data-dc-tpl": tplId - }; - for (const [k, g] of propGetters) { - let v = g(vals); - if (k === "style" && typeof v === "string") v = cssToObj(v); - if ((k === "value" || k === "checked") && v === void 0) { - v = k === "checked" ? false : ""; - } - props[k] = v; - } - if (pseudoClasses.length) { - props.className = [props.className, ...pseudoClasses].filter(Boolean).join(" "); - } - return h(realTag, props, ...renderDeckKids(kids, kidKeys, vals, ctx)); - }; - } - - // src/logic.ts - var StreamableLogic = class { - constructor(props) { - __publicField(this, "props"); - __publicField(this, "state", {}); - /** Back-pointer to the wrapper component, installed after construction. */ - __publicField(this, "__host"); - this.props = props || {}; - } - setState(update, cb) { - this.__host && this.__host.__setLogicState(update, cb); - } - forceUpdate() { - this.__host && this.__host.forceUpdate(); - } - componentDidMount() { - } - componentDidUpdate(_prevProps) { - } - componentWillUnmount() { - } - /** The flat object the template renders against (merged over props). */ - renderVals() { - return {}; - } - }; - function evalDcLogic(src) { - //! nosemgrep: eval-and-function-constructor - const fn = new Function( - "DCLogic", - "StreamableLogic", - "React", - src + '\n;return (typeof Component!=="undefined"&&Component)||undefined;' - ); - return fn(StreamableLogic, StreamableLogic, getReact()); - } - - // src/component.ts - function shallowEqual(a, b) { - if (!b) return false; - const ak = Object.keys(a).filter((k) => k !== "children"); - const bk = Object.keys(b).filter((k) => k !== "children"); - if (ak.length !== bk.length) return false; - for (const k of ak) if (a[k] !== b[k]) return false; - return true; - } - function Placeholder({ - name, - hintSize, - streaming, - error - }) { - const [w, hgt] = (hintSize || "100%,60px").split(","); - return h( - "div", - { - className: "sc-placeholder" + (streaming ? " sc-streaming" : ""), - style: { width: w.trim(), height: hgt && hgt.trim() }, - title: name - }, - error ? h( - "div", - { className: "sc-placeholder-error" }, - (name ? name + ": " : "") + error - ) : null - ); - } - function hintToMin(hint) { - if (!hint) return void 0; - const [w, hgt] = hint.split(","); - return { minWidth: w.trim(), minHeight: hgt && hgt.trim() }; - } - function createComponentFactory(registry, ensureFetched) { - const React = getReact(); - const AncestorContext = React.createContext([]); - class StreamableComponent extends React.Component { - constructor(props) { - super(props); - __publicField(this, "__name"); - __publicField(this, "__sub"); - __publicField(this, "__needsDidMount", false); - /** Snapshot of the registry's streaming flags taken at render time — - * builders read it off the RenderCtx (this) to pick placeholder vs - * render-nothing for unresolved values. */ - __publicField(this, "__streamingNow", false); - __publicField(this, "__htmlStreamingNow", false); - /** When a construct throws, remember the (class, registry.ver, props) - * triple so render-time reconcile doesn't re-attempt it on every parent - * re-render. A registry bump (new class, template, external module - * resolving via bumpAll) changes `ver` and breaks the memo so an - * env-dependent constructor can self-heal. */ - __publicField(this, "__failedLogic", null); - __publicField(this, "__failedUserProps", null); - __publicField(this, "__failedVer", -1); - /** Per-instance constructor error — kept here (not on the registry entry) - * so one instance's successful construct can't hide a sibling's failure, - * and a construct can never wipe an eval error `updateJs` recorded on - * `r.logicError`. */ - __publicField(this, "__ctorError", null); - __publicField(this, "logic"); - this.__name = props.__name; - this.state = { __v: 0, __err: null }; - this.__sub = () => { - if (this.state.__err) this.setState({ __err: null }); - this.forceUpdate(); - }; - this.__makeLogic(registry.get(this.__name).Logic, null); - ensureFetched(this.__name); - } - /** Error-boundary hook: a render crash anywhere in this DC's subtree - * (its own template, an x-import'd component, a child DC without its - * own deeper boundary) lands here instead of unmounting the page. */ - static getDerivedStateFromError(e) { - return { __err: e instanceof Error && e.message ? e.message : String(e) }; - } - componentDidCatch(e, info) { - console.error( - "[dc-runtime] render error in <" + this.__name + ">:", - e, - info?.componentStack || "" - ); - } - /** Instantiate the logic class (or the no-op base) and adopt `prevState` - * over its initial state — used both at mount and on hot-swap. */ - __makeLogic(Logic, prevState) { - const L = Logic || StreamableLogic; - try { - this.logic = new L(this.__userProps()); - this.__failedLogic = null; - this.__failedUserProps = null; - this.__ctorError = null; - } catch (e) { - console.error(e); - this.__failedLogic = Logic; - this.__failedUserProps = this.__userProps(); - this.__failedVer = registry.get(this.__name).ver; - this.__ctorError = this.__name + ": " + (e instanceof Error && e.message ? e.message : String(e)); - this.logic = new StreamableLogic( - this.__userProps() - ); - } - this.logic.__host = this; - if (prevState) - this.logic.state = { ...this.logic.state || {}, ...prevState }; - } - /** The props the author's logic + template see — internal __-prefixed - * wiring stripped. */ - __userProps() { - const { __name, __hintSize, __tplId, __hostStyle, ...rest } = this.props; - return rest; - } - __setLogicState(update, cb) { - const prev = this.logic.state; - const patch = typeof update === "function" ? update(prev) : update; - this.logic.state = { ...prev, ...patch }; - this.setState((s) => ({ __v: s.__v + 1 }), cb); - } - /** Swap the logic instance when the registry's Logic class changed - * (streaming completion, hot reload). State carries over; didMount - * re-fires after the swap commits so refs exist. */ - __reconcileLogic() { - const r = registry.get(this.__name); - const Next = r.Logic; - const Cur = this.logic.constructor; - if (Next === Cur || !Next && Cur === StreamableLogic || Next === this.__failedLogic && r.ver === this.__failedVer && shallowEqual(this.__userProps(), this.__failedUserProps)) { - return; - } - if (!this.__needsDidMount) { - try { - this.logic.componentWillUnmount(); - } catch (e) { - console.error(e); - } - } - this.__makeLogic(Next, this.logic.state); - this.__needsDidMount = true; - } - componentDidMount() { - registry.get(this.__name).subs.add(this.__sub); - try { - this.logic.componentDidMount(); - } catch (e) { - console.error(e); - } - } - componentDidUpdate(prevProps) { - this.logic.props = this.__userProps(); - if (this.__needsDidMount) { - if (this.state.__err || !registry.get(this.__name).tpl) return; - this.__needsDidMount = false; - try { - this.logic.componentDidMount(); - } catch (e) { - console.error(e); - } - } else { - try { - this.logic.componentDidUpdate(prevProps); - } catch (e) { - console.error(e); - } - } - } - componentWillUnmount() { - registry.get(this.__name).subs.delete(this.__sub); - if (!this.__needsDidMount) { - try { - this.logic.componentWillUnmount(); - } catch (e) { - console.error(e); - } - } - } - render() { - const r = registry.get(this.__name); - const cls = "sc-host" + (r.htmlStreaming ? " sc-streaming-html" : "") + (r.jsStreaming ? " sc-streaming-js" : ""); - const hintStyle = r.htmlStreaming ? hintToMin(this.props.__hintSize) : void 0; - const hostStyle = this.props.__hostStyle || hintStyle ? { ...hintStyle || {}, ...this.props.__hostStyle || {} } : void 0; - const hostBase = { - className: cls, - style: hostStyle, - "data-sc-name": this.__name, - "data-dc-tpl": this.props.__tplId - }; - const chain = Array.isArray(this.context) ? this.context : []; - if (chain.includes(this.__name)) { - const cycle = [ - ...chain.slice(chain.indexOf(this.__name)), - this.__name - ].join(" \u2192 "); - return h( - "div", - { ...hostBase, className: cls + " sc-has-error" }, - h(Placeholder, { - name: this.__name, - hintSize: this.props.__hintSize, - error: "circular import: " + cycle - }) - ); - } - if (this.state.__err) { - return h( - "div", - { ...hostBase, className: cls + " sc-has-error" }, - h( - "div", - { className: "sc-logic-error", "data-omelette-chrome": "" }, - this.__name + ": " + this.state.__err - ), - h(Placeholder, { - name: this.__name, - hintSize: this.props.__hintSize, - error: this.state.__err - }) - ); - } - this.__reconcileLogic(); - if (!r.tpl) { - return h( - "div", - hostBase, - h(Placeholder, { name: this.__name, hintSize: this.props.__hintSize }) - ); - } - const userProps = this.__userProps(); - this.logic.props = userProps; - let vals = userProps; - let renderErr = r.logicError || this.__ctorError; - try { - vals = { ...userProps, ...this.logic.renderVals() || {} }; - } catch (e) { - console.error(e); - renderErr = this.__name + ".renderVals(): " + (e instanceof Error && e.message ? e.message : String(e)); - } - this.__streamingNow = !!(r.htmlStreaming || r.jsStreaming); - this.__htmlStreamingNow = !!r.htmlStreaming; - return h( - "div", - { ...hostBase, className: cls + (renderErr ? " sc-has-error" : "") }, - renderErr && h( - "div", - { className: "sc-logic-error", "data-omelette-chrome": "" }, - renderErr - ), - h( - AncestorContext.Provider, - { value: [...chain, this.__name] }, - r.tpl(vals, this) - ) - ); - } - } - __publicField(StreamableComponent, "contextType", AncestorContext); - const named = /* @__PURE__ */ new Map(); - function getDC(name) { - const hit = named.get(name); - if (hit) return hit; - function Dispatcher(p) { - const [, setTick] = React.useState(0); - React.useEffect(() => { - const sub = () => setTick((n) => n + 1); - registry.get(name).subs.add(sub); - return () => { - registry.get(name).subs.delete(sub); - }; - }, []); - ensureFetched(name); - return h(StreamableComponent, { ...p, __name: name }); - } - Dispatcher.displayName = name; - named.set(name, Dispatcher); - return Dispatcher; - } - return { - getDC, - StreamableComponent - }; - } - - // src/bundled.ts - function bundledBlob(url) { - const blobs = window.__resourceBlobs; - const b = blobs ? blobs[url.split("#")[0]] : void 0; - return b instanceof Blob ? b : null; - } - - // src/cdn.ts - var REACT_URL = "https://unpkg.com/react@18.3.1/umd/react.production.min.js"; - var REACT_SRI = "sha384-DGyLxAyjq0f9SPpVevD6IgztCFlnMF6oW/XQGmfe+IsZ8TqEiDrcHkMLKI6fiB/Z"; - var REACT_DOM_URL = "https://unpkg.com/react-dom@18.3.1/umd/react-dom.production.min.js"; - var REACT_DOM_SRI = "sha384-gTGxhz21lVGYNMcdJOyq01Edg0jhn/c22nsx0kyqP0TxaV5WVdsSH1fSDUf5YJj1"; - var BABEL_URL = "https://unpkg.com/@babel/standalone@7.29.0/babel.min.js"; - var BABEL_SRI = "sha384-m08KidiNqLdpJqLq95G/LEi8Qvjl/xUYll3QILypMoQ65QorJ9Lvtp2RXYGBFj1y"; - function cdnScriptFor(url, sri) { - const res = window.__resources; - const v = res ? res[url] : void 0; - return typeof v === "string" && v ? { src: v } : { src: url, integrity: sri }; - } - - // src/external.ts - var isCustomElementName = (n) => !n.includes(".") && n.includes("-"); - function isRenderableType(g) { - if (typeof g === "function") return !isElementClass(g); - return typeof g === "object" && g !== null && typeof g.$$typeof === "symbol"; - } - function resolveDottedPath(root, name) { - let cur = root; - for (const seg of name.split(".")) { - if (cur == null) return void 0; - cur = cur[seg]; - } - return cur; - } - var GLOBAL_POLL_INTERVAL_MS = 50; - var GLOBAL_POLL_TIMEOUT_MS = 3e4; - function createExternalModules(onResolved) { - const cache = /* @__PURE__ */ new Map(); - let babelLoading = null; - const reportedMissing = /* @__PURE__ */ new Map(); - const polling = /* @__PURE__ */ new Set(); - function ensureBabel() { - if (window.Babel) return Promise.resolve(); - if (babelLoading) return babelLoading; - const babel = cdnScriptFor(BABEL_URL, BABEL_SRI); - babelLoading = new Promise((res, rej) => { - const s = document.createElement("script"); - s.src = babel.src; - if (babel.integrity) { - s.integrity = babel.integrity; - s.crossOrigin = "anonymous"; - } - s.onload = () => res(); - s.onerror = rej; - document.head.appendChild(s); - }); - return babelLoading; - } - const pending = /* @__PURE__ */ new Map(); - function load(kind, url, after) { - const existing = pending.get(url); - if (existing) return existing; - cache.set(url, null); - console.info("[dc-runtime] x-import: loading", url, "(" + kind + ")"); - const ready = Promise.all([ - kind === "jsx" ? ensureBabel() : Promise.resolve(), - after ?? Promise.resolve() - ]); - const p = ready.then(() => { - const pre = bundledBlob(url); - if (pre) return pre.text(); - return fetch(url).then((r) => { - if (!r.ok) throw new Error("HTTP " + r.status); - return r.text(); - }); - }).then((src) => { - const code = kind === "jsx" ? window.Babel.transform(src, { - filename: url, - presets: ["react", "typescript"] - }).code : src; - const module = { exports: {} }; - const before = new Set(Object.keys(window)); - //! nosemgrep: eval-and-function-constructor - new Function("React", "module", "exports", "require", code)( - getReact(), - module, - module.exports, - () => ({}) - ); - const globals = {}; - for (const k of Object.keys(window)) { - if (!before.has(k) && typeof window[k] === "function") { - globals[k] = window[k]; - } - } - cache.set(url, { mod: module.exports, globals }); - console.info( - "[dc-runtime] x-import: loaded", - url, - "\u2014 exports:", - Object.keys(module.exports), - "window globals:", - Object.keys(globals) - ); - onResolved(); - }).catch((e) => { - cache.set(url, { - mod: {}, - globals: {}, - error: "failed to load: " + (e instanceof Error && e.message ? e.message : String(e)) - }); - console.error( - "[dc-runtime] x-import: FAILED to load", - url, - "(" + kind + ")", - e - ); - onResolved(); - }); - pending.set(url, p); - return p; - } - function resolve2(url, name) { - const entry = cache.get(url); - if (!entry) return null; - const { mod, globals } = entry; - const C = mod && mod[name] || globals && globals[name] || typeof window !== "undefined" && window[name] || mod && mod.default; - if (typeof C === "function") return C; - const key = url + "\0" + name; - if (!reportedMissing.has(key)) { - reportedMissing.set( - key, - entry.error || 'no export named "' + name + '" (has: ' + Object.keys(mod).join(", ") + ")" - ); - console.error( - "[dc-runtime] x-import: module", - url, - "loaded but has no component named", - JSON.stringify(name), - "\u2014 available exports:", - Object.keys(mod), - "window globals:", - Object.keys(globals), - ". The module must `module.exports = {" + name + "}` or set `window." + name + "`." - ); - } - return null; - } - function waitForGlobal(name) { - if (polling.has(name)) return; - polling.add(name); - const started = Date.now(); - const isCE = isCustomElementName(name); - const tick = () => { - const found = isCE ? customElements.get(name) : isRenderableType(resolveDottedPath(window, name)); - if (found) { - polling.delete(name); - onResolved(); - return; - } - if (Date.now() - started >= GLOBAL_POLL_TIMEOUT_MS) { - console.warn( - "[dc-runtime] x-import: global", - JSON.stringify(name), - "never appeared on window after " + GLOBAL_POLL_TIMEOUT_MS + "ms" - ); - return; - } - setTimeout(tick, GLOBAL_POLL_INTERVAL_MS); - }; - setTimeout(tick, GLOBAL_POLL_INTERVAL_MS); - } - function resolveGlobal(url, name) { - const isCE = isCustomElementName(name); - if (!url) { - if (isCE) { - if (customElements.get(name)) return name; - waitForGlobal(name); - return null; - } - const g2 = resolveDottedPath(window, name); - if (isRenderableType(g2)) return g2; - waitForGlobal(name); - return null; - } - const entry = cache.get(url); - if (!entry) return null; - if (isCE && customElements.get(name)) return name; - const g = entry.globals[name] ?? resolveDottedPath(window, name); - if (isRenderableType(g)) return g; - if (name.includes(".")) return null; - const key = url + "\0global\0" + name; - if (!reportedMissing.has(key)) { - reportedMissing.set(key, null); - if (isCE && !customElements.get(name)) { - console.warn( - "[dc-runtime] x-import:", - url, - "loaded but no custom element", - JSON.stringify(name), - "is registered and window." + name + " is not a function \u2014 rendering <" + name + "> as an unknown element." - ); - } - } - return name; - } - function getError(url, name) { - const entry = cache.get(url); - if (entry?.error) return entry.error; - return reportedMissing.get(url + "\0" + name) || null; - } - return { load, resolve: resolve2, resolveGlobal, getError }; - } - function isElementClass(g) { - try { - return typeof g === "function" && typeof HTMLElement !== "undefined" && g.prototype instanceof HTMLElement; - } catch { - return false; - } - } - - // src/atomics.ts - var ATOMIC_CSS = ( - // layout - ".fx{display:flex}.col{display:flex;flex-direction:column}.grid{display:grid}.ac{align-items:center}.jc{justify-content:center}.jb{justify-content:space-between}.f1{flex:1}.noshrink{flex-shrink:0}.wrap{flex-wrap:wrap}.fw5{font-weight:500}.fw6{font-weight:600}.fw7{font-weight:700}.fw8{font-weight:800}.fs11{font-size:11px}.fs12{font-size:12px}.fs13{font-size:13px}.fs14{font-size:14px}.fs15{font-size:15px}.fs16{font-size:16px}.fs20{font-size:20px}.fs22{font-size:22px}.upper{text-transform:uppercase}.tc{text-align:center}.nowrap{white-space:nowrap}.gap8{gap:8px}.gap10{gap:10px}.gap12{gap:12px}.gap16{gap:16px}.gap24{gap:24px}.m0{margin:0}.mt8{margin-top:8px}.mt12{margin-top:12px}.mt16{margin-top:16px}.mb8{margin-bottom:8px}.mb12{margin-bottom:12px}.mb16{margin-bottom:16px}.posrel{position:relative}.posabs{position:absolute}.round{border-radius:50%}.ohide{overflow:hidden}.bbox{box-sizing:border-box}.pointer{cursor:pointer}.w100{width:100%}.b0{border:none}" - ); - - // src/helmet.ts - var DESIGN_DOC_MODE_RE = /]*\bname\s*=\s*["']design_doc_mode["'][^>]*\b(?:content|value)\s*=\s*["'](\w+)["']/i; - var CANVAS_BG_LIGHT = "#f0eee6"; - var CANVAS_BG_DARK = "#2e2c26"; - function createHelmetManager(doc, isStreaming) { - const mounted = /* @__PURE__ */ new Set(); - const live = /* @__PURE__ */ new Map(); - let designDocMode = null; - let canvasStyleEl = null; - let appTheme = "light"; - try { - const ds = doc.documentElement.dataset.theme; - appTheme = ds === "dark" || ds === "light" ? ds : new URLSearchParams(doc.defaultView?.location.search ?? "").get( - "theme" - ) === "dark" ? "dark" : "light"; - } catch { - } - function applyCanvasBg() { - if (!canvasStyleEl) return; - const bg = appTheme === "dark" ? CANVAS_BG_DARK : CANVAS_BG_LIGHT; - canvasStyleEl.textContent = `html,body{background:${bg}}#dc-root>.sc-host{position:relative}`; - } - function postDesignMode(mode) { - if (window.parent === window) return; - try { - window.parent.postMessage({ type: "__dc_design_mode", mode }, "*"); - } catch { - } - } - function setDesignDocMode(mode) { - if (mode === designDocMode) return; - designDocMode = mode; - postDesignMode(mode); - if (mode === "canvas") { - doc.documentElement.setAttribute("data-dc-canvas", ""); - canvasStyleEl = doc.createElement("style"); - canvasStyleEl.setAttribute("data-dc-canvas", ""); - applyCanvasBg(); - doc.head.appendChild(canvasStyleEl); - } else { - doc.documentElement.removeAttribute("data-dc-canvas"); - canvasStyleEl?.remove(); - canvasStyleEl = null; - } - } - window.addEventListener("message", (e) => { - const type = e.data && e.data.type; - if (type === "__dc_theme") { - const t = e.data.theme; - if (t === "light" || t === "dark") { - appTheme = t; - applyCanvasBg(); - } - return; - } - if (!designDocMode || type !== "__dc_probe") return; - postDesignMode(designDocMode); - }); - function compile(node) { - const raw = [...node.children]; - const helmetClosed = node.nextSibling != null || node.parentNode?.nextSibling != null; - if (node.hasAttribute("data-dc-atomics") && !mounted.has("__dc-atomics")) { - mounted.add("__dc-atomics"); - const el = doc.createElement("style"); - el.id = "__dc-atomics"; - el.textContent = ATOMIC_CSS; - doc.head.appendChild(el); - } - return (_vals, ctx) => { - const name = ctx && ctx.__name || ""; - const streaming = !!(name && isStreaming(name)); - for (let i = 0; i < raw.length; i++) { - const child = raw[i]; - const tag = child.tagName; - const mayBePartial = streaming && !helmetClosed && i === raw.length - 1; - if (tag === "SCRIPT") { - if (mayBePartial) continue; - const key = "SCRIPT|" + (child.getAttribute("src") || child.textContent || ""); - if (mounted.has(key)) continue; - mounted.add(key); - const el = doc.createElement("script"); - for (const { name: an, value } of [...child.attributes]) - el.setAttribute(an, value); - if (child.textContent) el.textContent = child.textContent; - doc.head.appendChild(el); - } else if (tag === "LINK" || tag === "META") { - if (mayBePartial) continue; - const key = tag + "|" + (child.getAttribute("href") || child.getAttribute("src") || child.outerHTML); - if (mounted.has(key)) continue; - mounted.add(key); - if (tag === "LINK") { - const rel = (child.getAttribute("rel") || "").toLowerCase().split(/\s+/); - const href = (child.getAttribute("href") || "").trim(); - const res = window.__resources; - const pre = res && rel.includes("stylesheet") && !rel.includes("alternate") ? res[href] : void 0; - const blob = typeof pre === "string" && pre ? bundledBlob(pre) : null; - if (blob) { - const el = doc.createElement("style"); - if (child.hasAttribute("disabled")) { - el.setAttribute("media", "not all"); - } else if (child.getAttribute("media")) { - el.setAttribute("media", child.getAttribute("media")); - } - if (child.getAttribute("title")) - el.setAttribute("title", child.getAttribute("title")); - void blob.text().then((css) => { - el.textContent = css; - }); - doc.head.appendChild(el); - continue; - } - } - doc.head.appendChild(child.cloneNode(true)); - } else { - const key = name + "|" + i; - let el = live.get(key); - if (!el || el.tagName !== tag) { - if (el) el.remove(); - el = doc.createElement(tag.toLowerCase()); - live.set(key, el); - doc.head.appendChild(el); - } - for (const { name: an, value } of [...child.attributes]) { - if (el.getAttribute(an) !== value) el.setAttribute(an, value); - } - if (el.textContent !== child.textContent) - el.textContent = child.textContent; - } - } - return null; - }; - } - return { compile, setDesignDocMode }; - } - - // src/pseudo.ts - function scanUnquotedUrl(css, i) { - if (css[i] !== "u" && css[i] !== "U" || css.slice(i, i + 4).toLowerCase() !== "url(" || /[a-z0-9_-]/i.test(css[i - 1] ?? "")) { - return -1; - } - let j = i + 4; - while (j < css.length && /\s/.test(css[j])) j++; - if (css[j] === '"' || css[j] === "'") return -1; - while (j < css.length && css[j] !== ")") { - if (css[j] === "\\") j++; - j++; - } - return j < css.length ? j + 1 : css.length; - } - function stripComments(css) { - let out = ""; - let quote = ""; - for (let i = 0; i < css.length; i++) { - const c = css[i]; - if (quote) { - if (c === "\\") { - out += c + (css[i + 1] ?? ""); - i++; - continue; - } - if (c === quote) quote = ""; - out += c; - } else if (c === "'" || c === '"') { - quote = c; - out += c; - } else if (c === "/" && css[i + 1] === "*") { - const end = css.indexOf("*/", i + 2); - i = end === -1 ? css.length : end + 1; - out += " "; - } else { - const end = scanUnquotedUrl(css, i); - if (end === -1) out += c; - else { - out += css.slice(i, end); - i = end - 1; - } - } - } - return out; - } - function importantify(css) { - css = stripComments(css); - const decls = []; - let start = 0; - let depth = 0; - let quote = ""; - for (let i = 0; i < css.length; i++) { - const c = css[i]; - if (quote) { - if (c === "\\") i++; - else if (c === quote) quote = ""; - } else if (c === "'" || c === '"') quote = c; - else if (c === "(") depth++; - else if (c === ")") depth = Math.max(0, depth - 1); - else if (c === ";" && depth === 0) { - decls.push(css.slice(start, i)); - start = i + 1; - } else { - const end = scanUnquotedUrl(css, i); - if (end !== -1) i = end - 1; - } - } - decls.push(css.slice(start)); - return decls.map((d) => d.trim()).filter(Boolean).map((d) => /!\s*important$/i.test(d) ? d : d + " !important").join(";"); - } - function createPseudoSheet(doc) { - let el = null; - const cache = /* @__PURE__ */ new Map(); - let n = 0; - return (pseudo, css) => { - const k = pseudo + "|" + css; - const hit = cache.get(k); - if (hit) return hit; - if (!el) { - el = doc.createElement("style"); - doc.head.appendChild(el); - } - const cls = "scp" + (n++).toString(36); - const isPseudoElement = pseudo === "before" || pseudo === "after"; - const sel = isPseudoElement ? "." + cls + "::" + pseudo : "." + cls + ":" + pseudo; - el.sheet.insertRule( - sel + "{" + (isPseudoElement ? css : importantify(css)) + "}", - el.sheet.cssRules.length - ); - cache.set(k, cls); - return cls; - }; - } - - // src/registry.ts - function createRegistry() { - const entries = /* @__PURE__ */ Object.create(null); - function get(name) { - return entries[name] || (entries[name] = { - html: "", - tpl: null, - Logic: null, - jsStreaming: false, - htmlStreaming: false, - ver: 0, - subs: /* @__PURE__ */ new Set(), - fetched: false - }); - } - function bump(name) { - const r = get(name); - r.ver++; - for (const fn of r.subs) fn(); - } - return { - entries, - get, - bump, - bumpAll() { - for (const n in entries) bump(n); - } - }; - } - - // src/runtime.ts - var COMPONENT_DIR = "."; - function createRuntime(doc = document) { - const registry = createRegistry(); - const pseudoClass = createPseudoSheet(doc); - const helmet = createHelmetManager( - doc, - (name) => registry.get(name).htmlStreaming - ); - const external = createExternalModules(() => registry.bumpAll()); - const factory = createComponentFactory(registry, ensureFetched); - const host = { - component: (name) => factory.getDC(name), - placeholder: (props) => h(Placeholder, props), - helmet: (node) => helmet.compile(node), - loadExternal: (kind, url, after) => external.load(kind, url, after), - resolveExternal: (url, name) => external.resolve(url, name), - resolveExternalGlobal: (url, name) => external.resolveGlobal(url, name), - resolveExternalError: (url, name) => external.getError(url, name), - pseudoClass - }; - function ensureFetched(name) { - const r = registry.get(name); - if (r.fetched) return; - r.fetched = true; - const url = COMPONENT_DIR + "/" + encodeURIComponent(name) + ".dc.html"; - const res = window.__resources; - const pre = res ? res[url] : void 0; - const target = typeof pre === "string" && pre ? pre : url; - const blob = bundledBlob(target); - (blob ? blob.text() : fetch(target).then((res2) => { - if (!res2.ok) { - console.error( - '[dc-runtime] sibling fetch for "' + name + '" failed:', - url, - "returned", - res2.status, - "\u2014 the reference renders as an empty placeholder." - ); - return ""; - } - return res2.text(); - })).then((t) => { - if (!t) return; - const parsed = parseDcText(t); - if (!parsed) { - console.error( - '[dc-runtime] sibling fetch for "' + name + '":', - url, - "has no block \u2014 not a Design Component." - ); - return; - } - if (parsed.props) r.propsMeta = parsed.props; - if (parsed.preview) r.preview = parsed.preview; - if (parsed.template && !r.html) updateHtml(name, parsed.template); - if (parsed.js && !r.Logic) updateJs(name, parsed.js); - }).catch( - (e) => console.error( - '[dc-runtime] sibling fetch for "' + name + '" threw:', - url, - e - ) - ); - } - let rootName = null; - function updateHtml(name, html) { - const r = registry.get(name); - r.html = html; - if (name === rootName) { - const mode = DESIGN_DOC_MODE_RE.exec(html)?.[1] ?? null; - if (mode || !r.htmlStreaming) helmet.setDesignDocMode(mode); - } - try { - r.tpl = compileTemplate(html, host); - } catch (e) { - console.error("[dc-runtime] template compile FAILED for", name, e); - } - registry.bump(name); - } - function updateJs(name, src) { - const r = registry.get(name); - const seq = r.jsSeq = (r.jsSeq || 0) + 1; - try { - const Cls = evalDcLogic(src); - if (r.jsSeq !== seq) return; - if (typeof Cls !== "function") { - r.logicError = name + ".dc.html: