mamuflih13 commited on
Commit
31de45b
·
1 Parent(s): f276253

Add OpenVINO face models, video normalization, and Qwen multimodal model

Browse files
Marquee.html ADDED
@@ -0,0 +1,220 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="utf-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1">
6
+ <title>Marquee — Setup Console & Broadcast</title>
7
+ <link rel="preconnect" href="https://fonts.googleapis.com">
8
+ <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
9
+ <link href="https://fonts.googleapis.com/css2?family=Unbounded:wght@400;500;600;700;800&family=Bebas+Neue&family=Nunito:wght@600;700;800;900&display=swap" rel="stylesheet">
10
+ <link rel="stylesheet" href="marquee.css">
11
+ </head>
12
+ <body>
13
+
14
+ <!-- =======================================================
15
+ ICON SPRITE — line icons, broadcast vernacular
16
+ ======================================================= -->
17
+ <svg width="0" height="0" style="position:absolute" aria-hidden="true">
18
+ <defs>
19
+ <symbol id="i-clap" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.7" stroke-linejoin="round"><path d="M3 8h18v11a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V8Z"/><path d="m3 8 1.6-3.4 3.8 1L6.7 9M9.4 5.6l3.8 1L11.5 10M15 7l3.8 1-1.5 3.4M21 8l-.8-2.2-3.4-.9"/></symbol>
20
+ <symbol id="i-play" viewBox="0 0 24 24" fill="currentColor"><path d="M8 5v14l11-7z"/></symbol>
21
+ <symbol id="i-pause" viewBox="0 0 24 24" fill="currentColor"><rect x="6" y="5" width="4" height="14" rx="1"/><rect x="14" y="5" width="4" height="14" rx="1"/></symbol>
22
+ <symbol id="i-star" viewBox="0 0 24 24" fill="currentColor"><path d="m12 2 2.9 6.3 6.9.8-5.1 4.7 1.4 6.8L12 17.8 5.9 20.6l1.4-6.8L2.2 9.1l6.9-.8z"/></symbol>
23
+ <symbol id="i-spot" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.7" stroke-linecap="round"><path d="M12 3v3M5 6l2 2M19 6l-2 2"/><path d="M9 9h6l3 11H6z" fill="currentColor" opacity=".15"/><path d="M9 9h6l3 11H6z"/></symbol>
24
+ <symbol id="i-crown" viewBox="0 0 24 24" fill="currentColor"><path d="M3 7l4 4 5-7 5 7 4-4-2 12H5z"/></symbol>
25
+ <symbol id="i-bird" viewBox="0 0 24 24" fill="currentColor"><path d="M14 4a3 3 0 0 0-3 3c0 .5-.4 1-1 1.5-1.8 1.3-3 3.4-3 5.8C7 17.9 9.6 20 13 20c.6 0 1-.6.8-1.2C13.3 17 13 16 14 15c2.2-.4 4-2.3 4-4.8V7a3 3 0 0 0-3-3zm.6 3.2a.9.9 0 1 1 0-.1z"/></symbol>
26
+ <symbol id="i-lock" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.7"><rect x="5" y="11" width="14" height="9" rx="2"/><path d="M8 11V8a4 4 0 0 1 8 0v3"/></symbol>
27
+ <symbol id="i-download" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.7" stroke-linecap="round" stroke-linejoin="round"><path d="M12 4v10m0 0 4-4m-4 4-4-4M5 20h14"/></symbol>
28
+ <symbol id="i-mic" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.7" stroke-linecap="round"><rect x="9" y="3" width="6" height="11" rx="3"/><path d="M6 11a6 6 0 0 0 12 0M12 17v4"/></symbol>
29
+ <symbol id="i-list" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.7" stroke-linecap="round"><path d="M8 6h12M8 12h12M8 18h12M4 6h.01M4 12h.01M4 18h.01"/></symbol>
30
+ <symbol id="i-whistle" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.7" stroke-linecap="round"><path d="M3 11a5 5 0 0 0 5 5h4l5 3v-8a3 3 0 0 0-3-3H8a5 5 0 0 0-5 3Z"/><path d="M14 4v3"/></symbol>
31
+ <symbol id="i-glam" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.7" stroke-linecap="round"><path d="M3 14c2-1 4-1 5 0M16 14c2-1 4-1 5 0M3 11h18M7 11l1 5M17 11l-1 5"/><circle cx="6.5" cy="8.5" r="2"/><circle cx="17.5" cy="8.5" r="2"/></symbol>
32
+ <symbol id="i-paw" viewBox="0 0 24 24" fill="currentColor"><circle cx="6" cy="9" r="2"/><circle cx="11" cy="6" r="2"/><circle cx="16" cy="7" r="2"/><circle cx="19" cy="11" r="2"/><path d="M12 12c-3 0-6 2.5-6 5 0 2 2 3 6 3s6-1 6-3c0-2.5-3-5-6-5Z"/></symbol>
33
+ <symbol id="i-glove" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.7" stroke-linejoin="round"><path d="M7 10V6a2 2 0 0 1 4 0v3M11 9V5a2 2 0 0 1 4 0v5l2 1a3 3 0 0 1 1.4 3.4l-.8 3A2 2 0 0 1 15.7 21H10a3 3 0 0 1-2.8-2L5.5 14A2.5 2.5 0 0 1 7 10Z"/></symbol>
34
+ <symbol id="i-cam" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5"><rect x="3" y="6" width="13" height="12" rx="2"/><path d="m16 10 5-3v10l-5-3z"/></symbol>
35
+ <symbol id="i-check" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.4" stroke-linecap="round" stroke-linejoin="round"><path d="m4 12 5 5L20 6"/></symbol>
36
+ <symbol id="i-back" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"><path d="M15 5l-7 7 7 7"/></symbol>
37
+ <symbol id="i-portrait" viewBox="0 0 24 24" fill="currentColor"><circle cx="12" cy="8" r="4.4"/><path d="M3.5 22c0-4.7 3.8-8 8.5-8s8.5 3.3 8.5 8z"/></symbol>
38
+ </defs>
39
+ </svg>
40
+
41
+ <div class="shell">
42
+
43
+ <!-- ============== MASTHEAD ============== -->
44
+ <header class="masthead">
45
+ <div class="brand">
46
+ <div class="sign">
47
+ <div class="bulbs t"><i></i><i></i><i></i><i></i><i></i><i></i><i></i></div>
48
+ <h1 class="disp">MARQUEE</h1>
49
+ <div class="bulbs b"><i></i><i></i><i></i><i></i><i></i><i></i><i></i></div>
50
+ </div>
51
+ <div class="tag">Everyone's a headliner.<small>Turn any clip into a live broadcast.</small></div>
52
+ </div>
53
+ <div class="broadcast-meta">
54
+ <span class="clock" id="clock">--:--:--</span>
55
+ <span class="status" id="status"><span class="led"></span><span id="statusTxt">Standby</span></span>
56
+ </div>
57
+ </header>
58
+
59
+ <!-- ============================================================
60
+ VIEW 1 — SETUP CONSOLE
61
+ ============================================================ -->
62
+ <section class="view active" id="setupView">
63
+
64
+ <!-- rundown / running order -->
65
+ <nav class="rundown" id="rundown" aria-label="Show rundown">
66
+ <div class="cue on" data-step="1"><div class="node">01</div><div class="ttl">Roll Tape</div><div class="sub">Drop a clip</div></div>
67
+ <div class="cue" data-step="2"><div class="node">02</div><div class="ttl">Casting</div><div class="sub">Find the faces</div></div>
68
+ <div class="cue" data-step="3"><div class="node">03</div><div class="ttl">Star Select</div><div class="sub">Name the stars</div></div>
69
+ <div class="cue" data-step="4"><div class="node">04</div><div class="ttl">Showtime</div><div class="sub">Go live</div></div>
70
+ </nav>
71
+
72
+ <div class="console">
73
+
74
+ <!-- ---------- LEFT: the control desk ---------- -->
75
+ <div class="desk">
76
+
77
+ <!-- Roll Tape -->
78
+ <div class="card" id="rollCard">
79
+ <div class="card-h"><svg class="i ic"><use href="#i-clap"/></svg><h2>Roll Tape</h2><span class="meta">SRC 01</span></div>
80
+ <div class="drop" id="drop" tabindex="0" role="button" aria-label="Drop a video clip to roll tape">
81
+ <div class="ask">
82
+ <span class="clap"><svg class="i"><use href="#i-clap"/></svg></span>
83
+ <div class="t">Drop a clip on the desk</div>
84
+ <div class="s">MP4 · MOV · up to 60s · portrait, landscape or square</div>
85
+ </div>
86
+ <div class="loaded">
87
+ <div class="strip"><span class="play"><svg class="i"><use href="#i-play"/></svg></span></div>
88
+ <div>
89
+ <div class="fn" id="fileName">rooftop_freekick.mp4</div>
90
+ <div class="fd"><span id="fileDur">00:42</span> · 1080×1920 · ready to cast</div>
91
+ </div>
92
+ </div>
93
+ </div>
94
+ <input type="file" id="fileInput" accept="video/*" hidden>
95
+ <button class="btn cyan mt" id="castBtn" disabled><svg class="i"><use href="#i-spot"/></svg> Find the stars</button>
96
+ <div class="sweep"></div>
97
+ </div>
98
+
99
+ <!-- Pick the show / vibes -->
100
+ <div class="card" id="vibeCard">
101
+ <div class="card-h"><svg class="i ic"><use href="#i-mic"/></svg><h2>Pick the show</h2><span class="meta">TONE</span></div>
102
+ <div class="vibes" id="vibes" role="group" aria-label="Narration vibe">
103
+ <button class="vibe" data-vibe="football" style="--vc:var(--star-lime)" aria-pressed="false">
104
+ <span class="ic"><svg class="i"><use href="#i-whistle"/></svg></span>
105
+ <span><span class="vt">Football</span><span class="vd">Roaring play-by-play</span></span>
106
+ </button>
107
+ <button class="vibe" data-vibe="diva" style="--vc:var(--star-hot)" aria-pressed="false">
108
+ <span class="ic"><svg class="i"><use href="#i-glam"/></svg></span>
109
+ <span><span class="vt">Diva Hour</span><span class="vd">Red-carpet shade</span></span>
110
+ </button>
111
+ <button class="vibe" data-vibe="wildlife" style="--vc:var(--star-cyan)" aria-pressed="false">
112
+ <span class="ic"><svg class="i"><use href="#i-paw"/></svg></span>
113
+ <span><span class="vt">Wildlife</span><span class="vd">Hushed nature doc</span></span>
114
+ </button>
115
+ <button class="vibe" data-vibe="boxing" style="--vc:var(--star-tang)" aria-pressed="false">
116
+ <span class="ic"><svg class="i"><use href="#i-glove"/></svg></span>
117
+ <span><span class="vt">Boxing</span><span class="vd">Ringside hype</span></span>
118
+ </button>
119
+ </div>
120
+
121
+ <!-- Coco the host — re-dresses per vibe -->
122
+ <div class="coco" id="coco">
123
+ <div class="av">
124
+ <svg class="i bird"><use href="#i-bird"/></svg>
125
+ <span class="acc" id="cocoAcc"><svg><use href="#i-star"/></svg></span>
126
+ </div>
127
+ <div class="say">
128
+ <span class="who">Coco · your host</span>
129
+ <span id="cocoLine">Pick a show and I'll set the mood. I dress for the occasion.</span>
130
+ </div>
131
+ </div>
132
+ </div>
133
+
134
+ <!-- Showtime -->
135
+ <button class="btn show" id="liveBtn" disabled><svg class="i"><use href="#i-star"/></svg> Showtime — go live</button>
136
+
137
+ </div>
138
+
139
+ <!-- ---------- RIGHT: the stage ---------- -->
140
+ <div class="stage-panel" id="stagePanel">
141
+ <div class="stage-h">
142
+ <svg class="i ic"><use href="#i-spot"/></svg>
143
+ <h2>Star Select</h2>
144
+ <span class="count" id="starCount">NO CAST YET</span>
145
+ </div>
146
+
147
+ <!-- empty -->
148
+ <div class="empty" id="stageEmpty">
149
+ <div class="spot"><svg class="i"><use href="#i-spot"/></svg></div>
150
+ <div class="t">The stage is dark</div>
151
+ <div class="s">Roll a clip and hit <b>Find the stars</b> — everyone we spot gets a card under the spotlight.</div>
152
+ </div>
153
+
154
+ <!-- roster (filled by JS) -->
155
+ <div class="roster" id="roster" hidden></div>
156
+ </div>
157
+
158
+ </div>
159
+
160
+ <p class="note">
161
+ <svg class="i lock"><use href="#i-lock"/></svg>
162
+ Your clip stays on <b>your machine</b>. Faces are detected locally and never uploaded — Marquee only writes the captions you see.
163
+ </p>
164
+ </section>
165
+
166
+ <!-- ============================================================
167
+ VIEW 2 — BROADCAST PLAYER
168
+ ============================================================ -->
169
+ <section class="view" id="broadcastView">
170
+ <div class="booth-top">
171
+ <button class="back" id="backBtn"><svg class="i"><use href="#i-back"/></svg> Back to console</button>
172
+ <div class="orient" role="group" aria-label="Clip orientation">
173
+ <button data-orient="landscape" aria-pressed="true">16:9</button>
174
+ <button data-orient="square" aria-pressed="false">1:1</button>
175
+ <button data-orient="portrait" aria-pressed="false">9:16</button>
176
+ </div>
177
+ </div>
178
+
179
+ <div class="booth" id="booth">
180
+ <!-- the screen -->
181
+ <div>
182
+ <div class="screen-wrap">
183
+ <div class="screen" id="screen">
184
+ <video id="vid" class="screen-vid" playsinline preload="metadata"></video>
185
+ <div class="ph"><svg class="i"><use href="#i-cam"/></svg>YOUR CLIP PLAYS HERE</div>
186
+ <div class="bug"><span class="dot"></span> ON AIR</div>
187
+ <div class="tc" id="tc">00:00</div>
188
+ <!-- lower-third chyron -->
189
+ <div class="chyron" id="chyron">
190
+ <div class="bar"></div>
191
+ <div class="row"><span class="who" id="chyWho">COCO</span><span class="line" id="chyLine">Standing by…</span></div>
192
+ </div>
193
+ </div>
194
+ </div>
195
+ <div class="transport">
196
+ <button class="pp" id="pp" aria-label="Play / pause"><svg class="i"><use href="#i-play"/></svg></button>
197
+ <div class="scrub" id="scrub"><div class="fill" id="fill"></div><div class="knob" id="knob"></div></div>
198
+ <span class="clock" id="tcDur" style="font-size:16px">00:42</span>
199
+ </div>
200
+ </div>
201
+
202
+ <!-- play-by-play rail -->
203
+ <div class="pbp">
204
+ <div class="pbp-h"><svg class="i ic"><use href="#i-list"/></svg><h3>Play-by-play</h3><span class="ct" id="pbpCt">— lines</span></div>
205
+ <div class="lines" id="lines"></div>
206
+ <div class="export">
207
+ <button class="btn-srt" id="srtBtn"><svg class="i ic"><use href="#i-download"/></svg> Download captions (.srt)</button>
208
+ <div class="hint">Drops straight into <b>CapCut</b>, Premiere &amp; DaVinci — timed to your clip.</div>
209
+ </div>
210
+ </div>
211
+ </div>
212
+ </section>
213
+
214
+ </div>
215
+
216
+ <div class="toast" id="toast"><svg class="i ic"><use href="#i-check"/></svg><span id="toastTxt">Done</span></div>
217
+
218
+ <script src="marquee.js"></script>
219
+ </body>
220
+ </html>
README.md CHANGED
@@ -1,16 +1,100 @@
1
  ---
2
  title: Marquee
3
- emoji: 💬
4
- colorFrom: yellow
5
- colorTo: purple
6
  sdk: gradio
7
- sdk_version: 6.5.1
8
  app_file: app.py
9
- pinned: false
10
- hf_oauth: true
11
- hf_oauth_scopes:
12
- - inference-api
13
- short_description: hyping up your daily life!
14
  ---
15
 
16
- An example chatbot using [Gradio](https://gradio.app), [`huggingface_hub`](https://huggingface.co/docs/huggingface_hub/v0.22.2/en/index), and the [Hugging Face Inference API](https://huggingface.co/docs/api-inference/index).
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
  ---
2
  title: Marquee
3
+ emoji: 🎙️
4
+ colorFrom: purple
5
+ colorTo: pink
6
  sdk: gradio
7
+ sdk_version: 5.34.0
8
  app_file: app.py
9
+ license: apache-2.0
10
+ short_description: Turn any clip into a live broadcast where your friends are the stars
11
+ tags:
12
+ - build-small-hackathon
 
13
  ---
14
 
15
+ # Marquee 🎙️ — *Everyone's a headliner.*
16
+
17
+ Drop a clip of friends doing anything. Marquee recognizes who's in it, you give
18
+ each person a stage name, pick a show style, and an AI commentator calls the
19
+ action play-by-play, by name, in sync — as a broadcast you can play and export.
20
+
21
+ ## ⚙️ Running it on a Space (read this first)
22
+
23
+ - **Hardware must be ZeroGPU.** The commentary model runs inside `@spaces.GPU`,
24
+ which only has a GPU on **Nvidia ZeroGPU** hardware. On a plain CPU Space the
25
+ 7B VLM has no GPU and is effectively unusable. Set it in **Settings →
26
+ Hardware → ZeroGPU**.
27
+ - **No secrets needed at runtime.** Everything downloads from public sources
28
+ (Qwen from the HF Hub, the face models from Intel's OMZ storage). Do **not**
29
+ commit any `HF_TOKEN`/`.env` — if you need one for `gradio deploy`, keep it
30
+ local and add it as a **Space secret**, never a file in the repo.
31
+ - **Nothing to pre-download.** `download_models()` fetches the three tiny
32
+ OpenVINO IR files at first boot (~5 MB total). First boot also warms up Qwen.
33
+
34
+ ## How it's wired (the important part)
35
+
36
+ Gradio is the **runtime** (so `@spaces.GPU` / ZeroGPU works). The custom UI is a
37
+ **static HTML page** served at `/`, talking to plain FastAPI JSON endpoints via
38
+ `fetch()` — no Gradio JS client, no iframe.
39
+
40
+ ```
41
+ Static UI (Marquee.html + marquee.css + marquee.js) ──► served at "/"
42
+ │ plain fetch(), same-origin
43
+ ▼
44
+ FastAPI (app.py)
45
+ ├─ POST /api/scan (file) -> normalize 720p -> detect+cluster faces
46
+ │ -> session + roster (face crops as data URLs)
47
+ ├─ GET /video/{sid} -> streams the normalized mp4
48
+ └─ POST /api/generate (session, names, vibe)-> key events -> name-grounded frames
49
+ -> Qwen2.5-VL (ZeroGPU) -> {t,text} script
50
+ ```
51
+
52
+ An empty `gr.Blocks` is mounted at `/gradio` only so the HF "gradio" SDK runtime
53
+ and ZeroGPU machinery initialise.
54
+
55
+ Heavy per-session state (identities, motion, normalized video path) lives in a
56
+ server-side `SESSIONS` dict keyed by the `session_id` the scan call returns, so
57
+ the two stateless client calls never round-trip embeddings.
58
+
59
+ Face recognition is used **only** to feed names to the VLM — boxes are never
60
+ shown. The broadcast player reads the real normalized `<video>` and syncs the
61
+ chyron + play-by-play rail to `video.currentTime`.
62
+
63
+ ## Files
64
+ - `app.py` — FastAPI backend + the two API endpoints + UI assembly
65
+ - `Marquee.html` / `marquee.css` / `marquee.js` — the custom UI (design + flow)
66
+ - `video.py` — rotation-aware 720p normalize (one upright source of truth)
67
+ - `faces.py` — OpenVINO detect → align → embed → cluster (CPU)
68
+ - `events.py` — motion-based key events
69
+ - `commentary.py` — Qwen2.5-VL personas (incl. Diva Hour) + strict JSON parse
70
+ - `ov_models.py` — OpenVINO IR auto-download + thin runtime wrappers
71
+
72
+ ## Models (all open)
73
+ - face-detection-retail-0004, landmarks-regression-retail-0009,
74
+ face-reidentification-retail-0095 (OpenVINO IR, CPU)
75
+ - Qwen2.5-VL-7B-Instruct (ZeroGPU)
76
+
77
+ ## First debugging spots
78
+ - **Scan returns 500 / "xml not found".** The OMZ download at boot failed (check
79
+ boot logs for the `[ov]`/`[warn]` lines). `ov_models.download_models()` pulls
80
+ from `storage.openvinotoolkit.org`; re-run it or commit the `models/` folder.
81
+ - **Commentary errors / wrong device.** Confirm the Space is on **ZeroGPU**, not
82
+ CPU. The model loads on CPU and moves to GPU inside `@spaces.GPU` — never call
83
+ `.cuda()` at import.
84
+ - **VLM cold start** eats the first request if boot warmup didn't run. Keep the
85
+ warmup (it loads weights to RAM so the first call only pays the CPU→GPU move).
86
+ - **Clustering merges/splits people** -> tune `SAME_PERSON` in `faces.py`.
87
+ - **Root `/` 404s on the Space** (rare) -> the gradio SDK didn't serve the
88
+ mounted FastAPI app; switch to `gradio.Server` mode for fully custom frontends.
89
+
90
+ ## North star (next iteration)
91
+ - TTS per line (hyped delivery) mixed under the clip's own audio.
92
+ - Rendered MP4 export with captions (and voice) burned in.
93
+ - Canvas hype FX on key moments (data's already there).
94
+
95
+ ## Local dev
96
+ ```bash
97
+ pip install -r requirements.txt
98
+ python -c "from ov_models import download_models; download_models()"
99
+ python app.py # http://localhost:7860 (Qwen lazy-loads on first generate)
100
+ ```
_gitignore ADDED
@@ -0,0 +1,220 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Byte-compiled / optimized / DLL files
2
+ __pycache__/
3
+ *.py[codz]
4
+ *$py.class
5
+
6
+ # C extensions
7
+ *.so
8
+
9
+ # Distribution / packaging
10
+ .Python
11
+ build/
12
+ develop-eggs/
13
+ dist/
14
+ downloads/
15
+ eggs/
16
+ .eggs/
17
+ lib/
18
+ lib64/
19
+ parts/
20
+ sdist/
21
+ var/
22
+ wheels/
23
+ share/python-wheels/
24
+ *.egg-info/
25
+ .installed.cfg
26
+ *.egg
27
+ MANIFEST
28
+
29
+ # PyInstaller
30
+ # Usually these files are written by a python script from a template
31
+ # before PyInstaller builds the exe, so as to inject date/other infos into it.
32
+ *.manifest
33
+ *.spec
34
+
35
+ # Installer logs
36
+ pip-log.txt
37
+ pip-delete-this-directory.txt
38
+
39
+ # Unit test / coverage reports
40
+ htmlcov/
41
+ .tox/
42
+ .nox/
43
+ .coverage
44
+ .coverage.*
45
+ .cache
46
+ nosetests.xml
47
+ coverage.xml
48
+ *.cover
49
+ *.py.cover
50
+ .hypothesis/
51
+ .pytest_cache/
52
+ cover/
53
+
54
+ # Translations
55
+ *.mo
56
+ *.pot
57
+
58
+ # Django stuff:
59
+ *.log
60
+ local_settings.py
61
+ db.sqlite3
62
+ db.sqlite3-journal
63
+
64
+ # Flask stuff:
65
+ instance/
66
+ .webassets-cache
67
+
68
+ # Scrapy stuff:
69
+ .scrapy
70
+
71
+ # Sphinx documentation
72
+ docs/_build/
73
+
74
+ # PyBuilder
75
+ .pybuilder/
76
+ target/
77
+
78
+ # Jupyter Notebook
79
+ .ipynb_checkpoints
80
+
81
+ # IPython
82
+ profile_default/
83
+ ipython_config.py
84
+
85
+ # pyenv
86
+ # For a library or package, you might want to ignore these files since the code is
87
+ # intended to run in multiple environments; otherwise, check them in:
88
+ # .python-version
89
+
90
+ # pipenv
91
+ # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
92
+ # However, in case of collaboration, if having platform-specific dependencies or dependencies
93
+ # having no cross-platform support, pipenv may install dependencies that don't work, or not
94
+ # install all needed dependencies.
95
+ # Pipfile.lock
96
+
97
+ # UV
98
+ # Similar to Pipfile.lock, it is generally recommended to include uv.lock in version control.
99
+ # This is especially recommended for binary packages to ensure reproducibility, and is more
100
+ # commonly ignored for libraries.
101
+ # uv.lock
102
+
103
+ # poetry
104
+ # Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control.
105
+ # This is especially recommended for binary packages to ensure reproducibility, and is more
106
+ # commonly ignored for libraries.
107
+ # https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control
108
+ # poetry.lock
109
+ # poetry.toml
110
+
111
+ # pdm
112
+ # Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control.
113
+ # pdm recommends including project-wide configuration in pdm.toml, but excluding .pdm-python.
114
+ # https://pdm-project.org/en/latest/usage/project/#working-with-version-control
115
+ # pdm.lock
116
+ # pdm.toml
117
+ .pdm-python
118
+ .pdm-build/
119
+
120
+ # pixi
121
+ # Similar to Pipfile.lock, it is generally recommended to include pixi.lock in version control.
122
+ # pixi.lock
123
+ # Pixi creates a virtual environment in the .pixi directory, just like venv module creates one
124
+ # in the .venv directory. It is recommended not to include this directory in version control.
125
+ .pixi
126
+
127
+ # PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm
128
+ __pypackages__/
129
+
130
+ # Celery stuff
131
+ celerybeat-schedule
132
+ celerybeat.pid
133
+
134
+ # Redis
135
+ *.rdb
136
+ *.aof
137
+ *.pid
138
+
139
+ # RabbitMQ
140
+ mnesia/
141
+ rabbitmq/
142
+ rabbitmq-data/
143
+
144
+ # ActiveMQ
145
+ activemq-data/
146
+
147
+ # SageMath parsed files
148
+ *.sage.py
149
+
150
+ # Environments
151
+ .env
152
+ .envrc
153
+ .venv
154
+ env/
155
+ venv/
156
+ ENV/
157
+ env.bak/
158
+ venv.bak/
159
+
160
+ # Spyder project settings
161
+ .spyderproject
162
+ .spyproject
163
+
164
+ # Rope project settings
165
+ .ropeproject
166
+
167
+ # mkdocs documentation
168
+ /site
169
+
170
+ # mypy
171
+ .mypy_cache/
172
+ .dmypy.json
173
+ dmypy.json
174
+
175
+ # Pyre type checker
176
+ .pyre/
177
+
178
+ # pytype static type analyzer
179
+ .pytype/
180
+
181
+ # Cython debug symbols
182
+ cython_debug/
183
+
184
+ # PyCharm
185
+ # JetBrains specific template is maintained in a separate JetBrains.gitignore that can
186
+ # be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
187
+ # and can be added to the global gitignore or merged into this file. For a more nuclear
188
+ # option (not recommended) you can uncomment the following to ignore the entire idea folder.
189
+ # .idea/
190
+
191
+ # Abstra
192
+ # Abstra is an AI-powered process automation framework.
193
+ # Ignore directories containing user credentials, local state, and settings.
194
+ # Learn more at https://abstra.io/docs
195
+ .abstra/
196
+
197
+ # Visual Studio Code
198
+ # Visual Studio Code specific template is maintained in a separate VisualStudioCode.gitignore
199
+ # that can be found at https://github.com/github/gitignore/blob/main/Global/VisualStudioCode.gitignore
200
+ # and can be added to the global gitignore or merged into this file. However, if you prefer,
201
+ # you could uncomment the following to ignore the entire vscode folder
202
+ # .vscode/
203
+ # Temporary file for partial code execution
204
+ tempCodeRunnerFile.py
205
+
206
+ # Ruff stuff:
207
+ .ruff_cache/
208
+
209
+ # PyPI configuration file
210
+ .pypirc
211
+
212
+ # Marimo
213
+ marimo/_static/
214
+ marimo/_lsp/
215
+ __marimo__/
216
+
217
+ # Streamlit
218
+ .streamlit/secrets.toml
219
+
220
+ .vscode
app.py CHANGED
@@ -1,69 +1,193 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
  import gradio as gr
2
- from huggingface_hub import InferenceClient
 
 
3
 
 
 
 
 
 
4
 
5
- def respond(
6
- message,
7
- history: list[dict[str, str]],
8
- system_message,
9
- max_tokens,
10
- temperature,
11
- top_p,
12
- hf_token: gr.OAuthToken,
13
- ):
14
- """
15
- For more information on `huggingface_hub` Inference API support, please check the docs: https://huggingface.co/docs/huggingface_hub/v0.22.2/en/guides/inference
16
- """
17
- client = InferenceClient(token=hf_token.token, model="openai/gpt-oss-20b")
18
 
19
- messages = [{"role": "system", "content": system_message}]
 
 
 
 
 
 
 
20
 
21
- messages.extend(history)
 
 
 
22
 
23
- messages.append({"role": "user", "content": message})
 
 
 
 
 
 
 
 
 
24
 
25
- response = ""
26
 
27
- for message in client.chat_completion(
28
- messages,
29
- max_tokens=max_tokens,
30
- stream=True,
31
- temperature=temperature,
32
- top_p=top_p,
33
- ):
34
- choices = message.choices
35
- token = ""
36
- if len(choices) and choices[0].delta.content:
37
- token = choices[0].delta.content
38
 
39
- response += token
40
- yield response
 
 
41
 
42
 
43
- """
44
- For information on how to customize the ChatInterface, peruse the gradio docs: https://www.gradio.app/docs/chatinterface
45
- """
46
- chatbot = gr.ChatInterface(
47
- respond,
48
- additional_inputs=[
49
- gr.Textbox(value="You are a friendly Chatbot.", label="System message"),
50
- gr.Slider(minimum=1, maximum=2048, value=512, step=1, label="Max new tokens"),
51
- gr.Slider(minimum=0.1, maximum=4.0, value=0.7, step=0.1, label="Temperature"),
52
- gr.Slider(
53
- minimum=0.1,
54
- maximum=1.0,
55
- value=0.95,
56
- step=0.05,
57
- label="Top-p (nucleus sampling)",
58
- ),
59
- ],
60
- )
61
-
62
- with gr.Blocks() as demo:
63
- with gr.Sidebar():
64
- gr.LoginButton()
65
- chatbot.render()
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
66
 
 
67
 
68
  if __name__ == "__main__":
69
- demo.launch()
 
 
1
+ """app.py — Marquee backend (Gradio SDK Space + FastAPI).
2
+
3
+ Architecture:
4
+ - The UI is a static HTML file (Marquee.html + marquee.css + marquee.js)
5
+ served from "/". It talks to plain JSON endpoints at /api/scan and
6
+ /api/generate with plain fetch() — no Gradio JS client needed.
7
+ - An empty gr.Blocks is mounted at /gradio purely so the HF Spaces "gradio"
8
+ SDK runtime + ZeroGPU (@spaces.GPU) machinery initialises.
9
+
10
+ Heavy per-session state (identities, motion, normalized video path) lives in a
11
+ server-side SESSIONS dict keyed by a session_id the scan call returns.
12
+ """
13
+
14
+ import base64
15
+ import io
16
+ import json
17
+ import os
18
+ import tempfile
19
+ import uuid
20
+ from pathlib import Path
21
+
22
  import gradio as gr
23
+ from fastapi import FastAPI, Request
24
+ from fastapi.responses import HTMLResponse, JSONResponse
25
+ from fastapi.staticfiles import StaticFiles
26
 
27
+ from commentary import STYLES, _warmup_model, generate_commentary
28
+ from events import events_to_frames, pick_key_events
29
+ from faces import annotate_event_frame, cluster_faces, frame_at, scan_video
30
+ from ov_models import download_models
31
+ from video import get_duration, normalize_video
32
 
33
+ MAX_SECONDS = 60.0 # keep the demo snappy; matches the UI copy
34
+ HERE = Path(__file__).parent
35
+ SESSIONS = {}
 
 
 
 
 
 
 
 
 
 
36
 
37
+ TIERS = ["HEADLINER", "RISING", "DIVA", "WILDCARD", "BREAKOUT", "CAMEO"]
38
+ VIBE_TO_STYLE = {
39
+ "football": "Football (Premier League hype)",
40
+ "diva": "Diva Hour",
41
+ "wildlife": "Nature documentary",
42
+ "boxing": "Boxing announcer",
43
+ }
44
+ SUPPORTED_VIDEO = {".mp4", ".mov", ".webm", ".mkv", ".avi", ".m4v"}
45
 
46
+ try:
47
+ download_models()
48
+ except Exception as e:
49
+ print(f"[warn] model download failed at boot: {e}")
50
 
51
+ _ON_SPACES = bool(os.environ.get("SPACE_ID"))
52
+ if _ON_SPACES:
53
+ print("[boot] HF Space — warming up Qwen (CPU load)...")
54
+ try:
55
+ _warmup_model()
56
+ print("[boot] Model weights resident.")
57
+ except Exception as e:
58
+ print(f"[warn] warmup failed: {e}")
59
+ else:
60
+ print("[boot] Local — Qwen lazy-loads on first /api/generate call.")
61
 
 
62
 
63
+ # ── helpers ──────────────────────────────────────────────────────────────────
 
 
 
 
 
 
 
 
 
 
64
 
65
+ def _crop_b64(pil, size=160):
66
+ img = pil.copy(); img.thumbnail((size, size))
67
+ buf = io.BytesIO(); img.save(buf, format="PNG")
68
+ return "data:image/png;base64," + base64.b64encode(buf.getvalue()).decode()
69
 
70
 
71
+ def _build_ui() -> str:
72
+ """Inline CSS + JS into Marquee.html so the page is fully self-contained."""
73
+ doc = (HERE / "Marquee.html").read_text()
74
+ css = (HERE / "marquee.css").read_text()
75
+ js = (HERE / "marquee.js").read_text()
76
+
77
+ doc = doc.replace('<link rel="stylesheet" href="marquee.css">',
78
+ f"<style>{css}</style>")
79
+ doc = doc.replace('<script src="marquee.js"></script>',
80
+ f"<script type='module'>{js}</script>")
81
+ return doc
82
+
83
+
84
+ # ── FastAPI app ───────────────────────────────────────────────────────────────
85
+
86
+ app = FastAPI()
87
+
88
+ _UI_HTML = _build_ui()
89
+
90
+
91
+ @app.get("/", response_class=HTMLResponse)
92
+ async def root():
93
+ return HTMLResponse(_UI_HTML)
94
+
95
+
96
+ @app.post("/api/scan")
97
+ async def api_scan(request: Request):
98
+ try:
99
+ form = await request.form()
100
+ upload = form.get("file")
101
+ if upload is None:
102
+ return JSONResponse({"ok": False, "error": "no file"}, status_code=400)
103
+
104
+ suffix = Path(upload.filename).suffix.lower()
105
+ if suffix not in SUPPORTED_VIDEO:
106
+ return JSONResponse({"ok": False,
107
+ "error": f"Unsupported format '{suffix}'. Supported: {', '.join(sorted(SUPPORTED_VIDEO))}"})
108
+
109
+ tmp = tempfile.NamedTemporaryFile(delete=False, suffix=suffix)
110
+ tmp.write(await upload.read()); tmp.flush(); tmp.close()
111
+
112
+ dur = get_duration(tmp.name)
113
+ if dur > MAX_SECONDS + 1:
114
+ return JSONResponse({"ok": False, "error": "Clip longer than 60 seconds"})
115
+
116
+ norm_path, w, h = normalize_video(tmp.name)
117
+ embeds, crops, motion, meta = scan_video(norm_path, MAX_SECONDS)
118
+ identities = cluster_faces(embeds, crops)
119
+
120
+ sid = uuid.uuid4().hex
121
+ SESSIONS[sid] = {"norm": norm_path, "identities": identities,
122
+ "motion": motion, "meta": meta}
123
+
124
+ stars = [{"idx": i, "tier": TIERS[i % len(TIERS)],
125
+ "crop": _crop_b64(idn["crop"])}
126
+ for i, idn in enumerate(identities)]
127
+
128
+ return JSONResponse({"ok": True, "session_id": sid,
129
+ "video_url": f"/video/{sid}",
130
+ "duration": get_duration(norm_path),
131
+ "w": w, "h": h, "portrait": h > w, "stars": stars})
132
+
133
+ except Exception as e:
134
+ import traceback; traceback.print_exc()
135
+ return JSONResponse({"ok": False, "error": str(e)}, status_code=500)
136
+
137
+
138
+ @app.get("/video/{sid}")
139
+ async def serve_video(sid: str):
140
+ """Stream the normalized video file."""
141
+ from fastapi.responses import FileResponse
142
+ sess = SESSIONS.get(sid)
143
+ if not sess:
144
+ return JSONResponse({"error": "session not found"}, status_code=404)
145
+ return FileResponse(sess["norm"], media_type="video/mp4",
146
+ headers={"Accept-Ranges": "bytes"})
147
+
148
+
149
+ @app.post("/api/generate")
150
+ async def api_generate(request: Request):
151
+ try:
152
+ body = await request.json()
153
+ sid = body.get("session_id", "")
154
+ names = body.get("names", [])
155
+ vibe = body.get("vibe", "football")
156
+
157
+ sess = SESSIONS.get(sid)
158
+ if not sess:
159
+ return JSONResponse({"ok": False, "error": "Session expired — re-cast"})
160
+
161
+ name_map = {i: str(n).strip() for i, n in enumerate(names) if str(n).strip()}
162
+ style = VIBE_TO_STYLE.get(vibe, next(iter(STYLES)))
163
+
164
+ fps = sess["meta"]["fps"]
165
+ event_times = pick_key_events(sess["motion"], fps)
166
+ event_idxs = events_to_frames(event_times, fps)
167
+
168
+ frames = []
169
+ for ts, fidx in zip(event_times, event_idxs):
170
+ bgr = frame_at(sess["norm"], fidx)
171
+ if bgr is None: continue
172
+ frames.append((ts, annotate_event_frame(bgr, sess["identities"], name_map)))
173
+
174
+ script = generate_commentary(frames, list(name_map.values()), style)
175
+ return JSONResponse({"ok": True,
176
+ "script": [{"t": round(float(l["time"]), 2), "text": l["text"]}
177
+ for l in script]})
178
+
179
+ except Exception as e:
180
+ import traceback; traceback.print_exc()
181
+ return JSONResponse({"ok": False, "error": str(e)}, status_code=500)
182
+
183
+
184
+ # ── Gradio shim (required for the HF "gradio" SDK runtime + ZeroGPU) ──────────
185
+
186
+ with gr.Blocks(title="Marquee") as _gr:
187
+ gr.HTML("<p style='display:none'>Marquee backend</p>")
188
 
189
+ app = gr.mount_gradio_app(app, _gr, path="/gradio")
190
 
191
  if __name__ == "__main__":
192
+ import uvicorn
193
+ uvicorn.run(app, host="0.0.0.0", port=7860)
commentary.py ADDED
@@ -0,0 +1,166 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """commentary.py — the VLM brain. Read the [VLM 101]/[LLM 101] notes once;
2
+ that's most of the mental model for working with these models anywhere.
3
+
4
+ Key design choice for the north star: the VLM does NOT invent timestamps.
5
+ events.py already picked real key-event times; we hand the model one annotated
6
+ frame per event and ask for one line per event. So every line is pinned to a
7
+ real moment -> speech can be placed exactly there later. Deterministic sync.
8
+
9
+ [ZeroGPU 101] On HF ZeroGPU, CUDA is NOT initialised in the main process — you
10
+ must never call .cuda() at import / module scope. The pattern that actually
11
+ works: load the model to CPU once (cheap RAM, no GPU), then move it to GPU
12
+ *inside* a @spaces.GPU function, which is the only place a GPU exists. Using
13
+ device_map="auto" at import races with that mechanism and is the usual cause of
14
+ "model on wrong device" errors on ZeroGPU.
15
+ """
16
+
17
+ import json
18
+ import re
19
+
20
+ import spaces
21
+ import torch
22
+
23
+ MODEL_ID = "Qwen/Qwen2.5-VL-7B-Instruct"
24
+ _model = _processor = None
25
+
26
+ STYLES = {
27
+ "Football (Premier League hype)":
28
+ "an over-the-top English football commentator, dramatic and poetic",
29
+ "MasterChef judge":
30
+ "a brutally dramatic cooking-show commentator treating everything like "
31
+ "a high-stakes elimination",
32
+ "Nature documentary":
33
+ "a hushed, awestruck nature narrator observing these humans like rare "
34
+ "wildlife",
35
+ "Boxing announcer":
36
+ "a booming boxing announcer treating every move as championship-defining",
37
+ "Diva Hour":
38
+ "a fabulous, shady red-carpet diva commentator narrating like everyone "
39
+ "is a celebrity arriving at a star-studded gala, full of glamour and sass",
40
+ }
41
+
42
+ # [LLM 101] System prompt = persona + a STRICT output contract. Show the exact
43
+ # JSON schema, say what not to do, keep rules last. We tell it the names so it
44
+ # uses them even if a label is hard to read in a given frame.
45
+ SYSTEM_PROMPT = """You are {persona}.
46
+
47
+ You'll receive ordered keyframes from a short video. Each is tagged with its
48
+ timestamp. Some people have their NAME drawn in a box above their head — call
49
+ those people by name, loudly and often. Unnamed people: describe them ("the
50
+ challenger in red").
51
+
52
+ People in this clip: {roster}.
53
+
54
+ Write ONE punchy line of hype commentary per frame.
55
+
56
+ Rules:
57
+ - Output ONLY a JSON array. No markdown, no extra text.
58
+ - Schema: [{{"time": <float seconds>, "text": "<one line>"}}]
59
+ - Use the EXACT timestamp given for each frame as "time".
60
+ - Each line under 16 words (it gets spoken before the next event).
61
+ - USE CAPS + exclamation for big moments; the TTS reads punctuation as emotion.
62
+ - Be funny. Treat mundane actions as legendary athletic feats."""
63
+
64
+
65
+ def _load():
66
+ """Load Qwen weights to CPU and cache in globals. No CUDA here — that's
67
+ illegal at module scope on ZeroGPU. Call once at boot (warmup) so the first
68
+ real request only pays the CPU->GPU move, not a cold download.
69
+ """
70
+ global _model, _processor
71
+ if _model is not None:
72
+ return _model, _processor
73
+
74
+ from transformers import (AutoProcessor,
75
+ Qwen2_5_VLForConditionalGeneration)
76
+ print("[info] Loading Qwen2.5-VL-7B to CPU (first time: download + load)...")
77
+ _model = Qwen2_5_VLForConditionalGeneration.from_pretrained(
78
+ MODEL_ID, torch_dtype=torch.bfloat16)
79
+ _processor = AutoProcessor.from_pretrained(MODEL_ID)
80
+ print("[info] Qwen weights resident in RAM.")
81
+ return _model, _processor
82
+
83
+
84
+ def _warmup_model():
85
+ """Boot-time hook: pull weights to disk + RAM so the first /api/generate
86
+ only pays the CPU->GPU move. No GPU is touched here, so it's safe to call
87
+ outside any @spaces.GPU window at app startup.
88
+ """
89
+ return _load()
90
+
91
+
92
+ @spaces.GPU(duration=120)
93
+ def generate_commentary(event_frames, roster, style_key):
94
+ """event_frames: [(timestamp_sec, PIL.Image), ...] (annotated, key events)
95
+ roster: list of names the user assigned
96
+ -> [{"time": float, "text": str}, ...] aligned to the event times.
97
+ """
98
+ model, processor = _load()
99
+ if torch.cuda.is_available(): # ZeroGPU: GPU only exists in here
100
+ model = model.to("cuda")
101
+ from qwen_vl_utils import process_vision_info
102
+
103
+ # [VLM 101] Multimodal user turn: content is a list mixing text + images.
104
+ # We label each image with its real timestamp so the model echoes it back.
105
+ content = []
106
+ for ts, img in event_frames:
107
+ content.append({"type": "text", "text": f"Frame at t={ts:.2f}s:"})
108
+ content.append({"type": "image", "image": img})
109
+ content.append({"type": "text", "text": "Write the commentary JSON now."})
110
+
111
+ persona = STYLES.get(style_key, list(STYLES.values())[0])
112
+ roster_str = ", ".join(roster) if roster else "nobody named yet"
113
+ messages = [
114
+ {"role": "system",
115
+ "content": SYSTEM_PROMPT.format(persona=persona, roster=roster_str)},
116
+ {"role": "user", "content": content},
117
+ ]
118
+
119
+ # [LLM 101] Chat template converts message dicts into the exact special-
120
+ # token format the instruct model was trained on. Skip it and quality dies.
121
+ text = processor.apply_chat_template(messages, tokenize=False,
122
+ add_generation_prompt=True)
123
+ image_inputs, _ = process_vision_info(messages)
124
+ inputs = processor(text=[text], images=image_inputs,
125
+ return_tensors="pt").to(model.device)
126
+
127
+ # [LLM 101] temperature 0.8 -> a bit of comedic randomness; max_new_tokens
128
+ # caps output length (your latency/cost lever).
129
+ out = model.generate(**inputs, max_new_tokens=500, do_sample=True,
130
+ temperature=0.8, top_p=0.95)
131
+ new = out[:, inputs.input_ids.shape[1]:]
132
+ raw = processor.batch_decode(new, skip_special_tokens=True)[0]
133
+
134
+ valid_times = [round(ts, 2) for ts, _ in event_frames]
135
+ return _parse(raw, valid_times)
136
+
137
+
138
+ def _parse(raw: str, valid_times: list[float]) -> list[dict]:
139
+ """[LLM 101] Never trust LLM output formatting. Strip fences, grab the JSON
140
+ array, snap each line's time to the nearest real event time, degrade
141
+ gracefully if it returns garbage.
142
+ """
143
+ raw = re.sub(r"```(?:json)?", "", raw).strip()
144
+ m = re.search(r"\[.*\]", raw, re.DOTALL)
145
+ try:
146
+ items = json.loads(m.group(0) if m else raw)
147
+ except (json.JSONDecodeError, AttributeError):
148
+ items = []
149
+
150
+ script = []
151
+ for i, it in enumerate(items):
152
+ try:
153
+ txt = str(it["text"]).strip()
154
+ t = float(it.get("time", valid_times[min(i, len(valid_times) - 1)]))
155
+ except (KeyError, TypeError, ValueError):
156
+ continue
157
+ # snap to nearest real event timestamp
158
+ t = min(valid_times, key=lambda v: abs(v - t)) if valid_times else t
159
+ if txt:
160
+ script.append({"time": t, "text": txt})
161
+
162
+ if not script and valid_times:
163
+ script = [{"time": valid_times[0],
164
+ "text": "WHAT A MOMENT, LADIES AND GENTLEMEN!"}]
165
+ script.sort(key=lambda x: x["time"])
166
+ return script
download_model.py ADDED
@@ -0,0 +1,53 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """download_model.py — run this ONCE before `python app.py`.
2
+
3
+ Downloads Qwen2.5-VL-7B to your HF cache (~/.cache/huggingface/hub).
4
+ After this, app.py finds it in cache and loads in ~30 sec (no re-download).
5
+
6
+ Usage:
7
+ python download_model.py
8
+
9
+ If you interrupt (Ctrl+C), run again — huggingface_hub resumes partial downloads.
10
+ Set HF_TOKEN env var for higher rate limits (optional, not required for this model):
11
+ HF_TOKEN=hf_xxx python download_model.py
12
+ """
13
+
14
+ import os
15
+ import sys
16
+
17
+ MODEL_ID = "Qwen/Qwen2.5-VL-7B-Instruct"
18
+ # MODEL_ID = "Qwen2.5-VL-3B-Instruct"
19
+
20
+ def main():
21
+ try:
22
+ from huggingface_hub import snapshot_download
23
+ except ImportError:
24
+ print("[error] huggingface_hub not installed. Run: pip install huggingface_hub")
25
+ sys.exit(1)
26
+
27
+ token = os.environ.get("HF_TOKEN")
28
+ if not token:
29
+ print("[info] No HF_TOKEN set. Download will work but rate-limited.")
30
+ print("[info] Optional: set HF_TOKEN env var for faster downloads.")
31
+
32
+ print(f"[info] Downloading {MODEL_ID} to HF cache...")
33
+ print("[info] This is ~16 GB. First time: ~5-30 min depending on connection.")
34
+ print("[info] Safe to Ctrl+C and resume — will pick up where it left off.\n")
35
+
36
+ try:
37
+ path = snapshot_download(
38
+ repo_id=MODEL_ID,
39
+ token=token,
40
+ resume_download=True,
41
+ )
42
+ print(f"\n[done] Model cached at: {path}")
43
+ print("[done] You can now run: python app.py")
44
+ except KeyboardInterrupt:
45
+ print("\n[interrupted] Partial download saved. Run again to resume.")
46
+ sys.exit(0)
47
+ except Exception as e:
48
+ print(f"\n[error] Download failed: {e}")
49
+ sys.exit(1)
50
+
51
+
52
+ if __name__ == "__main__":
53
+ main()
events.py ADDED
@@ -0,0 +1,45 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """events.py — pick "key events" from the motion signal. No model: a key event
2
+ is just a local peak in frame-to-frame motion (someone moved a lot = something
3
+ happened). Returns timestamps the VLM will comment on and the TTS will sync to.
4
+ """
5
+
6
+ import numpy as np
7
+
8
+
9
+ def pick_key_events(motion: dict, fps: float, max_events: int = 10,
10
+ min_gap_sec: float = 1.5) -> list[float]:
11
+ """motion: {frame_idx: score}. Returns sorted list of timestamps (sec).
12
+
13
+ Greedy non-max suppression: take the highest-motion frame, suppress its
14
+ neighbours within min_gap, repeat. This keeps events spread out instead of
15
+ clustering 5 lines onto one busy moment.
16
+ """
17
+ if not motion:
18
+ return [0.5]
19
+
20
+ # Only above-average-motion frames count as "events" — otherwise a calm
21
+ # clip would spawn fake events on still frames just to hit max_events.
22
+ scores = np.array(list(motion.values()), dtype=float)
23
+ floor = scores.mean()
24
+ candidates = {f: s for f, s in motion.items() if s > floor}
25
+ if not candidates: # totally flat clip -> uniform
26
+ n = min(max_events, max(1, int(len(motion) / (min_gap_sec * fps))))
27
+ step = len(motion) / max(1, n)
28
+ return sorted((i + 0.5) * step / fps for i in range(n))
29
+
30
+ items = sorted(candidates.items(), key=lambda kv: kv[1], reverse=True)
31
+ min_gap_frames = int(min_gap_sec * fps)
32
+
33
+ chosen = []
34
+ for frame_idx, _score in items:
35
+ if all(abs(frame_idx - c) >= min_gap_frames for c in chosen):
36
+ chosen.append(frame_idx)
37
+ if len(chosen) >= max_events:
38
+ break
39
+
40
+ chosen.sort()
41
+ return [c / fps for c in chosen]
42
+
43
+
44
+ def events_to_frames(timestamps: list[float], fps: float) -> list[int]:
45
+ return [int(round(ts * fps)) for ts in timestamps]
faces.py ADDED
@@ -0,0 +1,186 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """faces.py — face recog used PURELY to feed names to the VLM. Nothing here is
2
+ ever shown in the player; boxes/labels exist only as VLM input.
3
+
4
+ Because we don't draw labels on output, we don't need dense detection or
5
+ tracking. We:
6
+ 1. sample sparsely (~2 fps) to enumerate everyone + score motion
7
+ 2. align (landmarks) + embed (re-id) each face
8
+ 3. cluster embeddings -> unique identities -> crops for the naming gallery
9
+ Then, separately, for the handful of key-event frames, we detect + match +
10
+ burn names on a throwaway copy that goes to the VLM.
11
+ """
12
+
13
+ import cv2
14
+ import numpy as np
15
+ from PIL import Image
16
+
17
+ from ov_models import OVModel
18
+
19
+ DET_CONF = 0.6
20
+ SAME_PERSON = 0.55 # cosine sim above this = same identity (tune 0.4-0.6)
21
+ SAMPLE_FPS = 2.0 # detection sampling rate (identity discovery)
22
+
23
+ # ArcFace-style reference 5 points, scaled to a 128x128 aligned crop.
24
+ _REF_5PTS = np.array([
25
+ [38.2946, 51.6963], [73.5318, 51.5014], [56.0252, 71.7366],
26
+ [41.5493, 92.3655], [70.7299, 92.2041],
27
+ ], dtype=np.float32) * (128.0 / 112.0)
28
+
29
+ _det = _lm = _reid = None
30
+
31
+
32
+ def _load():
33
+ global _det, _lm, _reid
34
+ if _det is None:
35
+ _det = OVModel("face-detection-retail-0004")
36
+ _lm = OVModel("landmarks-regression-retail-0009")
37
+ _reid = OVModel("face-reidentification-retail-0095")
38
+ return _det, _lm, _reid
39
+
40
+
41
+ def _detect(frame_bgr) -> list[list[int]]:
42
+ det, _, _ = _load()
43
+ h, w = frame_bgr.shape[:2]
44
+ out = det.infer(frame_bgr).reshape(-1, 7) # [_,_,conf,x1,y1,x2,y2] norm
45
+ boxes = []
46
+ for _, _, conf, x1, y1, x2, y2 in out:
47
+ if conf < DET_CONF:
48
+ continue
49
+ boxes.append([max(0, int(x1 * w)), max(0, int(y1 * h)),
50
+ min(w, int(x2 * w)), min(h, int(y2 * h))])
51
+ return boxes
52
+
53
+
54
+ def _aligned_embed(frame_bgr, box) -> np.ndarray | None:
55
+ """Crop -> landmark-align to 128x128 -> re-id embedding (L2-normalized).
56
+ Alignment matters: re-id-0095 expects an aligned face; raw crops cluster
57
+ badly on tilted/profile shots, which would split or merge people.
58
+ """
59
+ _, lm, reid = _load()
60
+ x1, y1, x2, y2 = box
61
+ crop = frame_bgr[y1:y2, x1:x2]
62
+ if crop.size == 0:
63
+ return None
64
+
65
+ # landmarks-regression-retail-0009 -> 10 values = 5 (x,y) in crop-relative
66
+ pts = lm.infer(crop).reshape(5, 2)
67
+ pts[:, 0] *= (x2 - x1)
68
+ pts[:, 1] *= (y2 - y1)
69
+ pts[:, 0] += x1
70
+ pts[:, 1] += y1
71
+
72
+ M, _ = cv2.estimateAffinePartial2D(pts.astype(np.float32), _REF_5PTS)
73
+ if M is None:
74
+ aligned = cv2.resize(crop, (128, 128)) # fallback: no alignment
75
+ else:
76
+ aligned = cv2.warpAffine(frame_bgr, M, (128, 128))
77
+
78
+ v = reid.infer(aligned).reshape(-1)
79
+ return v / (np.linalg.norm(v) + 1e-9)
80
+
81
+
82
+ def scan_video(video_path: str, max_seconds: float = 600.0):
83
+ """Sparse pass. Returns:
84
+ embeds: list[np.ndarray] # one per detected face
85
+ crops: list[PIL.Image] # matching crops
86
+ motion: {frame_idx: float} # for key-event picking
87
+ meta: {"fps", "n_frames"}
88
+ """
89
+ _load()
90
+ cap = cv2.VideoCapture(video_path)
91
+ fps = cap.get(cv2.CAP_PROP_FPS) or 30.0
92
+ stride = max(1, int(round(fps / SAMPLE_FPS)))
93
+
94
+ embeds, crops, motion = [], [], {}
95
+ prev_small = None
96
+ idx = 0
97
+ while True:
98
+ ok, frame = cap.read()
99
+ if not ok or idx / fps > max_seconds:
100
+ break
101
+ if idx % stride == 0:
102
+ # motion vs previous SAMPLED frame (coarse but fine for events)
103
+ small = cv2.cvtColor(cv2.resize(frame, (160, 90)), cv2.COLOR_BGR2GRAY)
104
+ if prev_small is not None:
105
+ motion[idx] = float(np.mean(cv2.absdiff(small, prev_small)))
106
+ prev_small = small
107
+
108
+ for box in _detect(frame):
109
+ emb = _aligned_embed(frame, box)
110
+ if emb is None:
111
+ continue
112
+ embeds.append(emb)
113
+ x1, y1, x2, y2 = box
114
+ crops.append(Image.fromarray(
115
+ cv2.cvtColor(frame[y1:y2, x1:x2], cv2.COLOR_BGR2RGB)))
116
+ idx += 1
117
+ n_frames = idx
118
+ cap.release()
119
+ return embeds, crops, motion, {"fps": fps, "n_frames": n_frames}
120
+
121
+
122
+ def cluster_faces(embeds, crops):
123
+ """Greedy-cluster all detected faces into unique identities. Returns:
124
+ identities: [{"centroid": emb, "crop": best PIL, "count": n}, ...]
125
+ Index in this list == identity id used everywhere else.
126
+ """
127
+ identities = []
128
+ for emb, crop in zip(embeds, crops):
129
+ best, best_sim = None, SAME_PERSON
130
+ for ident in identities:
131
+ sim = float(np.dot(emb, ident["centroid"]))
132
+ if sim >= best_sim:
133
+ best, best_sim = ident, sim
134
+ if best is None:
135
+ identities.append({"centroid": emb, "crop": crop,
136
+ "crop_area": crop.size[0] * crop.size[1],
137
+ "count": 1})
138
+ else:
139
+ n = best["count"]
140
+ best["centroid"] = (best["centroid"] * n + emb) / (n + 1)
141
+ best["centroid"] /= (np.linalg.norm(best["centroid"]) + 1e-9)
142
+ best["count"] = n + 1
143
+ if crop.size[0] * crop.size[1] > best["crop_area"]:
144
+ best["crop"] = crop
145
+ best["crop_area"] = crop.size[0] * crop.size[1]
146
+ # drop singletons (likely false detections) unless that leaves nothing
147
+ strong = [i for i in identities if i["count"] >= 2]
148
+ return strong or identities
149
+
150
+
151
+ def annotate_event_frame(frame_bgr, identities, names) -> Image.Image:
152
+ """For ONE key-event frame: detect faces, match each to a named identity,
153
+ burn the name above the box. This image is VLM-input only — the user never
154
+ sees it. This is how the VLM grounds names to the right person.
155
+ """
156
+ img = frame_bgr.copy()
157
+ centroids = [i["centroid"] for i in identities]
158
+ for box in _detect(frame_bgr):
159
+ emb = _aligned_embed(frame_bgr, box)
160
+ if emb is None or not centroids:
161
+ continue
162
+ sims = [float(np.dot(emb, c)) for c in centroids]
163
+ iid = int(np.argmax(sims))
164
+ if sims[iid] < SAME_PERSON:
165
+ continue
166
+ name = names.get(iid, "").strip()
167
+ if not name:
168
+ continue
169
+ x1, y1, x2, y2 = box
170
+ scale = max(0.5, (x2 - x1) / 200.0)
171
+ thick = max(1, int(scale * 2))
172
+ (tw, th), _ = cv2.getTextSize(name, cv2.FONT_HERSHEY_DUPLEX, scale, thick)
173
+ tx, ty = x1, max(th + 6, y1 - 8)
174
+ cv2.rectangle(img, (tx - 4, ty - th - 6), (tx + tw + 4, ty + 4),
175
+ (0, 0, 0), -1)
176
+ cv2.putText(img, name, (tx, ty), cv2.FONT_HERSHEY_DUPLEX,
177
+ scale, (0, 255, 255), thick, cv2.LINE_AA)
178
+ return Image.fromarray(cv2.cvtColor(img, cv2.COLOR_BGR2RGB))
179
+
180
+
181
+ def frame_at(video_path: str, frame_idx: int):
182
+ cap = cv2.VideoCapture(video_path)
183
+ cap.set(cv2.CAP_PROP_POS_FRAMES, frame_idx)
184
+ ok, frame = cap.read()
185
+ cap.release()
186
+ return frame if ok else None
marquee.css ADDED
@@ -0,0 +1,468 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /* ============================================================
2
+ MARQUEE — design system
3
+ Tokens straight from the brief so they lift into Gradio css=.
4
+ Sections: tokens · base · masthead · rundown · desk · stage
5
+ · star card · vibes + Coco · broadcast player · utilities
6
+ ============================================================ */
7
+
8
+ :root{
9
+ /* night & surfaces */
10
+ --night:#190B2E; /* base */
11
+ --night-2:#120821; /* deeper, for wells */
12
+ --stage:#241241; /* panels */
13
+ --stage-2:#2E1A52; /* raised */
14
+ --raise:#382060; /* inputs-on-dark */
15
+ --line:#3A2A5E; /* borders */
16
+ --line-soft:#2C1E4C;
17
+
18
+ /* leads + helper */
19
+ --hot:#FF3D8B; /* primary / on-air / energy */
20
+ --gold:#FFC94D; /* stardom: lights, crowns */
21
+ --cyan:#3DE7FF; /* confirm / scan */
22
+ --violet:#B98BFF; /* playful / mascot */
23
+
24
+ /* text */
25
+ --cream:#FFF3E9;
26
+ --muted:#A98FC9;
27
+ --muted-2:#7C66A6;
28
+
29
+ /* the casting rainbow — each star gets their own character color.
30
+ all high-chroma, all in-family: the "agent select" punch. */
31
+ --star-gold:#FFC94D;
32
+ --star-hot:#FF4D9A;
33
+ --star-cyan:#3DE7FF;
34
+ --star-violet:#C18BFF;
35
+ --star-lime:#9BE85B;
36
+ --star-tang:#FF8A4D;
37
+
38
+ --fx:cubic-bezier(.2,.9,.25,1);
39
+ --pop:cubic-bezier(.2,1.7,.4,1);
40
+ }
41
+
42
+ *{box-sizing:border-box;margin:0;padding:0}
43
+ html{-webkit-font-smoothing:antialiased;text-rendering:optimizeLegibility}
44
+
45
+ body{
46
+ font-family:Nunito,system-ui,sans-serif;
47
+ color:var(--cream);
48
+ background:var(--night);
49
+ min-height:100vh;
50
+ line-height:1.5;
51
+ /* one justified light: a stage spotlight from above + a faint floor wash.
52
+ no decorative blob gradients. */
53
+ background-image:
54
+ radial-gradient(120% 70% at 50% -25%, rgba(255,201,77,.10), transparent 55%),
55
+ radial-gradient(140% 90% at 50% 120%, rgba(61,231,255,.05), transparent 60%);
56
+ background-attachment:fixed;
57
+ }
58
+ /* film grain — the only decorative texture, very subtle */
59
+ body::after{
60
+ content:"";position:fixed;inset:0;pointer-events:none;z-index:80;opacity:.04;
61
+ mix-blend-mode:overlay;
62
+ background-image:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='160' height='160'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='.85' numOctaves='2'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23n)'/%3E%3C/svg%3E");
63
+ }
64
+
65
+ .disp{font-family:Unbounded,sans-serif}
66
+ .marq{font-family:'Bebas Neue',sans-serif;letter-spacing:.06em}
67
+ svg.i{width:1em;height:1em;display:block;flex:none}
68
+
69
+ .shell{max-width:1180px;margin:0 auto;padding:clamp(16px,3vw,34px) clamp(16px,4vw,40px) 40px;position:relative;z-index:1}
70
+
71
+ /* ============================================================
72
+ MASTHEAD — the broadcast header bar. signature lives here.
73
+ ============================================================ */
74
+ .masthead{
75
+ display:flex;align-items:center;justify-content:space-between;gap:20px;
76
+ padding-bottom:22px;margin-bottom:6px;flex-wrap:wrap;
77
+ }
78
+ .brand{display:flex;align-items:center;gap:18px}
79
+
80
+ /* the lit marquee sign — spend the boldness here */
81
+ .sign{
82
+ position:relative;padding:14px 22px 15px;border-radius:14px;
83
+ border:2px solid rgba(255,201,77,.55);
84
+ background:
85
+ radial-gradient(120% 140% at 50% 0%, rgba(255,201,77,.14), transparent 70%),
86
+ linear-gradient(180deg, rgba(255,201,77,.05), rgba(25,11,46,.2));
87
+ box-shadow:inset 0 0 22px rgba(255,201,77,.16), 0 10px 40px rgba(255,61,139,.10);
88
+ }
89
+ .sign h1{
90
+ font-family:Unbounded,sans-serif;font-weight:800;font-size:clamp(26px,3.4vw,38px);
91
+ letter-spacing:.04em;line-height:.9;
92
+ color:var(--cream);
93
+ text-shadow:0 0 18px rgba(255,201,77,.45), 0 0 2px rgba(255,201,77,.8);
94
+ }
95
+ /* perimeter bulbs, top + bottom rows */
96
+ .sign .bulbs{position:absolute;left:14px;right:14px;display:flex;justify-content:space-between}
97
+ .sign .bulbs.t{top:-5px}.sign .bulbs.b{bottom:-5px}
98
+ .sign .bulbs i{width:6px;height:6px;border-radius:50%;background:var(--gold);
99
+ box-shadow:0 0 8px var(--gold),0 0 2px #fff;animation:tw 2.6s infinite}
100
+ .sign .bulbs i:nth-child(2n){animation-delay:.7s}
101
+ .sign .bulbs i:nth-child(3n){animation-delay:1.5s;background:var(--hot);box-shadow:0 0 8px var(--hot),0 0 2px #fff}
102
+ @keyframes tw{0%,100%{opacity:1}48%{opacity:.28}}
103
+
104
+ .tag{font-family:Unbounded,sans-serif;font-weight:500;font-size:13px;color:var(--gold);letter-spacing:.01em}
105
+ .tag small{display:block;font-family:Nunito;font-weight:600;color:var(--muted);font-size:12px;margin-top:3px;letter-spacing:0}
106
+
107
+ /* right side: live clock + status */
108
+ .broadcast-meta{display:flex;align-items:center;gap:14px}
109
+ .clock{font-family:'Bebas Neue',sans-serif;letter-spacing:.12em;font-size:20px;color:var(--muted);
110
+ font-variant-numeric:tabular-nums}
111
+ .status{display:flex;align-items:center;gap:9px;white-space:nowrap;font-family:'Bebas Neue',sans-serif;letter-spacing:.14em;
112
+ font-size:16px;color:var(--muted);border:1px solid var(--line);padding:8px 15px;border-radius:999px;
113
+ background:rgba(46,26,82,.5)}
114
+ .status .led{width:9px;height:9px;border-radius:50%;background:var(--muted-2);transition:.3s}
115
+ .status.live{color:var(--hot);border-color:rgba(255,61,139,.5);background:rgba(255,61,139,.08)}
116
+ .status.live .led{background:var(--hot);box-shadow:0 0 12px var(--hot);animation:tw 1.1s infinite}
117
+
118
+ /* ============================================================
119
+ RUNDOWN — broadcast running order, not 4 identical cards.
120
+ nodes on a connecting track.
121
+ ============================================================ */
122
+ .rundown{position:relative;display:grid;grid-template-columns:repeat(4,1fr);
123
+ margin:8px 0 30px;padding:0 6px}
124
+ .rundown::before{content:"";position:absolute;top:19px;left:11%;right:11%;height:2px;
125
+ background:var(--line-soft)}
126
+ .cue{position:relative;display:flex;flex-direction:column;align-items:center;gap:9px;text-align:center}
127
+ .cue .node{width:40px;height:40px;border-radius:50%;display:grid;place-items:center;
128
+ background:var(--stage);border:2px solid var(--line);
129
+ font-family:'Bebas Neue',sans-serif;font-size:20px;color:var(--muted-2);
130
+ position:relative;z-index:1;transition:.35s var(--fx)}
131
+ .cue .ttl{font-family:Unbounded,sans-serif;font-weight:600;font-size:13px;color:var(--muted)}
132
+ .cue .sub{font-size:11px;color:var(--muted-2);letter-spacing:.01em}
133
+ /* done */
134
+ .cue.done .node{border-color:var(--cyan);color:var(--cyan);background:rgba(61,231,255,.08)}
135
+ .cue.done .ttl{color:var(--cream)}
136
+ /* active */
137
+ .cue.on .node{border-color:var(--gold);color:var(--gold);background:rgba(255,201,77,.12);
138
+ box-shadow:0 0 0 5px rgba(255,201,77,.1),0 0 22px rgba(255,201,77,.3)}
139
+ .cue.on .ttl{color:var(--gold)}
140
+ @media(prefers-reduced-motion:no-preference){
141
+ .cue.on .node{animation:cuePulse 2s infinite}
142
+ }
143
+ @keyframes cuePulse{0%,100%{box-shadow:0 0 0 5px rgba(255,201,77,.1),0 0 22px rgba(255,201,77,.28)}
144
+ 50%{box-shadow:0 0 0 8px rgba(255,201,77,.04),0 0 30px rgba(255,201,77,.45)}}
145
+
146
+ /* ============================================================
147
+ LAYOUT — control desk (left) vs the stage (right).
148
+ intentionally asymmetric; the two halves are treated differently.
149
+ ============================================================ */
150
+ .console{display:grid;grid-template-columns:362px 1fr;gap:20px;align-items:start}
151
+ @media(max-width:900px){.console{grid-template-columns:1fr}}
152
+
153
+ /* the desk: flat, utilitarian — it's the control surface, not a showpiece */
154
+ .desk{display:flex;flex-direction:column;gap:16px}
155
+ .card{background:var(--stage);border:1px solid var(--line-soft);border-radius:16px;padding:18px}
156
+ .card-h{display:flex;align-items:center;gap:10px;margin-bottom:14px}
157
+ .card-h .ic{color:var(--gold);font-size:18px}
158
+ .card-h h2{font-family:Unbounded,sans-serif;font-weight:600;font-size:14px;letter-spacing:.01em}
159
+ .card-h .meta{margin-left:auto;font-family:'Bebas Neue',sans-serif;letter-spacing:.07em;font-size:12px;color:var(--muted-2)}
160
+
161
+ /* roll tape drop zone — clapperboard motif */
162
+ .drop{position:relative;border:1.5px dashed var(--line);border-radius:13px;padding:26px 16px;
163
+ text-align:center;cursor:pointer;transition:.22s var(--fx);overflow:hidden;
164
+ background:radial-gradient(200px 110px at 50% -20%,rgba(255,201,77,.07),transparent 70%)}
165
+ .drop:hover{border-color:var(--gold);background:radial-gradient(200px 130px at 50% -10%,rgba(255,201,77,.12),transparent 70%)}
166
+ .drop:focus-visible{border-color:var(--cyan)}
167
+ .drop .clap{font-size:30px;color:var(--gold);display:inline-flex;margin-bottom:8px}
168
+ .drop .t{font-family:Unbounded,sans-serif;font-weight:600;font-size:15px}
169
+ .drop .s{font-size:12px;color:var(--muted);margin-top:4px}
170
+ /* loaded: a little filmstrip readout */
171
+ .drop .loaded{display:none;align-items:center;gap:12px;text-align:left}
172
+ .drop.loaded{border-style:solid;border-color:rgba(61,231,255,.45);background:rgba(61,231,255,.05);cursor:default}
173
+ .drop.loaded .ask{display:none}
174
+ .drop.loaded .loaded{display:flex}
175
+ .strip{width:52px;height:52px;border-radius:9px;flex:none;position:relative;overflow:hidden;
176
+ background:linear-gradient(135deg,var(--raise),var(--stage));border:1px solid var(--line)}
177
+ .strip::before,.strip::after{content:"";position:absolute;top:4px;bottom:4px;width:5px;
178
+ background:repeating-linear-gradient(var(--night) 0 3px,transparent 3px 7px)}
179
+ .strip::before{left:3px}.strip::after{right:3px}
180
+ .strip .play{position:absolute;inset:0;display:grid;place-items:center;color:var(--cyan);font-size:16px}
181
+ .loaded .fn{font-family:Unbounded,sans-serif;font-weight:600;font-size:13px;color:var(--cream)}
182
+ .loaded .fd{font-family:'Bebas Neue',sans-serif;letter-spacing:.05em;font-size:13px;color:var(--cyan);margin-top:3px}
183
+
184
+ /* buttons */
185
+ .btn{width:100%;border:0;cursor:pointer;font-family:Unbounded,sans-serif;font-weight:600;font-size:14px;
186
+ color:var(--night);padding:14px;border-radius:12px;display:flex;align-items:center;justify-content:center;
187
+ gap:9px;transition:.18s var(--fx)}
188
+ .btn:disabled{opacity:.4;cursor:not-allowed;background:var(--raise);color:var(--muted-2)}
189
+ .btn.cyan{background:linear-gradient(135deg,var(--cyan),#16b6d8)}
190
+ .btn.cyan:not(:disabled):hover{transform:translateY(-2px);box-shadow:0 12px 30px rgba(61,231,255,.3)}
191
+ .btn.show{font-size:17px;padding:17px;background:linear-gradient(110deg,var(--gold),var(--hot));position:relative;overflow:hidden}
192
+ .btn.show:not(:disabled):hover{transform:translateY(-2px);box-shadow:0 16px 38px rgba(255,61,139,.4)}
193
+ .btn.show:not(:disabled)::after{content:"";position:absolute;top:0;bottom:0;width:40%;
194
+ background:linear-gradient(90deg,transparent,rgba(255,255,255,.35),transparent);
195
+ transform:translateX(-200%)}
196
+ @media(prefers-reduced-motion:no-preference){
197
+ .btn.show:not(:disabled)::after{animation:shine 3.4s ease-in-out 1.2s infinite}
198
+ }
199
+ @keyframes shine{0%,100%{transform:translateX(-220%)}40%,60%{transform:translateX(360%)}}
200
+ .mt{margin-top:14px}
201
+
202
+ /* casting sweep bar */
203
+ .sweep{height:6px;border-radius:4px;margin-top:13px;background:var(--night-2);position:relative;overflow:hidden;
204
+ opacity:0;height:0;margin-top:0;transition:.25s}
205
+ .casting .sweep{opacity:1;height:6px;margin-top:13px}
206
+ .sweep::after{content:"";position:absolute;inset:0;width:36%;
207
+ background:linear-gradient(90deg,transparent,var(--gold),transparent)}
208
+ @media(prefers-reduced-motion:no-preference){.sweep::after{animation:sw 1s linear infinite}}
209
+ @keyframes sw{from{transform:translateX(-120%)}to{transform:translateX(330%)}}
210
+
211
+ /* ============================================================
212
+ THE STAGE — Star Select. theatrical, spotlit well.
213
+ ============================================================ */
214
+ .stage-panel{background:
215
+ radial-gradient(80% 60% at 50% -8%, rgba(255,201,77,.07), transparent 60%),
216
+ linear-gradient(180deg,var(--stage),var(--night-2));
217
+ border:1px solid var(--line);border-radius:20px;padding:20px;
218
+ box-shadow:inset 0 1px 0 rgba(255,243,233,.04), 0 24px 60px rgba(8,2,20,.5)}
219
+ .stage-h{display:flex;align-items:center;gap:11px;margin-bottom:16px}
220
+ .stage-h .ic{color:var(--gold);font-size:19px}
221
+ .stage-h h2{font-family:Unbounded,sans-serif;font-weight:700;font-size:16px}
222
+ .stage-h .count{margin-left:auto;white-space:nowrap;font-family:'Bebas Neue',sans-serif;letter-spacing:.08em;font-size:14px;
223
+ color:var(--muted);background:rgba(46,26,82,.6);padding:4px 11px;border-radius:8px}
224
+
225
+ /* empty state */
226
+ .empty{display:grid;place-items:center;min-height:330px;text-align:center;padding:20px}
227
+ .empty .spot{width:96px;height:96px;border-radius:50%;display:grid;place-items:center;margin-bottom:16px;
228
+ color:var(--muted-2);font-size:46px;
229
+ background:radial-gradient(circle at 50% 30%,rgba(255,201,77,.08),transparent 70%)}
230
+ .empty .t{font-family:Unbounded,sans-serif;font-weight:600;font-size:17px;color:var(--cream);margin-bottom:6px}
231
+ .empty .s{font-size:13px;color:var(--muted);max-width:300px}
232
+
233
+ /* star grid */
234
+ .roster{display:grid;grid-template-columns:repeat(auto-fill,minmax(164px,1fr));gap:16px}
235
+
236
+ /* ---- the star card (one reusable block) ----
237
+ per-card character color is set inline as --c. */
238
+ /* base state is fully visible — entrance is driven via WAAPI in JS so a
239
+ backgrounded tab never leaves cards stuck invisible. */
240
+ .star{--c:var(--gold);position:relative;border-radius:16px;overflow:hidden;
241
+ background:linear-gradient(180deg,var(--stage-2),var(--stage));
242
+ border:1px solid var(--line);transition:.25s var(--fx)}
243
+ .star:hover{transform:translateY(-3px);border-color:color-mix(in srgb,var(--c) 60%,transparent)}
244
+
245
+ /* portrait well with a spotlight beam tinted to the character color,
246
+ and an angular framed headshot placeholder (agent-select energy) */
247
+ .portrait{position:relative;height:138px;overflow:hidden;
248
+ background:radial-gradient(90% 80% at 50% 120%,color-mix(in srgb,var(--c) 22%,var(--night-2)),var(--night-2))}
249
+ .portrait .beam{position:absolute;top:-30px;left:50%;width:150px;height:170px;transform:translateX(-50%);
250
+ pointer-events:none;clip-path:polygon(40% 0,60% 0,100% 100%,0 100%);
251
+ background:linear-gradient(180deg,color-mix(in srgb,var(--c) 55%,transparent),transparent 70%);opacity:.7}
252
+ .portrait .head-ph{position:absolute;left:50%;bottom:-6px;transform:translateX(-50%);
253
+ color:color-mix(in srgb,var(--c) 50%,#ffffff);opacity:.4}
254
+ .portrait .frame{position:absolute;inset:8px;border:1.5px solid color-mix(in srgb,var(--c) 35%,transparent);
255
+ border-radius:11px;pointer-events:none}
256
+ .portrait .idx{position:absolute;top:10px;left:10px;font-family:'Bebas Neue',sans-serif;font-size:14px;
257
+ color:var(--cream);background:rgba(15,6,30,.7);padding:2px 7px;border-radius:7px;display:flex;align-items:center;gap:3px}
258
+ .portrait .idx .st{color:var(--c)}
259
+ .tier{position:absolute;top:10px;right:10px;font-family:'Bebas Neue',sans-serif;font-size:11px;letter-spacing:.02em;
260
+ white-space:nowrap;color:var(--night);background:var(--c);padding:3px 8px;border-radius:7px;
261
+ box-shadow:0 2px 10px color-mix(in srgb,var(--c) 50%,transparent)}
262
+
263
+ /* crown drops in on naming */
264
+ /* crown lifts in above the star's head on naming */
265
+ .crown{position:absolute;top:50px;left:50%;transform:translateX(-50%) translateY(12px) scale(.4) rotate(-12deg);
266
+ opacity:0;width:30px;height:30px;color:var(--gold);
267
+ filter:drop-shadow(0 2px 6px color-mix(in srgb,var(--gold) 70%,transparent));
268
+ transition:.4s var(--pop)}
269
+ .star.named .crown{opacity:1;transform:translateX(-50%) scale(1) rotate(0)}
270
+
271
+ .star-body{padding:13px}
272
+ .star-body label{font-family:'Bebas Neue',sans-serif;letter-spacing:.08em;font-size:12px;color:var(--muted);
273
+ display:block;margin-bottom:7px}
274
+ .star input{width:100%;background:var(--night-2);border:1px solid var(--line);color:var(--cream);
275
+ border-radius:10px;padding:10px 12px;font-family:Nunito;font-weight:800;font-size:14px;outline:none;transition:.16s}
276
+ .star input::placeholder{color:var(--muted-2);font-weight:600}
277
+ .star input:focus{border-color:var(--c);box-shadow:0 0 0 3px color-mix(in srgb,var(--c) 25%,transparent)}
278
+
279
+ /* crowned: card lights in its own character color */
280
+ .star.named{border-color:color-mix(in srgb,var(--c) 65%,transparent);
281
+ box-shadow:0 0 0 1px color-mix(in srgb,var(--c) 30%,transparent),0 10px 30px color-mix(in srgb,var(--c) 18%,transparent)}
282
+ .star.named .portrait{background:radial-gradient(90% 90% at 50% 120%,color-mix(in srgb,var(--c) 40%,var(--night-2)),var(--night-2))}
283
+ .star.named .portrait .beam{opacity:1}
284
+ .star.named .head-ph{opacity:.6}
285
+
286
+ /* ============================================================
287
+ VIBES (narration modes) + COCO host
288
+ ============================================================ */
289
+ .vibes{display:grid;grid-template-columns:repeat(2,1fr);gap:10px}
290
+ .vibe{display:flex;align-items:center;gap:11px;padding:12px;border-radius:13px;cursor:pointer;color:var(--cream);
291
+ border:1px solid var(--line-soft);background:var(--stage-2);transition:.18s var(--fx);text-align:left}
292
+ .vibe:hover{border-color:var(--violet);transform:translateY(-1px)}
293
+ .vibe .ic{font-size:20px;color:var(--muted);display:inline-flex;transition:.18s}
294
+ .vibe>span:last-child{display:flex;flex-direction:column;gap:2px;min-width:0}
295
+ .vibe .vt{display:block;font-family:Unbounded,sans-serif;font-weight:600;font-size:13px;color:var(--cream)}
296
+ .vibe .vd{display:block;font-size:11px;color:var(--muted);line-height:1.25}
297
+ .vibe[aria-pressed="true"]{border-color:var(--vc,var(--hot));background:color-mix(in srgb,var(--vc,var(--hot)) 12%,var(--stage-2))}
298
+ .vibe[aria-pressed="true"] .ic{color:var(--vc,var(--hot))}
299
+
300
+ /* Coco — the host bug. re-dresses per vibe. */
301
+ .coco{display:flex;align-items:center;gap:13px;margin-top:14px;padding:13px 14px;border-radius:14px;
302
+ background:linear-gradient(120deg,color-mix(in srgb,var(--vc,var(--violet)) 12%,var(--stage-2)),var(--stage-2));
303
+ border:1px solid color-mix(in srgb,var(--vc,var(--violet)) 30%,var(--line-soft));transition:.35s var(--fx)}
304
+ .coco .av{position:relative;width:46px;height:46px;border-radius:13px;flex:none;display:grid;place-items:center;
305
+ background:radial-gradient(circle at 50% 30%,color-mix(in srgb,var(--vc,var(--violet)) 70%,#fff 5%),var(--vc,var(--violet)));
306
+ box-shadow:0 6px 18px color-mix(in srgb,var(--vc,var(--violet)) 40%,transparent);transition:.35s var(--fx)}
307
+ .coco .av .bird{width:30px;height:30px;color:var(--night);filter:drop-shadow(0 1px 1px rgba(255,255,255,.3))}
308
+ /* accessory badge sits on the avatar, swapped per vibe */
309
+ .coco .acc{position:absolute;right:-6px;bottom:-6px;width:22px;height:22px;border-radius:50%;
310
+ display:grid;place-items:center;background:var(--night);border:2px solid var(--vc,var(--violet));
311
+ color:var(--vc,var(--violet));font-size:11px}
312
+ .coco .acc svg{width:12px;height:12px}
313
+ .coco .say{font-size:13px;line-height:1.45}
314
+ .coco .say b{color:var(--vc,var(--gold))}
315
+ .coco .who{font-family:'Bebas Neue',sans-serif;letter-spacing:.1em;font-size:11px;color:var(--vc,var(--violet));
316
+ display:block;margin-bottom:2px}
317
+
318
+ /* privacy footnote — visible, not buried */
319
+ .note{margin-top:26px;text-align:center;font-size:12.5px;color:var(--muted);line-height:1.6}
320
+ .note b{color:var(--cream)}
321
+ .note .lock{color:var(--cyan);vertical-align:-2px;margin-right:5px}
322
+
323
+ /* ============================================================
324
+ VIEW SWITCH
325
+ ============================================================ */
326
+ .view{display:none}
327
+ .view.active{display:block;animation:viewIn .4s var(--fx)}
328
+ @keyframes viewIn{from{opacity:0;transform:translateY(8px)}to{opacity:1;transform:none}}
329
+
330
+ /* ============================================================
331
+ BROADCAST PLAYER
332
+ ============================================================ */
333
+ .booth-top{display:flex;align-items:center;justify-content:space-between;gap:16px;margin-bottom:18px;flex-wrap:wrap}
334
+ .back{display:inline-flex;align-items:center;gap:8px;background:var(--stage);border:1px solid var(--line);
335
+ color:var(--cream);font-family:Nunito;font-weight:700;font-size:13px;padding:9px 14px;border-radius:10px;cursor:pointer;transition:.16s}
336
+ .back:hover{border-color:var(--violet)}
337
+ .orient{display:flex;gap:4px;background:var(--stage);border:1px solid var(--line-soft);padding:4px;border-radius:11px}
338
+ .orient button{background:transparent;border:0;color:var(--muted);font-family:'Bebas Neue',sans-serif;letter-spacing:.06em;
339
+ font-size:13px;padding:7px 14px;border-radius:8px;cursor:pointer;transition:.16s}
340
+ .orient button[aria-pressed="true"]{background:var(--raise);color:var(--gold)}
341
+
342
+ .booth{display:grid;grid-template-columns:1fr 320px;gap:18px;align-items:start}
343
+ /* portrait: the clip is a phone-sized vertical frame, never a giant column-filling block.
344
+ capped width keeps the 9:16 height sane and the play-by-play rail beside it. */
345
+ .booth.portrait{grid-template-columns:minmax(0,340px) minmax(0,1fr)}
346
+ .booth.portrait .screen-wrap{max-width:340px}
347
+ @media(max-width:840px){
348
+ .booth,.booth.square{grid-template-columns:1fr}
349
+ .booth.portrait{grid-template-columns:1fr}
350
+ .booth.portrait .screen-wrap{margin-inline:auto}
351
+ }
352
+
353
+ /* the screen */
354
+ .screen-wrap{background:#000;border-radius:18px;overflow:hidden;border:1px solid var(--line);
355
+ box-shadow:0 24px 70px rgba(0,0,0,.6);position:relative}
356
+ .screen{position:relative;width:100%;aspect-ratio:16/9;background:
357
+ repeating-linear-gradient(135deg,#1c0f33 0 18px,#170c2b 18px 36px);
358
+ display:grid;place-items:center}
359
+ .booth.portrait .screen{aspect-ratio:9/16}
360
+ .booth.square .screen{aspect-ratio:1/1}
361
+ .screen .ph{font-family:'Bebas Neue',sans-serif;letter-spacing:.12em;color:var(--muted-2);font-size:15px;
362
+ text-align:center;padding:0 20px}
363
+ .screen .ph svg{width:40px;height:40px;margin:0 auto 10px;color:var(--line);display:block}
364
+
365
+ /* on-air bug */
366
+ .bug{position:absolute;top:14px;left:14px;display:flex;align-items:center;gap:8px;white-space:nowrap;
367
+ background:rgba(15,6,30,.6);backdrop-filter:blur(4px);padding:7px 12px;border-radius:9px;
368
+ font-family:'Bebas Neue',sans-serif;letter-spacing:.12em;font-size:14px;color:var(--cream)}
369
+ .bug .dot{width:10px;height:10px;border-radius:50%;background:var(--hot);box-shadow:0 0 10px var(--hot)}
370
+ @media(prefers-reduced-motion:no-preference){.bug .dot{animation:tw 1.1s infinite}}
371
+
372
+ /* timecode */
373
+ .tc{position:absolute;top:14px;right:14px;background:rgba(15,6,30,.6);backdrop-filter:blur(4px);
374
+ padding:7px 12px;border-radius:9px;font-family:'Bebas Neue',sans-serif;letter-spacing:.1em;font-size:15px;
375
+ color:var(--cyan);font-variant-numeric:tabular-nums}
376
+
377
+ /* lower-third chyron */
378
+ .chyron{position:absolute;left:14px;right:14px;bottom:14px;border-radius:12px;overflow:hidden;
379
+ background:linear-gradient(100deg,rgba(15,6,30,.92),rgba(36,18,65,.82));
380
+ border:1px solid color-mix(in srgb,var(--cc,var(--hot)) 40%,var(--line));
381
+ box-shadow:0 10px 30px rgba(0,0,0,.4);
382
+ transform:translateY(130%);opacity:0;transition:.45s var(--fx)}
383
+ .chyron.in{transform:none;opacity:1}
384
+ .chyron .bar{height:4px;background:linear-gradient(90deg,var(--cc,var(--hot)),var(--gold))}
385
+ .chyron .row{display:flex;align-items:stretch}
386
+ .chyron .who{display:flex;align-items:center;font-family:'Bebas Neue',sans-serif;letter-spacing:.06em;
387
+ font-size:17px;color:var(--night);background:var(--cc,var(--hot));padding:10px 14px;white-space:nowrap}
388
+ .chyron .line{padding:10px 16px;font-family:Nunito;font-weight:700;font-size:15px;color:var(--cream);
389
+ display:flex;align-items:center;line-height:1.3}
390
+ @media(prefers-reduced-motion:no-preference){
391
+ .chyron.in .line{animation:sharpen .5s var(--fx)}
392
+ }
393
+ @keyframes sharpen{from{filter:blur(7px);opacity:.3}to{filter:blur(0);opacity:1}}
394
+
395
+ /* transport */
396
+ .transport{display:flex;align-items:center;gap:14px;margin-top:14px}
397
+ .transport .pp{width:48px;height:48px;border-radius:50%;border:0;cursor:pointer;flex:none;display:grid;place-items:center;
398
+ background:linear-gradient(135deg,var(--gold),var(--hot));color:var(--night);font-size:20px;transition:.16s}
399
+ .transport .pp:hover{transform:scale(1.05)}
400
+ .scrub{flex:1;height:6px;border-radius:4px;background:var(--raise);position:relative;cursor:pointer}
401
+ .scrub .fill{position:absolute;left:0;top:0;bottom:0;border-radius:4px;width:0;
402
+ background:linear-gradient(90deg,var(--hot),var(--gold))}
403
+ .scrub .knob{position:absolute;top:50%;width:14px;height:14px;border-radius:50%;background:var(--cream);
404
+ transform:translate(-50%,-50%);left:0;box-shadow:0 2px 6px rgba(0,0,0,.5)}
405
+
406
+ /* play-by-play rail */
407
+ .pbp{background:var(--stage);border:1px solid var(--line-soft);border-radius:16px;overflow:hidden;
408
+ display:flex;flex-direction:column;max-height:520px}
409
+ .pbp .pbp-h{display:flex;align-items:center;gap:9px;padding:14px 16px;border-bottom:1px solid var(--line-soft)}
410
+ .pbp .pbp-h .ic{color:var(--gold);font-size:16px}
411
+ .pbp .pbp-h h3{font-family:Unbounded,sans-serif;font-weight:600;font-size:13px}
412
+ .pbp .pbp-h .ct{margin-left:auto;font-family:'Bebas Neue',sans-serif;letter-spacing:.06em;font-size:12px;color:var(--muted-2)}
413
+ .pbp .lines{overflow-y:auto;padding:6px}
414
+ .pbp .pl{display:flex;gap:11px;padding:11px 12px;border-radius:10px;cursor:pointer;transition:.15s;align-items:baseline}
415
+ .pbp .pl:hover{background:var(--stage-2)}
416
+ .pbp .pl .t{font-family:'Bebas Neue',sans-serif;letter-spacing:.04em;font-size:14px;color:var(--muted-2);
417
+ flex:none;width:42px;font-variant-numeric:tabular-nums;padding-top:1px}
418
+ .pbp .pl .tx{font-size:13px;line-height:1.45;color:var(--muted)}
419
+ .pbp .pl .tx b{color:var(--cream);font-weight:800}
420
+ .pbp .pl.now{background:color-mix(in srgb,var(--cc,var(--hot)) 14%,var(--stage-2))}
421
+ .pbp .pl.now .t{color:var(--cc,var(--hot))}
422
+ .pbp .pl.now .tx{color:var(--cream)}
423
+ .pbp .pl.now .tx b{color:var(--cc,var(--hot))}
424
+
425
+ /* export bar */
426
+ .export{padding:13px 16px;border-top:1px solid var(--line-soft);background:var(--night-2)}
427
+ .export .btn-srt{width:100%;display:flex;align-items:center;justify-content:center;gap:9px;cursor:pointer;
428
+ background:var(--stage-2);border:1px solid var(--line);color:var(--cream);font-family:Nunito;font-weight:700;
429
+ font-size:13px;padding:12px;border-radius:11px;transition:.16s}
430
+ .export .btn-srt:hover{border-color:var(--cyan);color:var(--cyan)}
431
+ .export .btn-srt .ic{color:var(--cyan);font-size:16px}
432
+ .export .hint{text-align:center;font-size:11px;color:var(--muted-2);margin-top:8px}
433
+ .export .hint b{color:var(--muted)}
434
+
435
+ /* confetti */
436
+ .confetti{position:fixed;top:-20px;width:11px;height:14px;z-index:90;pointer-events:none;border-radius:2px}
437
+ @media(prefers-reduced-motion:no-preference){.confetti{animation:fall linear forwards}}
438
+ @keyframes fall{to{transform:translateY(105vh) rotate(540deg);opacity:.2}}
439
+
440
+ /* ============================================================
441
+ UTILITIES
442
+ ============================================================ */
443
+ :focus-visible{outline:2px solid var(--cyan);outline-offset:3px;border-radius:8px}
444
+ .toast{position:fixed;left:50%;bottom:28px;transform:translateX(-50%) translateY(20px);opacity:0;
445
+ background:var(--stage-2);border:1px solid var(--cyan);color:var(--cream);padding:12px 20px;border-radius:12px;
446
+ font-weight:700;font-size:13px;z-index:95;transition:.3s var(--fx);display:flex;align-items:center;gap:9px;
447
+ box-shadow:0 14px 40px rgba(0,0,0,.5)}
448
+ .toast.show{transform:translateX(-50%);opacity:1}
449
+ .toast .ic{color:var(--cyan)}
450
+
451
+ @media(prefers-reduced-motion:reduce){*{animation:none!important;transition:none!important}}
452
+
453
+ @media(max-width:560px){
454
+ .masthead{gap:14px}
455
+ .rundown .sub{display:none}
456
+ .vibes{grid-template-columns:1fr}
457
+ .roster{grid-template-columns:repeat(auto-fill,minmax(140px,1fr))}
458
+ }
459
+
460
+ /* ============================================================
461
+ INTEGRATION ADDITIONS — real <video> in the screen, real face
462
+ crops in star cards (added for the Gradio wiring)
463
+ ============================================================ */
464
+ .screen-vid{position:absolute;inset:0;width:100%;height:100%;object-fit:contain;background:#000;display:none}
465
+ .screen.has-video .ph{display:none}
466
+ .screen.has-video .screen-vid{display:block}
467
+ .portrait .head-img{position:absolute;inset:0;width:100%;height:100%;object-fit:cover}
468
+ .star.named .head-img{filter:saturate(1.05)}
marquee.js ADDED
@@ -0,0 +1,412 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /* ============================================================
2
+ MARQUEE — flow engine, wired to the Gradio backend.
3
+ Runs as a module inside the app iframe.
4
+ Presentation code is unchanged from the design; the mock data
5
+ hooks (casting, commentary, playback) now call the real pipeline.
6
+ ============================================================ */
7
+ const $ = (s, r = document) => r.querySelector(s);
8
+ const $$ = (s, r = document) => [...r.querySelectorAll(s)];
9
+ const reduce = matchMedia('(prefers-reduced-motion:reduce)').matches;
10
+ const pad = n => String(n).padStart(2, '0');
11
+
12
+ /* ---------- Plain fetch API helpers — talk to FastAPI endpoints ---------- */
13
+ const BASE = window.location.origin;
14
+
15
+ async function apiScan(file) {
16
+ const fd = new FormData();
17
+ fd.append('file', file, file.name);
18
+ const res = await fetch(`${BASE}/api/scan`, {method:'POST', body: fd});
19
+ const data = await res.json();
20
+ if (!data.ok) throw new Error(data.error || 'scan failed');
21
+ return data;
22
+ }
23
+
24
+ async function apiGenerate(sessionId, names, vibe) {
25
+ const res = await fetch(`${BASE}/api/generate`, {
26
+ method: 'POST',
27
+ headers: {'Content-Type': 'application/json'},
28
+ body: JSON.stringify({session_id: sessionId, names, vibe})
29
+ });
30
+ const data = await res.json();
31
+ if (!data.ok) throw new Error(data.error || 'generate failed');
32
+ return data;
33
+ }
34
+
35
+ /* ---------- session state from the backend ---------- */
36
+ let SESSION = null, VIDEO_URL = null, DUR = 0, REAL_SCRIPT = [], selectedFile = null;
37
+
38
+ /* ---------- auto-fit the iframe to content (same-origin srcdoc) ---------- */
39
+ function fitFrame() {
40
+ try { if (window.frameElement) window.frameElement.style.height = document.documentElement.scrollHeight + 'px'; }
41
+ catch (e) {}
42
+ }
43
+ new ResizeObserver(fitFrame).observe(document.body);
44
+ window.addEventListener('load', fitFrame);
45
+
46
+ /* ---------- live clock ---------- */
47
+ const clocks = $$('#clock');
48
+ function fmtClock(d){return `${pad(d.getHours())}:${pad(d.getMinutes())}:${pad(d.getSeconds())}`;}
49
+ setInterval(()=>{const t=fmtClock(new Date());clocks.forEach(c=>c.textContent=t);},1000);
50
+ clocks.forEach(c=>c.textContent=fmtClock(new Date()));
51
+
52
+ /* ---------- status pill ---------- */
53
+ const status=$('#status'), statusTxt=$('#statusTxt');
54
+ function setStatus(txt,live){statusTxt.textContent=txt;status.classList.toggle('live',!!live);}
55
+
56
+ /* ---------- rundown ---------- */
57
+ const cues=$$('#rundown .cue');
58
+ function setStep(n){
59
+ cues.forEach(c=>{
60
+ const s=+c.dataset.step;
61
+ c.classList.toggle('done',s<n);
62
+ c.classList.toggle('on',s===n);
63
+ if(s<n)c.querySelector('.node').innerHTML='<svg class="i" style="width:18px;height:18px"><use href="#i-check"/></svg>';
64
+ else c.querySelector('.node').textContent='0'+s;
65
+ });
66
+ }
67
+
68
+ /* ---------- cast colors + tiers (presentation) ---------- */
69
+ const COLORS=['--star-gold','--star-hot','--star-cyan','--star-violet','--star-lime','--star-tang'];
70
+ let stars=[]; // {name, color, tier, crop}
71
+
72
+ /* ---------- VIBES — Coco's wardrobe + the show's voice ----------
73
+ NOTE: the play-by-play LINES are no longer templated here — the real
74
+ commentary comes from the VLM. We keep Coco's persona + colors. */
75
+ const VIBES={
76
+ football:{ acc:'#i-whistle', vc:'--star-lime', who:'COCO · TOUCHLINE',
77
+ coco:"Whistle's on. I'm calling this one like a cup final — keep your eyes on the pitch!"},
78
+ diva:{ acc:'#i-glam', vc:'--star-hot', who:'COCO · RED CARPET',
79
+ coco:"Shades on, boa fluffed. Darling, we are SO ready for the carpet. Bring the drama."},
80
+ wildlife:{ acc:'#i-paw', vc:'--star-cyan', who:'COCO · FIELD UNIT',
81
+ coco:"*whispering* …we must be very quiet now. A rare specimen has entered the frame."},
82
+ boxing:{ acc:'#i-glove', vc:'--star-tang', who:'COCO · RINGSIDE',
83
+ coco:"Gloves laced, ringside seat warm. Ladies and gentlemen… in the RED corner!"}
84
+ };
85
+ let currentVibe=null;
86
+
87
+ /* ---------- ROLL TAPE (real file) ---------- */
88
+ const drop=$('#drop'), castBtn=$('#castBtn'), liveBtn=$('#liveBtn'), rollCard=$('#rollCard');
89
+ const fileInput=$('#fileInput'), fileName=$('#fileName'), fileDur=$('#fileDur');
90
+
91
+ function handleFile(file){
92
+ if(!file) return;
93
+ selectedFile=file;
94
+ drop.classList.add('loaded');
95
+ fileName.textContent=file.name;
96
+ fileDur.textContent='reading…';
97
+ const url=URL.createObjectURL(file);
98
+ const probe=document.createElement('video'); probe.preload='metadata';
99
+ probe.onloadedmetadata=()=>{ fileDur.textContent=fmt(probe.duration)+' · ready to cast'; URL.revokeObjectURL(url); };
100
+ probe.onerror=()=>{ fileDur.textContent='ready to cast'; };
101
+ probe.src=url;
102
+ castBtn.disabled=false;
103
+ setStatus('Tape loaded'); toast('Tape loaded — ready to cast');
104
+ }
105
+ drop.addEventListener('click',()=>fileInput.click());
106
+ drop.addEventListener('keydown',e=>{if(e.key==='Enter'||e.key===' '){e.preventDefault();fileInput.click();}});
107
+ fileInput.addEventListener('change',e=>handleFile(e.target.files[0]));
108
+ ['dragover','dragenter'].forEach(ev=>drop.addEventListener(ev,e=>{e.preventDefault();drop.style.borderColor='var(--cyan)';}));
109
+ ['dragleave','drop'].forEach(ev=>drop.addEventListener(ev,e=>{
110
+ e.preventDefault();drop.style.borderColor='';
111
+ if(ev==='drop'&&e.dataTransfer.files[0])handleFile(e.dataTransfer.files[0]);
112
+ }));
113
+
114
+ /* ---------- CASTING (real scan) ---------- */
115
+ const stagePanel=$('#stagePanel'), stageEmpty=$('#stageEmpty'), roster=$('#roster'), starCount=$('#starCount');
116
+ castBtn.addEventListener('click',startCasting);
117
+
118
+ async function startCasting(){
119
+ if(castBtn.disabled||!selectedFile) return;
120
+ castBtn.disabled=true; rollCard.classList.add('casting');
121
+ setStep(2); setStatus('Casting…');
122
+ castBtn.innerHTML='<svg class="i"><use href="#i-spot"/></svg> Scanning the tape…';
123
+ try{
124
+ toast('Uploading tape…');
125
+ const data = await apiScan(selectedFile);
126
+ SESSION=data.session_id; VIDEO_URL=data.video_url; DUR=data.duration||0;
127
+ setBoothOrient(data.w,data.h);
128
+ const vid=$('#vid'); vid.src=VIDEO_URL; $('#screen').classList.add('has-video');
129
+ revealCast(data.stars||[]);
130
+ }catch(err){
131
+ setStatus('Casting failed');
132
+ toast('Casting failed: '+(err.message||err));
133
+ rollCard.classList.remove('casting');
134
+ castBtn.disabled=false;
135
+ castBtn.innerHTML='<svg class="i"><use href="#i-spot"/></svg> Find the stars';
136
+ }
137
+ }
138
+
139
+ function revealCast(starData){
140
+ rollCard.classList.remove('casting');
141
+ castBtn.innerHTML='<svg class="i"><use href="#i-spot"/></svg> Re-cast';
142
+ castBtn.disabled=false;
143
+ setStep(3); setStatus(starData.length+' stars found');
144
+
145
+ stars = starData.map((s,i)=>({name:'',tier:s.tier||'STAR',color:COLORS[i%COLORS.length],crop:s.crop||null}));
146
+ stageEmpty.style.display='none';
147
+ roster.hidden=false; roster.innerHTML='';
148
+ starCount.textContent=stars.length+' STARS FOUND';
149
+ stars.forEach((s,i)=>roster.appendChild(buildStar(s,i)));
150
+
151
+ if(!stars.length){
152
+ stageEmpty.style.display='';
153
+ stageEmpty.querySelector('.t').textContent='No stars spotted';
154
+ stageEmpty.querySelector('.s').innerHTML='No faces found — but the show still goes on. Pick a vibe and go live for nameless narration.';
155
+ roster.hidden=true;
156
+ }
157
+ toast(stars.length?stars.length+' stars found — give them stage names':'No faces found — you can still go live');
158
+ refreshLive();
159
+ }
160
+
161
+ /* ---------- THE STAR CARD (one reusable block) ---------- */
162
+ function buildStar(s,i){
163
+ const el=document.createElement('div');
164
+ el.className='star';
165
+ el.style.setProperty('--c',`var(${s.color})`);
166
+ if(!reduce){
167
+ el.animate(
168
+ [{opacity:0,transform:'translateY(14px) scale(.98)'},{opacity:1,transform:'none'}],
169
+ {duration:480,delay:i*90,easing:'cubic-bezier(.2,.9,.25,1)',fill:'backwards'}
170
+ );
171
+ }
172
+ const portrait = s.crop
173
+ ? `<img class="head-img" src="${s.crop}" alt="Player ${i+1}">`
174
+ : `<svg class="i head-ph" style="width:64px;height:68px"><use href="#i-portrait"/></svg>`;
175
+ el.innerHTML=`
176
+ <div class="portrait">
177
+ <div class="beam"></div>
178
+ ${portrait}
179
+ <div class="frame"></div>
180
+ <span class="idx"><span class="st">★</span>${i+1}</span>
181
+ <span class="tier">${s.tier}</span>
182
+ <svg class="i crown"><use href="#i-crown"/></svg>
183
+ </div>
184
+ <div class="star-body">
185
+ <label for="star${i}">Stage name</label>
186
+ <input id="star${i}" type="text" placeholder="e.g. The Maestro" maxlength="22" autocomplete="off">
187
+ </div>`;
188
+ const input=el.querySelector('input');
189
+ input.addEventListener('input',()=>{
190
+ const v=input.value.trim();
191
+ s.name=v;
192
+ const wasNamed=el.classList.contains('named');
193
+ el.classList.toggle('named',!!v);
194
+ if(v && !wasNamed && !reduce) popCrown(el);
195
+ refreshLive();
196
+ });
197
+ return el;
198
+ }
199
+ function popCrown(el){
200
+ const c=el.querySelector('.crown');
201
+ c.style.animation='none';void c.offsetWidth;
202
+ c.animate([{transform:'translateX(-50%) translateY(10px) scale(.4) rotate(-12deg)',opacity:0},
203
+ {transform:'translateX(-50%) scale(1.25) rotate(8deg)',opacity:1,offset:.6},
204
+ {transform:'translateX(-50%) scale(1) rotate(0)',opacity:1}],
205
+ {duration:480,easing:'cubic-bezier(.2,1.7,.4,1)'});
206
+ }
207
+
208
+ /* ---------- VIBES + COCO ---------- */
209
+ const vibesWrap=$('#vibes'), coco=$('#coco'), cocoLine=$('#cocoLine'),
210
+ cocoAcc=$('#cocoAcc'), cocoWho=coco.querySelector('.who');
211
+ vibesWrap.addEventListener('click',e=>{
212
+ const b=e.target.closest('.vibe'); if(!b)return;
213
+ $$('.vibe',vibesWrap).forEach(v=>v.setAttribute('aria-pressed','false'));
214
+ b.setAttribute('aria-pressed','true');
215
+ currentVibe=b.dataset.vibe;
216
+ dressCoco(currentVibe);
217
+ refreshLive();
218
+ });
219
+ function dressCoco(v){
220
+ const d=VIBES[v]; if(!d)return;
221
+ coco.style.setProperty('--vc',`var(${d.vc})`);
222
+ cocoAcc.innerHTML=`<svg><use href="${d.acc}"/></svg>`;
223
+ cocoWho.textContent=d.who;
224
+ cocoLine.textContent=d.coco;
225
+ if(!reduce){
226
+ coco.animate([{transform:'scale(.98)',opacity:.6},{transform:'scale(1)',opacity:1}],{duration:300,easing:'ease-out'});
227
+ const av=coco.querySelector('.av');
228
+ av.animate([{transform:'rotate(-8deg) scale(.9)'},{transform:'rotate(6deg) scale(1.08)'},{transform:'none'}],{duration:420,easing:'cubic-bezier(.2,1.7,.4,1)'});
229
+ }
230
+ }
231
+
232
+ /* ---------- LIVE-READY GATE ----------
233
+ ready when a vibe is picked (names optional — nameless narration is allowed) */
234
+ function namedCount(){return stars.filter(s=>s.name).length;}
235
+ function refreshLive(){
236
+ liveBtn.disabled=!(currentVibe && SESSION);
237
+ if(stars.length){
238
+ const n=namedCount();
239
+ starCount.textContent = n? `${n}/${stars.length} NAMED` : `${stars.length} STARS FOUND`;
240
+ }
241
+ }
242
+
243
+ /* ---------- SHOWTIME (real commentary) ---------- */
244
+ liveBtn.addEventListener('click',async ()=>{
245
+ if(liveBtn.disabled)return;
246
+ liveBtn.disabled=true;
247
+ liveBtn.innerHTML='<svg class="i"><use href="#i-star"/></svg> Rolling cameras…';
248
+ setStatus('Generating…');
249
+ try{
250
+ const names=stars.map(s=>s.name||'');
251
+ const data=await apiGenerate(SESSION, names, currentVibe);
252
+ REAL_SCRIPT=data.script||[];
253
+ setStep(4); setStatus('On air',true);
254
+ if(!reduce) confetti();
255
+ buildBroadcast();
256
+ setTimeout(()=>{switchView('broadcast');fitFrame();}, reduce?0:450);
257
+ }catch(err){
258
+ setStatus('Showtime failed');
259
+ toast('Showtime failed: '+(err.message||err));
260
+ }finally{
261
+ liveBtn.disabled=false;
262
+ liveBtn.innerHTML='<svg class="i"><use href="#i-star"/></svg> Showtime — go live';
263
+ }
264
+ });
265
+
266
+ /* ---------- VIEW SWITCH ---------- */
267
+ const setupView=$('#setupView'), broadcastView=$('#broadcastView');
268
+ function switchView(which){
269
+ const toBroadcast=which==='broadcast';
270
+ setupView.classList.toggle('active',!toBroadcast);
271
+ broadcastView.classList.toggle('active',toBroadcast);
272
+ window.scrollTo({top:0,behavior:reduce?'auto':'smooth'});
273
+ fitFrame();
274
+ }
275
+ $('#backBtn').addEventListener('click',()=>{switchView('setup');const v=$('#vid');if(v)v.pause();});
276
+
277
+ /* ============================================================
278
+ BROADCAST PLAYER — driven by a real <video>
279
+ ============================================================ */
280
+ let script=[]; // {t, who, text, cc}
281
+ const vid=$('#vid'), screen=$('#screen'), chyron=$('#chyron'), chyWho=$('#chyWho'), chyLine=$('#chyLine'),
282
+ linesEl=$('#lines'), pbpCt=$('#pbpCt'), tcEl=$('#tc'), fill=$('#fill'), knob=$('#knob'),
283
+ pp=$('#pp'), scrub=$('#scrub'), tcDur=$('#tcDur');
284
+ const ICON_PLAY='<svg class="i"><use href="#i-play"/></svg>';
285
+ const ICON_PAUSE='<svg class="i"><use href="#i-pause"/></svg>';
286
+
287
+ function fmt(s){s=isFinite(s)?s:0;return pad(Math.floor(s/60))+':'+pad(Math.floor(s%60));}
288
+
289
+ function buildBroadcast(){
290
+ const d=VIBES[currentVibe]||{vc:'--hot',who:'COCO'};
291
+ const who=(d.who||'COCO').replace('COCO · ','');
292
+ script=(REAL_SCRIPT||[]).map(l=>({t:+l.t||0, text:l.text||'', who, cc:d.vc}));
293
+ linesEl.innerHTML='';
294
+ pbpCt.textContent=script.length+' LINES';
295
+ if(!script.length){
296
+ linesEl.innerHTML='<div class="pl"><span class="tx">No commentary generated — try another clip.</span></div>';
297
+ }
298
+ script.forEach((l,i)=>{
299
+ const row=document.createElement('div');
300
+ row.className='pl'; row.dataset.i=i;
301
+ row.style.setProperty('--cc',`var(${l.cc})`);
302
+ row.innerHTML=`<span class="t">${fmt(l.t)}</span><span class="tx"><b>${l.who}</b> · ${l.text}</span>`;
303
+ row.addEventListener('click',()=>seek(l.t));
304
+ linesEl.appendChild(row);
305
+ });
306
+ if(vid){ try{vid.currentTime=0;}catch(e){} }
307
+ activeLine=-1; renderPlayhead();
308
+ }
309
+
310
+ /* playback engine — reads the real video element */
311
+ let activeLine=-1, rafP=null;
312
+ function dur(){ return DUR || (vid&&vid.duration) || 0; }
313
+ function renderPlayhead(){
314
+ const D=dur(), cur=(vid&&vid.currentTime)||0;
315
+ const pct=D?(cur/D)*100:0;
316
+ fill.style.width=pct+'%'; knob.style.left=pct+'%'; tcEl.textContent=fmt(cur);
317
+ let idx=-1;
318
+ for(let i=0;i<script.length;i++){if(script[i].t<=cur)idx=i;else break;}
319
+ if(idx!==activeLine){
320
+ activeLine=idx;
321
+ $$('.pl',linesEl).forEach((r,i)=>r.classList.toggle('now',i===idx));
322
+ showChyron(idx);
323
+ if(idx>=0){const r=linesEl.children[idx];const top=r.offsetTop-linesEl.clientHeight/2+r.clientHeight/2;
324
+ linesEl.scrollTo({top,behavior:reduce?'auto':'smooth'});}
325
+ }
326
+ }
327
+ function showChyron(idx){
328
+ if(idx<0){chyron.classList.remove('in');return;}
329
+ const l=script[idx];
330
+ chyron.classList.remove('in');
331
+ requestAnimationFrame(()=>{
332
+ chyron.style.setProperty('--cc',`var(${l.cc})`);
333
+ chyWho.textContent=l.who; chyLine.textContent=l.text;
334
+ requestAnimationFrame(()=>chyron.classList.add('in'));
335
+ });
336
+ }
337
+ function loop(){ renderPlayhead(); if(vid && !vid.paused && !vid.ended) rafP=requestAnimationFrame(loop); }
338
+ function seek(t){ if(vid) vid.currentTime=Math.max(0,Math.min(dur(),t)); }
339
+
340
+ if(vid){
341
+ vid.addEventListener('loadedmetadata',()=>{ if(vid.duration&&isFinite(vid.duration))DUR=vid.duration; tcDur.textContent=fmt(dur()); renderPlayhead(); });
342
+ vid.addEventListener('play',()=>{ pp.innerHTML=ICON_PAUSE; loop(); });
343
+ vid.addEventListener('pause',()=>{ pp.innerHTML=ICON_PLAY; if(rafP)cancelAnimationFrame(rafP); });
344
+ vid.addEventListener('ended',()=>{ pp.innerHTML=ICON_PLAY; });
345
+ vid.addEventListener('timeupdate',renderPlayhead);
346
+ }
347
+ pp.addEventListener('click',()=>{ if(!vid)return; vid.paused?vid.play():vid.pause(); });
348
+ scrub.addEventListener('click',e=>{
349
+ const r=scrub.getBoundingClientRect();
350
+ seek(((e.clientX-r.left)/r.width)*dur());
351
+ });
352
+
353
+ /* orientation toggle (manual) + auto from real dims */
354
+ const booth=$('#booth');
355
+ function setBoothOrient(w,h){
356
+ booth.classList.remove('portrait','square');
357
+ let o='landscape';
358
+ if(w&&h){ if(Math.abs(w-h)/Math.max(w,h)<0.08) o='square'; else if(h>w) o='portrait'; }
359
+ if(o!=='landscape') booth.classList.add(o);
360
+ $$('.orient button').forEach(x=>x.setAttribute('aria-pressed', String(x.dataset.orient===o)));
361
+ }
362
+ $$('.orient button').forEach(b=>b.addEventListener('click',()=>{
363
+ $$('.orient button').forEach(x=>x.setAttribute('aria-pressed','false'));
364
+ b.setAttribute('aria-pressed','true');
365
+ booth.classList.remove('portrait','square');
366
+ if(b.dataset.orient!=='landscape')booth.classList.add(b.dataset.orient);
367
+ fitFrame();
368
+ }));
369
+
370
+ /* ---------- SRT EXPORT (CapCut-ready) ---------- */
371
+ $('#srtBtn').addEventListener('click',()=>{
372
+ if(!script.length){toast('Go live first');return;}
373
+ const D=dur();
374
+ const srt=script.map((l,i)=>{
375
+ const start=l.t, end=Math.min(D||l.t+4, (script[i+1]?script[i+1].t:l.t+4));
376
+ return `${i+1}\n${srtTime(start)} --> ${srtTime(end)}\n${l.who}: ${l.text}\n`;
377
+ }).join('\n');
378
+ const blob=new Blob([srt],{type:'text/plain'});
379
+ const a=document.createElement('a');
380
+ a.href=URL.createObjectURL(blob);
381
+ a.download=`marquee_${currentVibe||'show'}_captions.srt`;
382
+ a.click(); URL.revokeObjectURL(a.href);
383
+ toast('Captions exported — drop the .srt into CapCut');
384
+ });
385
+ function srtTime(s){
386
+ const h=Math.floor(s/3600), m=Math.floor((s%3600)/60), sec=Math.floor(s%60);
387
+ return `${pad(h)}:${pad(m)}:${pad(sec)},000`;
388
+ }
389
+
390
+ /* ---------- confetti ---------- */
391
+ function confetti(){
392
+ const cols=['#FFC94D','#FF4D9A','#3DE7FF','#C18BFF','#9BE85B','#FF8A4D'];
393
+ for(let i=0;i<70;i++){
394
+ const c=document.createElement('div');
395
+ c.className='confetti';
396
+ c.style.left=Math.random()*100+'vw';
397
+ c.style.background=cols[i%cols.length];
398
+ c.style.animationDuration=(1.6+Math.random()*1.6)+'s';
399
+ c.style.animationDelay=(Math.random()*.3)+'s';
400
+ c.style.transform=`rotate(${Math.random()*360}deg)`;
401
+ if(Math.random()>.5)c.style.borderRadius='50%';
402
+ document.body.appendChild(c);
403
+ setTimeout(()=>c.remove(),3600);
404
+ }
405
+ }
406
+
407
+ /* ---------- toast ---------- */
408
+ const toastEl=$('#toast'), toastTxt=$('#toastTxt'); let toastT;
409
+ function toast(msg){
410
+ toastTxt.textContent=msg; toastEl.classList.add('show');
411
+ clearTimeout(toastT); toastT=setTimeout(()=>toastEl.classList.remove('show'),2600);
412
+ }
marquee_inlined_preview.html ADDED
@@ -0,0 +1,1093 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="utf-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1">
6
+ <title>Marquee — Setup Console & Broadcast</title>
7
+ <link rel="preconnect" href="https://fonts.googleapis.com">
8
+ <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
9
+ <link href="https://fonts.googleapis.com/css2?family=Unbounded:wght@400;500;600;700;800&family=Bebas+Neue&family=Nunito:wght@600;700;800;900&display=swap" rel="stylesheet">
10
+ <style>/* ============================================================
11
+ MARQUEE — design system
12
+ Tokens straight from the brief so they lift into Gradio css=.
13
+ Sections: tokens · base · masthead · rundown · desk · stage
14
+ · star card · vibes + Coco · broadcast player · utilities
15
+ ============================================================ */
16
+
17
+ :root{
18
+ /* night & surfaces */
19
+ --night:#190B2E; /* base */
20
+ --night-2:#120821; /* deeper, for wells */
21
+ --stage:#241241; /* panels */
22
+ --stage-2:#2E1A52; /* raised */
23
+ --raise:#382060; /* inputs-on-dark */
24
+ --line:#3A2A5E; /* borders */
25
+ --line-soft:#2C1E4C;
26
+
27
+ /* leads + helper */
28
+ --hot:#FF3D8B; /* primary / on-air / energy */
29
+ --gold:#FFC94D; /* stardom: lights, crowns */
30
+ --cyan:#3DE7FF; /* confirm / scan */
31
+ --violet:#B98BFF; /* playful / mascot */
32
+
33
+ /* text */
34
+ --cream:#FFF3E9;
35
+ --muted:#A98FC9;
36
+ --muted-2:#7C66A6;
37
+
38
+ /* the casting rainbow — each star gets their own character color.
39
+ all high-chroma, all in-family: the "agent select" punch. */
40
+ --star-gold:#FFC94D;
41
+ --star-hot:#FF4D9A;
42
+ --star-cyan:#3DE7FF;
43
+ --star-violet:#C18BFF;
44
+ --star-lime:#9BE85B;
45
+ --star-tang:#FF8A4D;
46
+
47
+ --fx:cubic-bezier(.2,.9,.25,1);
48
+ --pop:cubic-bezier(.2,1.7,.4,1);
49
+ }
50
+
51
+ *{box-sizing:border-box;margin:0;padding:0}
52
+ html{-webkit-font-smoothing:antialiased;text-rendering:optimizeLegibility}
53
+
54
+ body{
55
+ font-family:Nunito,system-ui,sans-serif;
56
+ color:var(--cream);
57
+ background:var(--night);
58
+ min-height:100vh;
59
+ line-height:1.5;
60
+ /* one justified light: a stage spotlight from above + a faint floor wash.
61
+ no decorative blob gradients. */
62
+ background-image:
63
+ radial-gradient(120% 70% at 50% -25%, rgba(255,201,77,.10), transparent 55%),
64
+ radial-gradient(140% 90% at 50% 120%, rgba(61,231,255,.05), transparent 60%);
65
+ background-attachment:fixed;
66
+ }
67
+ /* film grain — the only decorative texture, very subtle */
68
+ body::after{
69
+ content:"";position:fixed;inset:0;pointer-events:none;z-index:80;opacity:.04;
70
+ mix-blend-mode:overlay;
71
+ background-image:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='160' height='160'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='.85' numOctaves='2'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23n)'/%3E%3C/svg%3E");
72
+ }
73
+
74
+ .disp{font-family:Unbounded,sans-serif}
75
+ .marq{font-family:'Bebas Neue',sans-serif;letter-spacing:.06em}
76
+ svg.i{width:1em;height:1em;display:block;flex:none}
77
+
78
+ .shell{max-width:1180px;margin:0 auto;padding:clamp(16px,3vw,34px) clamp(16px,4vw,40px) 40px;position:relative;z-index:1}
79
+
80
+ /* ============================================================
81
+ MASTHEAD — the broadcast header bar. signature lives here.
82
+ ============================================================ */
83
+ .masthead{
84
+ display:flex;align-items:center;justify-content:space-between;gap:20px;
85
+ padding-bottom:22px;margin-bottom:6px;flex-wrap:wrap;
86
+ }
87
+ .brand{display:flex;align-items:center;gap:18px}
88
+
89
+ /* the lit marquee sign — spend the boldness here */
90
+ .sign{
91
+ position:relative;padding:14px 22px 15px;border-radius:14px;
92
+ border:2px solid rgba(255,201,77,.55);
93
+ background:
94
+ radial-gradient(120% 140% at 50% 0%, rgba(255,201,77,.14), transparent 70%),
95
+ linear-gradient(180deg, rgba(255,201,77,.05), rgba(25,11,46,.2));
96
+ box-shadow:inset 0 0 22px rgba(255,201,77,.16), 0 10px 40px rgba(255,61,139,.10);
97
+ }
98
+ .sign h1{
99
+ font-family:Unbounded,sans-serif;font-weight:800;font-size:clamp(26px,3.4vw,38px);
100
+ letter-spacing:.04em;line-height:.9;
101
+ color:var(--cream);
102
+ text-shadow:0 0 18px rgba(255,201,77,.45), 0 0 2px rgba(255,201,77,.8);
103
+ }
104
+ /* perimeter bulbs, top + bottom rows */
105
+ .sign .bulbs{position:absolute;left:14px;right:14px;display:flex;justify-content:space-between}
106
+ .sign .bulbs.t{top:-5px}.sign .bulbs.b{bottom:-5px}
107
+ .sign .bulbs i{width:6px;height:6px;border-radius:50%;background:var(--gold);
108
+ box-shadow:0 0 8px var(--gold),0 0 2px #fff;animation:tw 2.6s infinite}
109
+ .sign .bulbs i:nth-child(2n){animation-delay:.7s}
110
+ .sign .bulbs i:nth-child(3n){animation-delay:1.5s;background:var(--hot);box-shadow:0 0 8px var(--hot),0 0 2px #fff}
111
+ @keyframes tw{0%,100%{opacity:1}48%{opacity:.28}}
112
+
113
+ .tag{font-family:Unbounded,sans-serif;font-weight:500;font-size:13px;color:var(--gold);letter-spacing:.01em}
114
+ .tag small{display:block;font-family:Nunito;font-weight:600;color:var(--muted);font-size:12px;margin-top:3px;letter-spacing:0}
115
+
116
+ /* right side: live clock + status */
117
+ .broadcast-meta{display:flex;align-items:center;gap:14px}
118
+ .clock{font-family:'Bebas Neue',sans-serif;letter-spacing:.12em;font-size:20px;color:var(--muted);
119
+ font-variant-numeric:tabular-nums}
120
+ .status{display:flex;align-items:center;gap:9px;white-space:nowrap;font-family:'Bebas Neue',sans-serif;letter-spacing:.14em;
121
+ font-size:16px;color:var(--muted);border:1px solid var(--line);padding:8px 15px;border-radius:999px;
122
+ background:rgba(46,26,82,.5)}
123
+ .status .led{width:9px;height:9px;border-radius:50%;background:var(--muted-2);transition:.3s}
124
+ .status.live{color:var(--hot);border-color:rgba(255,61,139,.5);background:rgba(255,61,139,.08)}
125
+ .status.live .led{background:var(--hot);box-shadow:0 0 12px var(--hot);animation:tw 1.1s infinite}
126
+
127
+ /* ============================================================
128
+ RUNDOWN — broadcast running order, not 4 identical cards.
129
+ nodes on a connecting track.
130
+ ============================================================ */
131
+ .rundown{position:relative;display:grid;grid-template-columns:repeat(4,1fr);
132
+ margin:8px 0 30px;padding:0 6px}
133
+ .rundown::before{content:"";position:absolute;top:19px;left:11%;right:11%;height:2px;
134
+ background:var(--line-soft)}
135
+ .cue{position:relative;display:flex;flex-direction:column;align-items:center;gap:9px;text-align:center}
136
+ .cue .node{width:40px;height:40px;border-radius:50%;display:grid;place-items:center;
137
+ background:var(--stage);border:2px solid var(--line);
138
+ font-family:'Bebas Neue',sans-serif;font-size:20px;color:var(--muted-2);
139
+ position:relative;z-index:1;transition:.35s var(--fx)}
140
+ .cue .ttl{font-family:Unbounded,sans-serif;font-weight:600;font-size:13px;color:var(--muted)}
141
+ .cue .sub{font-size:11px;color:var(--muted-2);letter-spacing:.01em}
142
+ /* done */
143
+ .cue.done .node{border-color:var(--cyan);color:var(--cyan);background:rgba(61,231,255,.08)}
144
+ .cue.done .ttl{color:var(--cream)}
145
+ /* active */
146
+ .cue.on .node{border-color:var(--gold);color:var(--gold);background:rgba(255,201,77,.12);
147
+ box-shadow:0 0 0 5px rgba(255,201,77,.1),0 0 22px rgba(255,201,77,.3)}
148
+ .cue.on .ttl{color:var(--gold)}
149
+ @media(prefers-reduced-motion:no-preference){
150
+ .cue.on .node{animation:cuePulse 2s infinite}
151
+ }
152
+ @keyframes cuePulse{0%,100%{box-shadow:0 0 0 5px rgba(255,201,77,.1),0 0 22px rgba(255,201,77,.28)}
153
+ 50%{box-shadow:0 0 0 8px rgba(255,201,77,.04),0 0 30px rgba(255,201,77,.45)}}
154
+
155
+ /* ============================================================
156
+ LAYOUT — control desk (left) vs the stage (right).
157
+ intentionally asymmetric; the two halves are treated differently.
158
+ ============================================================ */
159
+ .console{display:grid;grid-template-columns:362px 1fr;gap:20px;align-items:start}
160
+ @media(max-width:900px){.console{grid-template-columns:1fr}}
161
+
162
+ /* the desk: flat, utilitarian — it's the control surface, not a showpiece */
163
+ .desk{display:flex;flex-direction:column;gap:16px}
164
+ .card{background:var(--stage);border:1px solid var(--line-soft);border-radius:16px;padding:18px}
165
+ .card-h{display:flex;align-items:center;gap:10px;margin-bottom:14px}
166
+ .card-h .ic{color:var(--gold);font-size:18px}
167
+ .card-h h2{font-family:Unbounded,sans-serif;font-weight:600;font-size:14px;letter-spacing:.01em}
168
+ .card-h .meta{margin-left:auto;font-family:'Bebas Neue',sans-serif;letter-spacing:.07em;font-size:12px;color:var(--muted-2)}
169
+
170
+ /* roll tape drop zone — clapperboard motif */
171
+ .drop{position:relative;border:1.5px dashed var(--line);border-radius:13px;padding:26px 16px;
172
+ text-align:center;cursor:pointer;transition:.22s var(--fx);overflow:hidden;
173
+ background:radial-gradient(200px 110px at 50% -20%,rgba(255,201,77,.07),transparent 70%)}
174
+ .drop:hover{border-color:var(--gold);background:radial-gradient(200px 130px at 50% -10%,rgba(255,201,77,.12),transparent 70%)}
175
+ .drop:focus-visible{border-color:var(--cyan)}
176
+ .drop .clap{font-size:30px;color:var(--gold);display:inline-flex;margin-bottom:8px}
177
+ .drop .t{font-family:Unbounded,sans-serif;font-weight:600;font-size:15px}
178
+ .drop .s{font-size:12px;color:var(--muted);margin-top:4px}
179
+ /* loaded: a little filmstrip readout */
180
+ .drop .loaded{display:none;align-items:center;gap:12px;text-align:left}
181
+ .drop.loaded{border-style:solid;border-color:rgba(61,231,255,.45);background:rgba(61,231,255,.05);cursor:default}
182
+ .drop.loaded .ask{display:none}
183
+ .drop.loaded .loaded{display:flex}
184
+ .strip{width:52px;height:52px;border-radius:9px;flex:none;position:relative;overflow:hidden;
185
+ background:linear-gradient(135deg,var(--raise),var(--stage));border:1px solid var(--line)}
186
+ .strip::before,.strip::after{content:"";position:absolute;top:4px;bottom:4px;width:5px;
187
+ background:repeating-linear-gradient(var(--night) 0 3px,transparent 3px 7px)}
188
+ .strip::before{left:3px}.strip::after{right:3px}
189
+ .strip .play{position:absolute;inset:0;display:grid;place-items:center;color:var(--cyan);font-size:16px}
190
+ .loaded .fn{font-family:Unbounded,sans-serif;font-weight:600;font-size:13px;color:var(--cream)}
191
+ .loaded .fd{font-family:'Bebas Neue',sans-serif;letter-spacing:.05em;font-size:13px;color:var(--cyan);margin-top:3px}
192
+
193
+ /* buttons */
194
+ .btn{width:100%;border:0;cursor:pointer;font-family:Unbounded,sans-serif;font-weight:600;font-size:14px;
195
+ color:var(--night);padding:14px;border-radius:12px;display:flex;align-items:center;justify-content:center;
196
+ gap:9px;transition:.18s var(--fx)}
197
+ .btn:disabled{opacity:.4;cursor:not-allowed;background:var(--raise);color:var(--muted-2)}
198
+ .btn.cyan{background:linear-gradient(135deg,var(--cyan),#16b6d8)}
199
+ .btn.cyan:not(:disabled):hover{transform:translateY(-2px);box-shadow:0 12px 30px rgba(61,231,255,.3)}
200
+ .btn.show{font-size:17px;padding:17px;background:linear-gradient(110deg,var(--gold),var(--hot));position:relative;overflow:hidden}
201
+ .btn.show:not(:disabled):hover{transform:translateY(-2px);box-shadow:0 16px 38px rgba(255,61,139,.4)}
202
+ .btn.show:not(:disabled)::after{content:"";position:absolute;top:0;bottom:0;width:40%;
203
+ background:linear-gradient(90deg,transparent,rgba(255,255,255,.35),transparent);
204
+ transform:translateX(-200%)}
205
+ @media(prefers-reduced-motion:no-preference){
206
+ .btn.show:not(:disabled)::after{animation:shine 3.4s ease-in-out 1.2s infinite}
207
+ }
208
+ @keyframes shine{0%,100%{transform:translateX(-220%)}40%,60%{transform:translateX(360%)}}
209
+ .mt{margin-top:14px}
210
+
211
+ /* casting sweep bar */
212
+ .sweep{height:6px;border-radius:4px;margin-top:13px;background:var(--night-2);position:relative;overflow:hidden;
213
+ opacity:0;height:0;margin-top:0;transition:.25s}
214
+ .casting .sweep{opacity:1;height:6px;margin-top:13px}
215
+ .sweep::after{content:"";position:absolute;inset:0;width:36%;
216
+ background:linear-gradient(90deg,transparent,var(--gold),transparent)}
217
+ @media(prefers-reduced-motion:no-preference){.sweep::after{animation:sw 1s linear infinite}}
218
+ @keyframes sw{from{transform:translateX(-120%)}to{transform:translateX(330%)}}
219
+
220
+ /* ============================================================
221
+ THE STAGE — Star Select. theatrical, spotlit well.
222
+ ============================================================ */
223
+ .stage-panel{background:
224
+ radial-gradient(80% 60% at 50% -8%, rgba(255,201,77,.07), transparent 60%),
225
+ linear-gradient(180deg,var(--stage),var(--night-2));
226
+ border:1px solid var(--line);border-radius:20px;padding:20px;
227
+ box-shadow:inset 0 1px 0 rgba(255,243,233,.04), 0 24px 60px rgba(8,2,20,.5)}
228
+ .stage-h{display:flex;align-items:center;gap:11px;margin-bottom:16px}
229
+ .stage-h .ic{color:var(--gold);font-size:19px}
230
+ .stage-h h2{font-family:Unbounded,sans-serif;font-weight:700;font-size:16px}
231
+ .stage-h .count{margin-left:auto;white-space:nowrap;font-family:'Bebas Neue',sans-serif;letter-spacing:.08em;font-size:14px;
232
+ color:var(--muted);background:rgba(46,26,82,.6);padding:4px 11px;border-radius:8px}
233
+
234
+ /* empty state */
235
+ .empty{display:grid;place-items:center;min-height:330px;text-align:center;padding:20px}
236
+ .empty .spot{width:96px;height:96px;border-radius:50%;display:grid;place-items:center;margin-bottom:16px;
237
+ color:var(--muted-2);font-size:46px;
238
+ background:radial-gradient(circle at 50% 30%,rgba(255,201,77,.08),transparent 70%)}
239
+ .empty .t{font-family:Unbounded,sans-serif;font-weight:600;font-size:17px;color:var(--cream);margin-bottom:6px}
240
+ .empty .s{font-size:13px;color:var(--muted);max-width:300px}
241
+
242
+ /* star grid */
243
+ .roster{display:grid;grid-template-columns:repeat(auto-fill,minmax(164px,1fr));gap:16px}
244
+
245
+ /* ---- the star card (one reusable block) ----
246
+ per-card character color is set inline as --c. */
247
+ /* base state is fully visible — entrance is driven via WAAPI in JS so a
248
+ backgrounded tab never leaves cards stuck invisible. */
249
+ .star{--c:var(--gold);position:relative;border-radius:16px;overflow:hidden;
250
+ background:linear-gradient(180deg,var(--stage-2),var(--stage));
251
+ border:1px solid var(--line);transition:.25s var(--fx)}
252
+ .star:hover{transform:translateY(-3px);border-color:color-mix(in srgb,var(--c) 60%,transparent)}
253
+
254
+ /* portrait well with a spotlight beam tinted to the character color,
255
+ and an angular framed headshot placeholder (agent-select energy) */
256
+ .portrait{position:relative;height:138px;overflow:hidden;
257
+ background:radial-gradient(90% 80% at 50% 120%,color-mix(in srgb,var(--c) 22%,var(--night-2)),var(--night-2))}
258
+ .portrait .beam{position:absolute;top:-30px;left:50%;width:150px;height:170px;transform:translateX(-50%);
259
+ pointer-events:none;clip-path:polygon(40% 0,60% 0,100% 100%,0 100%);
260
+ background:linear-gradient(180deg,color-mix(in srgb,var(--c) 55%,transparent),transparent 70%);opacity:.7}
261
+ .portrait .head-ph{position:absolute;left:50%;bottom:-6px;transform:translateX(-50%);
262
+ color:color-mix(in srgb,var(--c) 50%,#ffffff);opacity:.4}
263
+ .portrait .frame{position:absolute;inset:8px;border:1.5px solid color-mix(in srgb,var(--c) 35%,transparent);
264
+ border-radius:11px;pointer-events:none}
265
+ .portrait .idx{position:absolute;top:10px;left:10px;font-family:'Bebas Neue',sans-serif;font-size:14px;
266
+ color:var(--cream);background:rgba(15,6,30,.7);padding:2px 7px;border-radius:7px;display:flex;align-items:center;gap:3px}
267
+ .portrait .idx .st{color:var(--c)}
268
+ .tier{position:absolute;top:10px;right:10px;font-family:'Bebas Neue',sans-serif;font-size:11px;letter-spacing:.02em;
269
+ white-space:nowrap;color:var(--night);background:var(--c);padding:3px 8px;border-radius:7px;
270
+ box-shadow:0 2px 10px color-mix(in srgb,var(--c) 50%,transparent)}
271
+
272
+ /* crown drops in on naming */
273
+ /* crown lifts in above the star's head on naming */
274
+ .crown{position:absolute;top:50px;left:50%;transform:translateX(-50%) translateY(12px) scale(.4) rotate(-12deg);
275
+ opacity:0;width:30px;height:30px;color:var(--gold);
276
+ filter:drop-shadow(0 2px 6px color-mix(in srgb,var(--gold) 70%,transparent));
277
+ transition:.4s var(--pop)}
278
+ .star.named .crown{opacity:1;transform:translateX(-50%) scale(1) rotate(0)}
279
+
280
+ .star-body{padding:13px}
281
+ .star-body label{font-family:'Bebas Neue',sans-serif;letter-spacing:.08em;font-size:12px;color:var(--muted);
282
+ display:block;margin-bottom:7px}
283
+ .star input{width:100%;background:var(--night-2);border:1px solid var(--line);color:var(--cream);
284
+ border-radius:10px;padding:10px 12px;font-family:Nunito;font-weight:800;font-size:14px;outline:none;transition:.16s}
285
+ .star input::placeholder{color:var(--muted-2);font-weight:600}
286
+ .star input:focus{border-color:var(--c);box-shadow:0 0 0 3px color-mix(in srgb,var(--c) 25%,transparent)}
287
+
288
+ /* crowned: card lights in its own character color */
289
+ .star.named{border-color:color-mix(in srgb,var(--c) 65%,transparent);
290
+ box-shadow:0 0 0 1px color-mix(in srgb,var(--c) 30%,transparent),0 10px 30px color-mix(in srgb,var(--c) 18%,transparent)}
291
+ .star.named .portrait{background:radial-gradient(90% 90% at 50% 120%,color-mix(in srgb,var(--c) 40%,var(--night-2)),var(--night-2))}
292
+ .star.named .portrait .beam{opacity:1}
293
+ .star.named .head-ph{opacity:.6}
294
+
295
+ /* ============================================================
296
+ VIBES (narration modes) + COCO host
297
+ ============================================================ */
298
+ .vibes{display:grid;grid-template-columns:repeat(2,1fr);gap:10px}
299
+ .vibe{display:flex;align-items:center;gap:11px;padding:12px;border-radius:13px;cursor:pointer;color:var(--cream);
300
+ border:1px solid var(--line-soft);background:var(--stage-2);transition:.18s var(--fx);text-align:left}
301
+ .vibe:hover{border-color:var(--violet);transform:translateY(-1px)}
302
+ .vibe .ic{font-size:20px;color:var(--muted);display:inline-flex;transition:.18s}
303
+ .vibe>span:last-child{display:flex;flex-direction:column;gap:2px;min-width:0}
304
+ .vibe .vt{display:block;font-family:Unbounded,sans-serif;font-weight:600;font-size:13px;color:var(--cream)}
305
+ .vibe .vd{display:block;font-size:11px;color:var(--muted);line-height:1.25}
306
+ .vibe[aria-pressed="true"]{border-color:var(--vc,var(--hot));background:color-mix(in srgb,var(--vc,var(--hot)) 12%,var(--stage-2))}
307
+ .vibe[aria-pressed="true"] .ic{color:var(--vc,var(--hot))}
308
+
309
+ /* Coco — the host bug. re-dresses per vibe. */
310
+ .coco{display:flex;align-items:center;gap:13px;margin-top:14px;padding:13px 14px;border-radius:14px;
311
+ background:linear-gradient(120deg,color-mix(in srgb,var(--vc,var(--violet)) 12%,var(--stage-2)),var(--stage-2));
312
+ border:1px solid color-mix(in srgb,var(--vc,var(--violet)) 30%,var(--line-soft));transition:.35s var(--fx)}
313
+ .coco .av{position:relative;width:46px;height:46px;border-radius:13px;flex:none;display:grid;place-items:center;
314
+ background:radial-gradient(circle at 50% 30%,color-mix(in srgb,var(--vc,var(--violet)) 70%,#fff 5%),var(--vc,var(--violet)));
315
+ box-shadow:0 6px 18px color-mix(in srgb,var(--vc,var(--violet)) 40%,transparent);transition:.35s var(--fx)}
316
+ .coco .av .bird{width:30px;height:30px;color:var(--night);filter:drop-shadow(0 1px 1px rgba(255,255,255,.3))}
317
+ /* accessory badge sits on the avatar, swapped per vibe */
318
+ .coco .acc{position:absolute;right:-6px;bottom:-6px;width:22px;height:22px;border-radius:50%;
319
+ display:grid;place-items:center;background:var(--night);border:2px solid var(--vc,var(--violet));
320
+ color:var(--vc,var(--violet));font-size:11px}
321
+ .coco .acc svg{width:12px;height:12px}
322
+ .coco .say{font-size:13px;line-height:1.45}
323
+ .coco .say b{color:var(--vc,var(--gold))}
324
+ .coco .who{font-family:'Bebas Neue',sans-serif;letter-spacing:.1em;font-size:11px;color:var(--vc,var(--violet));
325
+ display:block;margin-bottom:2px}
326
+
327
+ /* privacy footnote — visible, not buried */
328
+ .note{margin-top:26px;text-align:center;font-size:12.5px;color:var(--muted);line-height:1.6}
329
+ .note b{color:var(--cream)}
330
+ .note .lock{color:var(--cyan);vertical-align:-2px;margin-right:5px}
331
+
332
+ /* ============================================================
333
+ VIEW SWITCH
334
+ ============================================================ */
335
+ .view{display:none}
336
+ .view.active{display:block;animation:viewIn .4s var(--fx)}
337
+ @keyframes viewIn{from{opacity:0;transform:translateY(8px)}to{opacity:1;transform:none}}
338
+
339
+ /* ============================================================
340
+ BROADCAST PLAYER
341
+ ============================================================ */
342
+ .booth-top{display:flex;align-items:center;justify-content:space-between;gap:16px;margin-bottom:18px;flex-wrap:wrap}
343
+ .back{display:inline-flex;align-items:center;gap:8px;background:var(--stage);border:1px solid var(--line);
344
+ color:var(--cream);font-family:Nunito;font-weight:700;font-size:13px;padding:9px 14px;border-radius:10px;cursor:pointer;transition:.16s}
345
+ .back:hover{border-color:var(--violet)}
346
+ .orient{display:flex;gap:4px;background:var(--stage);border:1px solid var(--line-soft);padding:4px;border-radius:11px}
347
+ .orient button{background:transparent;border:0;color:var(--muted);font-family:'Bebas Neue',sans-serif;letter-spacing:.06em;
348
+ font-size:13px;padding:7px 14px;border-radius:8px;cursor:pointer;transition:.16s}
349
+ .orient button[aria-pressed="true"]{background:var(--raise);color:var(--gold)}
350
+
351
+ .booth{display:grid;grid-template-columns:1fr 320px;gap:18px;align-items:start}
352
+ /* portrait: the clip is a phone-sized vertical frame, never a giant column-filling block.
353
+ capped width keeps the 9:16 height sane and the play-by-play rail beside it. */
354
+ .booth.portrait{grid-template-columns:minmax(0,340px) minmax(0,1fr)}
355
+ .booth.portrait .screen-wrap{max-width:340px}
356
+ @media(max-width:840px){
357
+ .booth,.booth.square{grid-template-columns:1fr}
358
+ .booth.portrait{grid-template-columns:1fr}
359
+ .booth.portrait .screen-wrap{margin-inline:auto}
360
+ }
361
+
362
+ /* the screen */
363
+ .screen-wrap{background:#000;border-radius:18px;overflow:hidden;border:1px solid var(--line);
364
+ box-shadow:0 24px 70px rgba(0,0,0,.6);position:relative}
365
+ .screen{position:relative;width:100%;aspect-ratio:16/9;background:
366
+ repeating-linear-gradient(135deg,#1c0f33 0 18px,#170c2b 18px 36px);
367
+ display:grid;place-items:center}
368
+ .booth.portrait .screen{aspect-ratio:9/16}
369
+ .booth.square .screen{aspect-ratio:1/1}
370
+ .screen .ph{font-family:'Bebas Neue',sans-serif;letter-spacing:.12em;color:var(--muted-2);font-size:15px;
371
+ text-align:center;padding:0 20px}
372
+ .screen .ph svg{width:40px;height:40px;margin:0 auto 10px;color:var(--line);display:block}
373
+
374
+ /* on-air bug */
375
+ .bug{position:absolute;top:14px;left:14px;display:flex;align-items:center;gap:8px;white-space:nowrap;
376
+ background:rgba(15,6,30,.6);backdrop-filter:blur(4px);padding:7px 12px;border-radius:9px;
377
+ font-family:'Bebas Neue',sans-serif;letter-spacing:.12em;font-size:14px;color:var(--cream)}
378
+ .bug .dot{width:10px;height:10px;border-radius:50%;background:var(--hot);box-shadow:0 0 10px var(--hot)}
379
+ @media(prefers-reduced-motion:no-preference){.bug .dot{animation:tw 1.1s infinite}}
380
+
381
+ /* timecode */
382
+ .tc{position:absolute;top:14px;right:14px;background:rgba(15,6,30,.6);backdrop-filter:blur(4px);
383
+ padding:7px 12px;border-radius:9px;font-family:'Bebas Neue',sans-serif;letter-spacing:.1em;font-size:15px;
384
+ color:var(--cyan);font-variant-numeric:tabular-nums}
385
+
386
+ /* lower-third chyron */
387
+ .chyron{position:absolute;left:14px;right:14px;bottom:14px;border-radius:12px;overflow:hidden;
388
+ background:linear-gradient(100deg,rgba(15,6,30,.92),rgba(36,18,65,.82));
389
+ border:1px solid color-mix(in srgb,var(--cc,var(--hot)) 40%,var(--line));
390
+ box-shadow:0 10px 30px rgba(0,0,0,.4);
391
+ transform:translateY(130%);opacity:0;transition:.45s var(--fx)}
392
+ .chyron.in{transform:none;opacity:1}
393
+ .chyron .bar{height:4px;background:linear-gradient(90deg,var(--cc,var(--hot)),var(--gold))}
394
+ .chyron .row{display:flex;align-items:stretch}
395
+ .chyron .who{display:flex;align-items:center;font-family:'Bebas Neue',sans-serif;letter-spacing:.06em;
396
+ font-size:17px;color:var(--night);background:var(--cc,var(--hot));padding:10px 14px;white-space:nowrap}
397
+ .chyron .line{padding:10px 16px;font-family:Nunito;font-weight:700;font-size:15px;color:var(--cream);
398
+ display:flex;align-items:center;line-height:1.3}
399
+ @media(prefers-reduced-motion:no-preference){
400
+ .chyron.in .line{animation:sharpen .5s var(--fx)}
401
+ }
402
+ @keyframes sharpen{from{filter:blur(7px);opacity:.3}to{filter:blur(0);opacity:1}}
403
+
404
+ /* transport */
405
+ .transport{display:flex;align-items:center;gap:14px;margin-top:14px}
406
+ .transport .pp{width:48px;height:48px;border-radius:50%;border:0;cursor:pointer;flex:none;display:grid;place-items:center;
407
+ background:linear-gradient(135deg,var(--gold),var(--hot));color:var(--night);font-size:20px;transition:.16s}
408
+ .transport .pp:hover{transform:scale(1.05)}
409
+ .scrub{flex:1;height:6px;border-radius:4px;background:var(--raise);position:relative;cursor:pointer}
410
+ .scrub .fill{position:absolute;left:0;top:0;bottom:0;border-radius:4px;width:0;
411
+ background:linear-gradient(90deg,var(--hot),var(--gold))}
412
+ .scrub .knob{position:absolute;top:50%;width:14px;height:14px;border-radius:50%;background:var(--cream);
413
+ transform:translate(-50%,-50%);left:0;box-shadow:0 2px 6px rgba(0,0,0,.5)}
414
+
415
+ /* play-by-play rail */
416
+ .pbp{background:var(--stage);border:1px solid var(--line-soft);border-radius:16px;overflow:hidden;
417
+ display:flex;flex-direction:column;max-height:520px}
418
+ .pbp .pbp-h{display:flex;align-items:center;gap:9px;padding:14px 16px;border-bottom:1px solid var(--line-soft)}
419
+ .pbp .pbp-h .ic{color:var(--gold);font-size:16px}
420
+ .pbp .pbp-h h3{font-family:Unbounded,sans-serif;font-weight:600;font-size:13px}
421
+ .pbp .pbp-h .ct{margin-left:auto;font-family:'Bebas Neue',sans-serif;letter-spacing:.06em;font-size:12px;color:var(--muted-2)}
422
+ .pbp .lines{overflow-y:auto;padding:6px}
423
+ .pbp .pl{display:flex;gap:11px;padding:11px 12px;border-radius:10px;cursor:pointer;transition:.15s;align-items:baseline}
424
+ .pbp .pl:hover{background:var(--stage-2)}
425
+ .pbp .pl .t{font-family:'Bebas Neue',sans-serif;letter-spacing:.04em;font-size:14px;color:var(--muted-2);
426
+ flex:none;width:42px;font-variant-numeric:tabular-nums;padding-top:1px}
427
+ .pbp .pl .tx{font-size:13px;line-height:1.45;color:var(--muted)}
428
+ .pbp .pl .tx b{color:var(--cream);font-weight:800}
429
+ .pbp .pl.now{background:color-mix(in srgb,var(--cc,var(--hot)) 14%,var(--stage-2))}
430
+ .pbp .pl.now .t{color:var(--cc,var(--hot))}
431
+ .pbp .pl.now .tx{color:var(--cream)}
432
+ .pbp .pl.now .tx b{color:var(--cc,var(--hot))}
433
+
434
+ /* export bar */
435
+ .export{padding:13px 16px;border-top:1px solid var(--line-soft);background:var(--night-2)}
436
+ .export .btn-srt{width:100%;display:flex;align-items:center;justify-content:center;gap:9px;cursor:pointer;
437
+ background:var(--stage-2);border:1px solid var(--line);color:var(--cream);font-family:Nunito;font-weight:700;
438
+ font-size:13px;padding:12px;border-radius:11px;transition:.16s}
439
+ .export .btn-srt:hover{border-color:var(--cyan);color:var(--cyan)}
440
+ .export .btn-srt .ic{color:var(--cyan);font-size:16px}
441
+ .export .hint{text-align:center;font-size:11px;color:var(--muted-2);margin-top:8px}
442
+ .export .hint b{color:var(--muted)}
443
+
444
+ /* confetti */
445
+ .confetti{position:fixed;top:-20px;width:11px;height:14px;z-index:90;pointer-events:none;border-radius:2px}
446
+ @media(prefers-reduced-motion:no-preference){.confetti{animation:fall linear forwards}}
447
+ @keyframes fall{to{transform:translateY(105vh) rotate(540deg);opacity:.2}}
448
+
449
+ /* ============================================================
450
+ UTILITIES
451
+ ============================================================ */
452
+ :focus-visible{outline:2px solid var(--cyan);outline-offset:3px;border-radius:8px}
453
+ .toast{position:fixed;left:50%;bottom:28px;transform:translateX(-50%) translateY(20px);opacity:0;
454
+ background:var(--stage-2);border:1px solid var(--cyan);color:var(--cream);padding:12px 20px;border-radius:12px;
455
+ font-weight:700;font-size:13px;z-index:95;transition:.3s var(--fx);display:flex;align-items:center;gap:9px;
456
+ box-shadow:0 14px 40px rgba(0,0,0,.5)}
457
+ .toast.show{transform:translateX(-50%);opacity:1}
458
+ .toast .ic{color:var(--cyan)}
459
+
460
+ @media(prefers-reduced-motion:reduce){*{animation:none!important;transition:none!important}}
461
+
462
+ @media(max-width:560px){
463
+ .masthead{gap:14px}
464
+ .rundown .sub{display:none}
465
+ .vibes{grid-template-columns:1fr}
466
+ .roster{grid-template-columns:repeat(auto-fill,minmax(140px,1fr))}
467
+ }
468
+
469
+ /* ============================================================
470
+ INTEGRATION ADDITIONS — real <video> in the screen, real face
471
+ crops in star cards (added for the Gradio wiring)
472
+ ============================================================ */
473
+ .screen-vid{position:absolute;inset:0;width:100%;height:100%;object-fit:contain;background:#000;display:none}
474
+ .screen.has-video .ph{display:none}
475
+ .screen.has-video .screen-vid{display:block}
476
+ .portrait .head-img{position:absolute;inset:0;width:100%;height:100%;object-fit:cover}
477
+ .star.named .head-img{filter:saturate(1.05)}
478
+ </style>
479
+ </head>
480
+ <body>
481
+
482
+ <!-- =======================================================
483
+ ICON SPRITE — line icons, broadcast vernacular
484
+ ======================================================= -->
485
+ <svg width="0" height="0" style="position:absolute" aria-hidden="true">
486
+ <defs>
487
+ <symbol id="i-clap" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.7" stroke-linejoin="round"><path d="M3 8h18v11a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V8Z"/><path d="m3 8 1.6-3.4 3.8 1L6.7 9M9.4 5.6l3.8 1L11.5 10M15 7l3.8 1-1.5 3.4M21 8l-.8-2.2-3.4-.9"/></symbol>
488
+ <symbol id="i-play" viewBox="0 0 24 24" fill="currentColor"><path d="M8 5v14l11-7z"/></symbol>
489
+ <symbol id="i-pause" viewBox="0 0 24 24" fill="currentColor"><rect x="6" y="5" width="4" height="14" rx="1"/><rect x="14" y="5" width="4" height="14" rx="1"/></symbol>
490
+ <symbol id="i-star" viewBox="0 0 24 24" fill="currentColor"><path d="m12 2 2.9 6.3 6.9.8-5.1 4.7 1.4 6.8L12 17.8 5.9 20.6l1.4-6.8L2.2 9.1l6.9-.8z"/></symbol>
491
+ <symbol id="i-spot" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.7" stroke-linecap="round"><path d="M12 3v3M5 6l2 2M19 6l-2 2"/><path d="M9 9h6l3 11H6z" fill="currentColor" opacity=".15"/><path d="M9 9h6l3 11H6z"/></symbol>
492
+ <symbol id="i-crown" viewBox="0 0 24 24" fill="currentColor"><path d="M3 7l4 4 5-7 5 7 4-4-2 12H5z"/></symbol>
493
+ <symbol id="i-bird" viewBox="0 0 24 24" fill="currentColor"><path d="M14 4a3 3 0 0 0-3 3c0 .5-.4 1-1 1.5-1.8 1.3-3 3.4-3 5.8C7 17.9 9.6 20 13 20c.6 0 1-.6.8-1.2C13.3 17 13 16 14 15c2.2-.4 4-2.3 4-4.8V7a3 3 0 0 0-3-3zm.6 3.2a.9.9 0 1 1 0-.1z"/></symbol>
494
+ <symbol id="i-lock" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.7"><rect x="5" y="11" width="14" height="9" rx="2"/><path d="M8 11V8a4 4 0 0 1 8 0v3"/></symbol>
495
+ <symbol id="i-download" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.7" stroke-linecap="round" stroke-linejoin="round"><path d="M12 4v10m0 0 4-4m-4 4-4-4M5 20h14"/></symbol>
496
+ <symbol id="i-mic" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.7" stroke-linecap="round"><rect x="9" y="3" width="6" height="11" rx="3"/><path d="M6 11a6 6 0 0 0 12 0M12 17v4"/></symbol>
497
+ <symbol id="i-list" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.7" stroke-linecap="round"><path d="M8 6h12M8 12h12M8 18h12M4 6h.01M4 12h.01M4 18h.01"/></symbol>
498
+ <symbol id="i-whistle" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.7" stroke-linecap="round"><path d="M3 11a5 5 0 0 0 5 5h4l5 3v-8a3 3 0 0 0-3-3H8a5 5 0 0 0-5 3Z"/><path d="M14 4v3"/></symbol>
499
+ <symbol id="i-glam" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.7" stroke-linecap="round"><path d="M3 14c2-1 4-1 5 0M16 14c2-1 4-1 5 0M3 11h18M7 11l1 5M17 11l-1 5"/><circle cx="6.5" cy="8.5" r="2"/><circle cx="17.5" cy="8.5" r="2"/></symbol>
500
+ <symbol id="i-paw" viewBox="0 0 24 24" fill="currentColor"><circle cx="6" cy="9" r="2"/><circle cx="11" cy="6" r="2"/><circle cx="16" cy="7" r="2"/><circle cx="19" cy="11" r="2"/><path d="M12 12c-3 0-6 2.5-6 5 0 2 2 3 6 3s6-1 6-3c0-2.5-3-5-6-5Z"/></symbol>
501
+ <symbol id="i-glove" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.7" stroke-linejoin="round"><path d="M7 10V6a2 2 0 0 1 4 0v3M11 9V5a2 2 0 0 1 4 0v5l2 1a3 3 0 0 1 1.4 3.4l-.8 3A2 2 0 0 1 15.7 21H10a3 3 0 0 1-2.8-2L5.5 14A2.5 2.5 0 0 1 7 10Z"/></symbol>
502
+ <symbol id="i-cam" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5"><rect x="3" y="6" width="13" height="12" rx="2"/><path d="m16 10 5-3v10l-5-3z"/></symbol>
503
+ <symbol id="i-check" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.4" stroke-linecap="round" stroke-linejoin="round"><path d="m4 12 5 5L20 6"/></symbol>
504
+ <symbol id="i-back" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"><path d="M15 5l-7 7 7 7"/></symbol>
505
+ <symbol id="i-portrait" viewBox="0 0 24 24" fill="currentColor"><circle cx="12" cy="8" r="4.4"/><path d="M3.5 22c0-4.7 3.8-8 8.5-8s8.5 3.3 8.5 8z"/></symbol>
506
+ </defs>
507
+ </svg>
508
+
509
+ <div class="shell">
510
+
511
+ <!-- ============== MASTHEAD ============== -->
512
+ <header class="masthead">
513
+ <div class="brand">
514
+ <div class="sign">
515
+ <div class="bulbs t"><i></i><i></i><i></i><i></i><i></i><i></i><i></i></div>
516
+ <h1 class="disp">MARQUEE</h1>
517
+ <div class="bulbs b"><i></i><i></i><i></i><i></i><i></i><i></i><i></i></div>
518
+ </div>
519
+ <div class="tag">Everyone's a headliner.<small>Turn any clip into a live broadcast.</small></div>
520
+ </div>
521
+ <div class="broadcast-meta">
522
+ <span class="clock" id="clock">--:--:--</span>
523
+ <span class="status" id="status"><span class="led"></span><span id="statusTxt">Standby</span></span>
524
+ </div>
525
+ </header>
526
+
527
+ <!-- ============================================================
528
+ VIEW 1 — SETUP CONSOLE
529
+ ============================================================ -->
530
+ <section class="view active" id="setupView">
531
+
532
+ <!-- rundown / running order -->
533
+ <nav class="rundown" id="rundown" aria-label="Show rundown">
534
+ <div class="cue on" data-step="1"><div class="node">01</div><div class="ttl">Roll Tape</div><div class="sub">Drop a clip</div></div>
535
+ <div class="cue" data-step="2"><div class="node">02</div><div class="ttl">Casting</div><div class="sub">Find the faces</div></div>
536
+ <div class="cue" data-step="3"><div class="node">03</div><div class="ttl">Star Select</div><div class="sub">Name the stars</div></div>
537
+ <div class="cue" data-step="4"><div class="node">04</div><div class="ttl">Showtime</div><div class="sub">Go live</div></div>
538
+ </nav>
539
+
540
+ <div class="console">
541
+
542
+ <!-- ---------- LEFT: the control desk ---------- -->
543
+ <div class="desk">
544
+
545
+ <!-- Roll Tape -->
546
+ <div class="card" id="rollCard">
547
+ <div class="card-h"><svg class="i ic"><use href="#i-clap"/></svg><h2>Roll Tape</h2><span class="meta">SRC 01</span></div>
548
+ <div class="drop" id="drop" tabindex="0" role="button" aria-label="Drop a video clip to roll tape">
549
+ <div class="ask">
550
+ <span class="clap"><svg class="i"><use href="#i-clap"/></svg></span>
551
+ <div class="t">Drop a clip on the desk</div>
552
+ <div class="s">MP4 · MOV · up to 60s · portrait, landscape or square</div>
553
+ </div>
554
+ <div class="loaded">
555
+ <div class="strip"><span class="play"><svg class="i"><use href="#i-play"/></svg></span></div>
556
+ <div>
557
+ <div class="fn" id="fileName">rooftop_freekick.mp4</div>
558
+ <div class="fd"><span id="fileDur">00:42</span> · 1080×1920 · ready to cast</div>
559
+ </div>
560
+ </div>
561
+ </div>
562
+ <input type="file" id="fileInput" accept="video/*" hidden>
563
+ <button class="btn cyan mt" id="castBtn" disabled><svg class="i"><use href="#i-spot"/></svg> Find the stars</button>
564
+ <div class="sweep"></div>
565
+ </div>
566
+
567
+ <!-- Pick the show / vibes -->
568
+ <div class="card" id="vibeCard">
569
+ <div class="card-h"><svg class="i ic"><use href="#i-mic"/></svg><h2>Pick the show</h2><span class="meta">TONE</span></div>
570
+ <div class="vibes" id="vibes" role="group" aria-label="Narration vibe">
571
+ <button class="vibe" data-vibe="football" style="--vc:var(--star-lime)" aria-pressed="false">
572
+ <span class="ic"><svg class="i"><use href="#i-whistle"/></svg></span>
573
+ <span><span class="vt">Football</span><span class="vd">Roaring play-by-play</span></span>
574
+ </button>
575
+ <button class="vibe" data-vibe="diva" style="--vc:var(--star-hot)" aria-pressed="false">
576
+ <span class="ic"><svg class="i"><use href="#i-glam"/></svg></span>
577
+ <span><span class="vt">Diva Hour</span><span class="vd">Red-carpet shade</span></span>
578
+ </button>
579
+ <button class="vibe" data-vibe="wildlife" style="--vc:var(--star-cyan)" aria-pressed="false">
580
+ <span class="ic"><svg class="i"><use href="#i-paw"/></svg></span>
581
+ <span><span class="vt">Wildlife</span><span class="vd">Hushed nature doc</span></span>
582
+ </button>
583
+ <button class="vibe" data-vibe="boxing" style="--vc:var(--star-tang)" aria-pressed="false">
584
+ <span class="ic"><svg class="i"><use href="#i-glove"/></svg></span>
585
+ <span><span class="vt">Boxing</span><span class="vd">Ringside hype</span></span>
586
+ </button>
587
+ </div>
588
+
589
+ <!-- Coco the host — re-dresses per vibe -->
590
+ <div class="coco" id="coco">
591
+ <div class="av">
592
+ <svg class="i bird"><use href="#i-bird"/></svg>
593
+ <span class="acc" id="cocoAcc"><svg><use href="#i-star"/></svg></span>
594
+ </div>
595
+ <div class="say">
596
+ <span class="who">Coco · your host</span>
597
+ <span id="cocoLine">Pick a show and I'll set the mood. I dress for the occasion.</span>
598
+ </div>
599
+ </div>
600
+ </div>
601
+
602
+ <!-- Showtime -->
603
+ <button class="btn show" id="liveBtn" disabled><svg class="i"><use href="#i-star"/></svg> Showtime — go live</button>
604
+
605
+ </div>
606
+
607
+ <!-- ---------- RIGHT: the stage ---------- -->
608
+ <div class="stage-panel" id="stagePanel">
609
+ <div class="stage-h">
610
+ <svg class="i ic"><use href="#i-spot"/></svg>
611
+ <h2>Star Select</h2>
612
+ <span class="count" id="starCount">NO CAST YET</span>
613
+ </div>
614
+
615
+ <!-- empty -->
616
+ <div class="empty" id="stageEmpty">
617
+ <div class="spot"><svg class="i"><use href="#i-spot"/></svg></div>
618
+ <div class="t">The stage is dark</div>
619
+ <div class="s">Roll a clip and hit <b>Find the stars</b> — everyone we spot gets a card under the spotlight.</div>
620
+ </div>
621
+
622
+ <!-- roster (filled by JS) -->
623
+ <div class="roster" id="roster" hidden></div>
624
+ </div>
625
+
626
+ </div>
627
+
628
+ <p class="note">
629
+ <svg class="i lock"><use href="#i-lock"/></svg>
630
+ Your clip stays on <b>your machine</b>. Faces are detected locally and never uploaded — Marquee only writes the captions you see.
631
+ </p>
632
+ </section>
633
+
634
+ <!-- ============================================================
635
+ VIEW 2 — BROADCAST PLAYER
636
+ ============================================================ -->
637
+ <section class="view" id="broadcastView">
638
+ <div class="booth-top">
639
+ <button class="back" id="backBtn"><svg class="i"><use href="#i-back"/></svg> Back to console</button>
640
+ <div class="orient" role="group" aria-label="Clip orientation">
641
+ <button data-orient="landscape" aria-pressed="true">16:9</button>
642
+ <button data-orient="square" aria-pressed="false">1:1</button>
643
+ <button data-orient="portrait" aria-pressed="false">9:16</button>
644
+ </div>
645
+ </div>
646
+
647
+ <div class="booth" id="booth">
648
+ <!-- the screen -->
649
+ <div>
650
+ <div class="screen-wrap">
651
+ <div class="screen" id="screen">
652
+ <video id="vid" class="screen-vid" playsinline preload="metadata"></video>
653
+ <div class="ph"><svg class="i"><use href="#i-cam"/></svg>YOUR CLIP PLAYS HERE</div>
654
+ <div class="bug"><span class="dot"></span> ON AIR</div>
655
+ <div class="tc" id="tc">00:00</div>
656
+ <!-- lower-third chyron -->
657
+ <div class="chyron" id="chyron">
658
+ <div class="bar"></div>
659
+ <div class="row"><span class="who" id="chyWho">COCO</span><span class="line" id="chyLine">Standing by…</span></div>
660
+ </div>
661
+ </div>
662
+ </div>
663
+ <div class="transport">
664
+ <button class="pp" id="pp" aria-label="Play / pause"><svg class="i"><use href="#i-play"/></svg></button>
665
+ <div class="scrub" id="scrub"><div class="fill" id="fill"></div><div class="knob" id="knob"></div></div>
666
+ <span class="clock" id="tcDur" style="font-size:16px">00:42</span>
667
+ </div>
668
+ </div>
669
+
670
+ <!-- play-by-play rail -->
671
+ <div class="pbp">
672
+ <div class="pbp-h"><svg class="i ic"><use href="#i-list"/></svg><h3>Play-by-play</h3><span class="ct" id="pbpCt">— lines</span></div>
673
+ <div class="lines" id="lines"></div>
674
+ <div class="export">
675
+ <button class="btn-srt" id="srtBtn"><svg class="i ic"><use href="#i-download"/></svg> Download captions (.srt)</button>
676
+ <div class="hint">Drops straight into <b>CapCut</b>, Premiere &amp; DaVinci — timed to your clip.</div>
677
+ </div>
678
+ </div>
679
+ </div>
680
+ </section>
681
+
682
+ </div>
683
+
684
+ <div class="toast" id="toast"><svg class="i ic"><use href="#i-check"/></svg><span id="toastTxt">Done</span></div>
685
+
686
+ <script type="module">/* ============================================================
687
+ MARQUEE — flow engine, wired to the Gradio backend.
688
+ Runs as a module inside the app iframe.
689
+ Presentation code is unchanged from the design; the mock data
690
+ hooks (casting, commentary, playback) now call the real pipeline.
691
+ ============================================================ */
692
+ import * as GradioClient from "https://cdn.jsdelivr.net/npm/@gradio/client/dist/index.min.js";
693
+ const Client = GradioClient.Client;
694
+ const handle_file = GradioClient.handle_file || ((f) => f);
695
+
696
+ const $ = (s, r = document) => r.querySelector(s);
697
+ const $$ = (s, r = document) => [...r.querySelectorAll(s)];
698
+ const reduce = matchMedia('(prefers-reduced-motion:reduce)').matches;
699
+ const pad = n => String(n).padStart(2, '0');
700
+
701
+ /* ---------- backend client (connects to this same Space) ---------- */
702
+ let _client = null;
703
+ async function api() {
704
+ if (!_client) _client = await Client.connect(window.location.origin);
705
+ return _client;
706
+ }
707
+
708
+ /* ---------- session state from the backend ---------- */
709
+ let SESSION = null, VIDEO_URL = null, DUR = 0, REAL_SCRIPT = [], selectedFile = null;
710
+
711
+ /* ---------- auto-fit the iframe to content (same-origin srcdoc) ---------- */
712
+ function fitFrame() {
713
+ try { if (window.frameElement) window.frameElement.style.height = document.documentElement.scrollHeight + 'px'; }
714
+ catch (e) {}
715
+ }
716
+ new ResizeObserver(fitFrame).observe(document.body);
717
+ window.addEventListener('load', fitFrame);
718
+
719
+ /* ---------- live clock ---------- */
720
+ const clocks = $$('#clock');
721
+ function fmtClock(d){return `${pad(d.getHours())}:${pad(d.getMinutes())}:${pad(d.getSeconds())}`;}
722
+ setInterval(()=>{const t=fmtClock(new Date());clocks.forEach(c=>c.textContent=t);},1000);
723
+ clocks.forEach(c=>c.textContent=fmtClock(new Date()));
724
+
725
+ /* ---------- status pill ---------- */
726
+ const status=$('#status'), statusTxt=$('#statusTxt');
727
+ function setStatus(txt,live){statusTxt.textContent=txt;status.classList.toggle('live',!!live);}
728
+
729
+ /* ---------- rundown ---------- */
730
+ const cues=$$('#rundown .cue');
731
+ function setStep(n){
732
+ cues.forEach(c=>{
733
+ const s=+c.dataset.step;
734
+ c.classList.toggle('done',s<n);
735
+ c.classList.toggle('on',s===n);
736
+ if(s<n)c.querySelector('.node').innerHTML='<svg class="i" style="width:18px;height:18px"><use href="#i-check"/></svg>';
737
+ else c.querySelector('.node').textContent='0'+s;
738
+ });
739
+ }
740
+
741
+ /* ---------- cast colors + tiers (presentation) ---------- */
742
+ const COLORS=['--star-gold','--star-hot','--star-cyan','--star-violet','--star-lime','--star-tang'];
743
+ let stars=[]; // {name, color, tier, crop}
744
+
745
+ /* ---------- VIBES — Coco's wardrobe + the show's voice ----------
746
+ NOTE: the play-by-play LINES are no longer templated here — the real
747
+ commentary comes from the VLM. We keep Coco's persona + colors. */
748
+ const VIBES={
749
+ football:{ acc:'#i-whistle', vc:'--star-lime', who:'COCO · TOUCHLINE',
750
+ coco:"Whistle's on. I'm calling this one like a cup final — keep your eyes on the pitch!"},
751
+ diva:{ acc:'#i-glam', vc:'--star-hot', who:'COCO · RED CARPET',
752
+ coco:"Shades on, boa fluffed. Darling, we are SO ready for the carpet. Bring the drama."},
753
+ wildlife:{ acc:'#i-paw', vc:'--star-cyan', who:'COCO · FIELD UNIT',
754
+ coco:"*whispering* …we must be very quiet now. A rare specimen has entered the frame."},
755
+ boxing:{ acc:'#i-glove', vc:'--star-tang', who:'COCO · RINGSIDE',
756
+ coco:"Gloves laced, ringside seat warm. Ladies and gentlemen… in the RED corner!"}
757
+ };
758
+ let currentVibe=null;
759
+
760
+ /* ---------- ROLL TAPE (real file) ---------- */
761
+ const drop=$('#drop'), castBtn=$('#castBtn'), liveBtn=$('#liveBtn'), rollCard=$('#rollCard');
762
+ const fileInput=$('#fileInput'), fileName=$('#fileName'), fileDur=$('#fileDur');
763
+
764
+ function handleFile(file){
765
+ if(!file) return;
766
+ selectedFile=file;
767
+ drop.classList.add('loaded');
768
+ fileName.textContent=file.name;
769
+ fileDur.textContent='reading…';
770
+ const url=URL.createObjectURL(file);
771
+ const probe=document.createElement('video'); probe.preload='metadata';
772
+ probe.onloadedmetadata=()=>{ fileDur.textContent=fmt(probe.duration)+' · ready to cast'; URL.revokeObjectURL(url); };
773
+ probe.onerror=()=>{ fileDur.textContent='ready to cast'; };
774
+ probe.src=url;
775
+ castBtn.disabled=false;
776
+ setStatus('Tape loaded'); toast('Tape loaded — ready to cast');
777
+ }
778
+ drop.addEventListener('click',()=>fileInput.click());
779
+ drop.addEventListener('keydown',e=>{if(e.key==='Enter'||e.key===' '){e.preventDefault();fileInput.click();}});
780
+ fileInput.addEventListener('change',e=>handleFile(e.target.files[0]));
781
+ ['dragover','dragenter'].forEach(ev=>drop.addEventListener(ev,e=>{e.preventDefault();drop.style.borderColor='var(--cyan)';}));
782
+ ['dragleave','drop'].forEach(ev=>drop.addEventListener(ev,e=>{
783
+ e.preventDefault();drop.style.borderColor='';
784
+ if(ev==='drop'&&e.dataTransfer.files[0])handleFile(e.dataTransfer.files[0]);
785
+ }));
786
+
787
+ /* ---------- CASTING (real scan) ---------- */
788
+ const stagePanel=$('#stagePanel'), stageEmpty=$('#stageEmpty'), roster=$('#roster'), starCount=$('#starCount');
789
+ castBtn.addEventListener('click',startCasting);
790
+
791
+ async function startCasting(){
792
+ if(castBtn.disabled||!selectedFile) return;
793
+ castBtn.disabled=true; rollCard.classList.add('casting');
794
+ setStep(2); setStatus('Casting…');
795
+ castBtn.innerHTML='<svg class="i"><use href="#i-spot"/></svg> Scanning the tape…';
796
+ try{
797
+ const c=await api();
798
+ const res=await c.predict("/scan",[handle_file(selectedFile)]);
799
+ const data=res.data[0];
800
+ if(!data||!data.ok) throw new Error((data&&data.error)||'scan failed');
801
+ SESSION=data.session_id; VIDEO_URL=data.video_url; DUR=data.duration||0;
802
+ setBoothOrient(data.w,data.h);
803
+ const vid=$('#vid'); vid.src=VIDEO_URL; $('#screen').classList.add('has-video');
804
+ revealCast(data.stars||[]);
805
+ }catch(err){
806
+ setStatus('Casting failed');
807
+ toast('Casting failed: '+(err.message||err));
808
+ rollCard.classList.remove('casting');
809
+ castBtn.disabled=false;
810
+ castBtn.innerHTML='<svg class="i"><use href="#i-spot"/></svg> Find the stars';
811
+ }
812
+ }
813
+
814
+ function revealCast(starData){
815
+ rollCard.classList.remove('casting');
816
+ castBtn.innerHTML='<svg class="i"><use href="#i-spot"/></svg> Re-cast';
817
+ castBtn.disabled=false;
818
+ setStep(3); setStatus(starData.length+' stars found');
819
+
820
+ stars = starData.map((s,i)=>({name:'',tier:s.tier||'STAR',color:COLORS[i%COLORS.length],crop:s.crop||null}));
821
+ stageEmpty.style.display='none';
822
+ roster.hidden=false; roster.innerHTML='';
823
+ starCount.textContent=stars.length+' STARS FOUND';
824
+ stars.forEach((s,i)=>roster.appendChild(buildStar(s,i)));
825
+
826
+ if(!stars.length){
827
+ stageEmpty.style.display='';
828
+ stageEmpty.querySelector('.t').textContent='No stars spotted';
829
+ stageEmpty.querySelector('.s').innerHTML='No faces found — but the show still goes on. Pick a vibe and go live for nameless narration.';
830
+ roster.hidden=true;
831
+ }
832
+ toast(stars.length?stars.length+' stars found — give them stage names':'No faces found — you can still go live');
833
+ refreshLive();
834
+ }
835
+
836
+ /* ---------- THE STAR CARD (one reusable block) ---------- */
837
+ function buildStar(s,i){
838
+ const el=document.createElement('div');
839
+ el.className='star';
840
+ el.style.setProperty('--c',`var(${s.color})`);
841
+ if(!reduce){
842
+ el.animate(
843
+ [{opacity:0,transform:'translateY(14px) scale(.98)'},{opacity:1,transform:'none'}],
844
+ {duration:480,delay:i*90,easing:'cubic-bezier(.2,.9,.25,1)',fill:'backwards'}
845
+ );
846
+ }
847
+ const portrait = s.crop
848
+ ? `<img class="head-img" src="${s.crop}" alt="Player ${i+1}">`
849
+ : `<svg class="i head-ph" style="width:64px;height:68px"><use href="#i-portrait"/></svg>`;
850
+ el.innerHTML=`
851
+ <div class="portrait">
852
+ <div class="beam"></div>
853
+ ${portrait}
854
+ <div class="frame"></div>
855
+ <span class="idx"><span class="st">★</span>${i+1}</span>
856
+ <span class="tier">${s.tier}</span>
857
+ <svg class="i crown"><use href="#i-crown"/></svg>
858
+ </div>
859
+ <div class="star-body">
860
+ <label for="star${i}">Stage name</label>
861
+ <input id="star${i}" type="text" placeholder="e.g. The Maestro" maxlength="22" autocomplete="off">
862
+ </div>`;
863
+ const input=el.querySelector('input');
864
+ input.addEventListener('input',()=>{
865
+ const v=input.value.trim();
866
+ s.name=v;
867
+ const wasNamed=el.classList.contains('named');
868
+ el.classList.toggle('named',!!v);
869
+ if(v && !wasNamed && !reduce) popCrown(el);
870
+ refreshLive();
871
+ });
872
+ return el;
873
+ }
874
+ function popCrown(el){
875
+ const c=el.querySelector('.crown');
876
+ c.style.animation='none';void c.offsetWidth;
877
+ c.animate([{transform:'translateX(-50%) translateY(10px) scale(.4) rotate(-12deg)',opacity:0},
878
+ {transform:'translateX(-50%) scale(1.25) rotate(8deg)',opacity:1,offset:.6},
879
+ {transform:'translateX(-50%) scale(1) rotate(0)',opacity:1}],
880
+ {duration:480,easing:'cubic-bezier(.2,1.7,.4,1)'});
881
+ }
882
+
883
+ /* ---------- VIBES + COCO ---------- */
884
+ const vibesWrap=$('#vibes'), coco=$('#coco'), cocoLine=$('#cocoLine'),
885
+ cocoAcc=$('#cocoAcc'), cocoWho=coco.querySelector('.who');
886
+ vibesWrap.addEventListener('click',e=>{
887
+ const b=e.target.closest('.vibe'); if(!b)return;
888
+ $$('.vibe',vibesWrap).forEach(v=>v.setAttribute('aria-pressed','false'));
889
+ b.setAttribute('aria-pressed','true');
890
+ currentVibe=b.dataset.vibe;
891
+ dressCoco(currentVibe);
892
+ refreshLive();
893
+ });
894
+ function dressCoco(v){
895
+ const d=VIBES[v]; if(!d)return;
896
+ coco.style.setProperty('--vc',`var(${d.vc})`);
897
+ cocoAcc.innerHTML=`<svg><use href="${d.acc}"/></svg>`;
898
+ cocoWho.textContent=d.who;
899
+ cocoLine.textContent=d.coco;
900
+ if(!reduce){
901
+ coco.animate([{transform:'scale(.98)',opacity:.6},{transform:'scale(1)',opacity:1}],{duration:300,easing:'ease-out'});
902
+ const av=coco.querySelector('.av');
903
+ av.animate([{transform:'rotate(-8deg) scale(.9)'},{transform:'rotate(6deg) scale(1.08)'},{transform:'none'}],{duration:420,easing:'cubic-bezier(.2,1.7,.4,1)'});
904
+ }
905
+ }
906
+
907
+ /* ---------- LIVE-READY GATE ----------
908
+ ready when a vibe is picked (names optional — nameless narration is allowed) */
909
+ function namedCount(){return stars.filter(s=>s.name).length;}
910
+ function refreshLive(){
911
+ liveBtn.disabled=!(currentVibe && SESSION);
912
+ if(stars.length){
913
+ const n=namedCount();
914
+ starCount.textContent = n? `${n}/${stars.length} NAMED` : `${stars.length} STARS FOUND`;
915
+ }
916
+ }
917
+
918
+ /* ---------- SHOWTIME (real commentary) ---------- */
919
+ liveBtn.addEventListener('click',async ()=>{
920
+ if(liveBtn.disabled)return;
921
+ liveBtn.disabled=true;
922
+ liveBtn.innerHTML='<svg class="i"><use href="#i-star"/></svg> Rolling cameras…';
923
+ setStatus('Generating…');
924
+ try{
925
+ const names=stars.map(s=>s.name||'');
926
+ const c=await api();
927
+ const res=await c.predict("/generate",[SESSION, names, currentVibe]);
928
+ const data=res.data[0];
929
+ if(!data||!data.ok) throw new Error((data&&data.error)||'generate failed');
930
+ REAL_SCRIPT=data.script||[];
931
+ setStep(4); setStatus('On air',true);
932
+ if(!reduce) confetti();
933
+ buildBroadcast();
934
+ setTimeout(()=>{switchView('broadcast');fitFrame();}, reduce?0:450);
935
+ }catch(err){
936
+ setStatus('Showtime failed');
937
+ toast('Showtime failed: '+(err.message||err));
938
+ }finally{
939
+ liveBtn.disabled=false;
940
+ liveBtn.innerHTML='<svg class="i"><use href="#i-star"/></svg> Showtime — go live';
941
+ }
942
+ });
943
+
944
+ /* ---------- VIEW SWITCH ---------- */
945
+ const setupView=$('#setupView'), broadcastView=$('#broadcastView');
946
+ function switchView(which){
947
+ const toBroadcast=which==='broadcast';
948
+ setupView.classList.toggle('active',!toBroadcast);
949
+ broadcastView.classList.toggle('active',toBroadcast);
950
+ window.scrollTo({top:0,behavior:reduce?'auto':'smooth'});
951
+ fitFrame();
952
+ }
953
+ $('#backBtn').addEventListener('click',()=>{switchView('setup');const v=$('#vid');if(v)v.pause();});
954
+
955
+ /* ============================================================
956
+ BROADCAST PLAYER — driven by a real <video>
957
+ ============================================================ */
958
+ let script=[]; // {t, who, text, cc}
959
+ const vid=$('#vid'), screen=$('#screen'), chyron=$('#chyron'), chyWho=$('#chyWho'), chyLine=$('#chyLine'),
960
+ linesEl=$('#lines'), pbpCt=$('#pbpCt'), tcEl=$('#tc'), fill=$('#fill'), knob=$('#knob'),
961
+ pp=$('#pp'), scrub=$('#scrub'), tcDur=$('#tcDur');
962
+ const ICON_PLAY='<svg class="i"><use href="#i-play"/></svg>';
963
+ const ICON_PAUSE='<svg class="i"><use href="#i-pause"/></svg>';
964
+
965
+ function fmt(s){s=isFinite(s)?s:0;return pad(Math.floor(s/60))+':'+pad(Math.floor(s%60));}
966
+
967
+ function buildBroadcast(){
968
+ const d=VIBES[currentVibe]||{vc:'--hot',who:'COCO'};
969
+ const who=(d.who||'COCO').replace('COCO · ','');
970
+ script=(REAL_SCRIPT||[]).map(l=>({t:+l.t||0, text:l.text||'', who, cc:d.vc}));
971
+ linesEl.innerHTML='';
972
+ pbpCt.textContent=script.length+' LINES';
973
+ if(!script.length){
974
+ linesEl.innerHTML='<div class="pl"><span class="tx">No commentary generated — try another clip.</span></div>';
975
+ }
976
+ script.forEach((l,i)=>{
977
+ const row=document.createElement('div');
978
+ row.className='pl'; row.dataset.i=i;
979
+ row.style.setProperty('--cc',`var(${l.cc})`);
980
+ row.innerHTML=`<span class="t">${fmt(l.t)}</span><span class="tx"><b>${l.who}</b> · ${l.text}</span>`;
981
+ row.addEventListener('click',()=>seek(l.t));
982
+ linesEl.appendChild(row);
983
+ });
984
+ if(vid){ try{vid.currentTime=0;}catch(e){} }
985
+ activeLine=-1; renderPlayhead();
986
+ }
987
+
988
+ /* playback engine — reads the real video element */
989
+ let activeLine=-1, rafP=null;
990
+ function dur(){ return DUR || (vid&&vid.duration) || 0; }
991
+ function renderPlayhead(){
992
+ const D=dur(), cur=(vid&&vid.currentTime)||0;
993
+ const pct=D?(cur/D)*100:0;
994
+ fill.style.width=pct+'%'; knob.style.left=pct+'%'; tcEl.textContent=fmt(cur);
995
+ let idx=-1;
996
+ for(let i=0;i<script.length;i++){if(script[i].t<=cur)idx=i;else break;}
997
+ if(idx!==activeLine){
998
+ activeLine=idx;
999
+ $$('.pl',linesEl).forEach((r,i)=>r.classList.toggle('now',i===idx));
1000
+ showChyron(idx);
1001
+ if(idx>=0){const r=linesEl.children[idx];const top=r.offsetTop-linesEl.clientHeight/2+r.clientHeight/2;
1002
+ linesEl.scrollTo({top,behavior:reduce?'auto':'smooth'});}
1003
+ }
1004
+ }
1005
+ function showChyron(idx){
1006
+ if(idx<0){chyron.classList.remove('in');return;}
1007
+ const l=script[idx];
1008
+ chyron.classList.remove('in');
1009
+ requestAnimationFrame(()=>{
1010
+ chyron.style.setProperty('--cc',`var(${l.cc})`);
1011
+ chyWho.textContent=l.who; chyLine.textContent=l.text;
1012
+ requestAnimationFrame(()=>chyron.classList.add('in'));
1013
+ });
1014
+ }
1015
+ function loop(){ renderPlayhead(); if(vid && !vid.paused && !vid.ended) rafP=requestAnimationFrame(loop); }
1016
+ function seek(t){ if(vid) vid.currentTime=Math.max(0,Math.min(dur(),t)); }
1017
+
1018
+ if(vid){
1019
+ vid.addEventListener('loadedmetadata',()=>{ if(vid.duration&&isFinite(vid.duration))DUR=vid.duration; tcDur.textContent=fmt(dur()); renderPlayhead(); });
1020
+ vid.addEventListener('play',()=>{ pp.innerHTML=ICON_PAUSE; loop(); });
1021
+ vid.addEventListener('pause',()=>{ pp.innerHTML=ICON_PLAY; if(rafP)cancelAnimationFrame(rafP); });
1022
+ vid.addEventListener('ended',()=>{ pp.innerHTML=ICON_PLAY; });
1023
+ vid.addEventListener('timeupdate',renderPlayhead);
1024
+ }
1025
+ pp.addEventListener('click',()=>{ if(!vid)return; vid.paused?vid.play():vid.pause(); });
1026
+ scrub.addEventListener('click',e=>{
1027
+ const r=scrub.getBoundingClientRect();
1028
+ seek(((e.clientX-r.left)/r.width)*dur());
1029
+ });
1030
+
1031
+ /* orientation toggle (manual) + auto from real dims */
1032
+ const booth=$('#booth');
1033
+ function setBoothOrient(w,h){
1034
+ booth.classList.remove('portrait','square');
1035
+ let o='landscape';
1036
+ if(w&&h){ if(Math.abs(w-h)/Math.max(w,h)<0.08) o='square'; else if(h>w) o='portrait'; }
1037
+ if(o!=='landscape') booth.classList.add(o);
1038
+ $$('.orient button').forEach(x=>x.setAttribute('aria-pressed', String(x.dataset.orient===o)));
1039
+ }
1040
+ $$('.orient button').forEach(b=>b.addEventListener('click',()=>{
1041
+ $$('.orient button').forEach(x=>x.setAttribute('aria-pressed','false'));
1042
+ b.setAttribute('aria-pressed','true');
1043
+ booth.classList.remove('portrait','square');
1044
+ if(b.dataset.orient!=='landscape')booth.classList.add(b.dataset.orient);
1045
+ fitFrame();
1046
+ }));
1047
+
1048
+ /* ---------- SRT EXPORT (CapCut-ready) ---------- */
1049
+ $('#srtBtn').addEventListener('click',()=>{
1050
+ if(!script.length){toast('Go live first');return;}
1051
+ const D=dur();
1052
+ const srt=script.map((l,i)=>{
1053
+ const start=l.t, end=Math.min(D||l.t+4, (script[i+1]?script[i+1].t:l.t+4));
1054
+ return `${i+1}\n${srtTime(start)} --> ${srtTime(end)}\n${l.who}: ${l.text}\n`;
1055
+ }).join('\n');
1056
+ const blob=new Blob([srt],{type:'text/plain'});
1057
+ const a=document.createElement('a');
1058
+ a.href=URL.createObjectURL(blob);
1059
+ a.download=`marquee_${currentVibe||'show'}_captions.srt`;
1060
+ a.click(); URL.revokeObjectURL(a.href);
1061
+ toast('Captions exported — drop the .srt into CapCut');
1062
+ });
1063
+ function srtTime(s){
1064
+ const h=Math.floor(s/3600), m=Math.floor((s%3600)/60), sec=Math.floor(s%60);
1065
+ return `${pad(h)}:${pad(m)}:${pad(sec)},000`;
1066
+ }
1067
+
1068
+ /* ---------- confetti ---------- */
1069
+ function confetti(){
1070
+ const cols=['#FFC94D','#FF4D9A','#3DE7FF','#C18BFF','#9BE85B','#FF8A4D'];
1071
+ for(let i=0;i<70;i++){
1072
+ const c=document.createElement('div');
1073
+ c.className='confetti';
1074
+ c.style.left=Math.random()*100+'vw';
1075
+ c.style.background=cols[i%cols.length];
1076
+ c.style.animationDuration=(1.6+Math.random()*1.6)+'s';
1077
+ c.style.animationDelay=(Math.random()*.3)+'s';
1078
+ c.style.transform=`rotate(${Math.random()*360}deg)`;
1079
+ if(Math.random()>.5)c.style.borderRadius='50%';
1080
+ document.body.appendChild(c);
1081
+ setTimeout(()=>c.remove(),3600);
1082
+ }
1083
+ }
1084
+
1085
+ /* ---------- toast ---------- */
1086
+ const toastEl=$('#toast'), toastTxt=$('#toastTxt'); let toastT;
1087
+ function toast(msg){
1088
+ toastTxt.textContent=msg; toastEl.classList.add('show');
1089
+ clearTimeout(toastT); toastT=setTimeout(()=>toastEl.classList.remove('show'),2600);
1090
+ }
1091
+ </script>
1092
+ </body>
1093
+ </html>
models/intel/face-detection-retail-0004/FP16/face-detection-retail-0004.bin ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:d0a8ca818c657b454ce6118715c49027b1b70d9d85a83a9a4a6606d773b02dfe
3
+ size 1176544
models/intel/face-detection-retail-0004/FP16/face-detection-retail-0004.xml ADDED
The diff for this file is too large to render. See raw diff
 
models/intel/face-reidentification-retail-0095/FP16/face-reidentification-retail-0095.bin ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:241229ca3d206321868d46ce74a3c0b06c49cea58db7dc70b2e842ff287545d1
3
+ size 2213628
models/intel/face-reidentification-retail-0095/FP16/face-reidentification-retail-0095.xml ADDED
The diff for this file is too large to render. See raw diff
 
models/intel/landmarks-regression-retail-0009/FP16/landmarks-regression-retail-0009.bin ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:a7285eec8cb20a50c0ed567c426db6c7b8753c51b47f798f8ef5d8e0fa12d3c5
3
+ size 381232
models/intel/landmarks-regression-retail-0009/FP16/landmarks-regression-retail-0009.xml ADDED
@@ -0,0 +1,2607 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?xml version="1.0"?>
2
+ <net name="torch-jit-export" version="11">
3
+ <layers>
4
+ <layer id="0" name="0" type="Parameter" version="opset1">
5
+ <data shape="1,3,48,48" element_type="f32" />
6
+ <rt_info>
7
+ <attribute name="old_api_map_element_type" version="0" value="f16" />
8
+ </rt_info>
9
+ <output>
10
+ <port id="0" precision="FP32" names="0">
11
+ <dim>1</dim>
12
+ <dim>3</dim>
13
+ <dim>48</dim>
14
+ <dim>48</dim>
15
+ <rt_info>
16
+ <attribute name="layout" version="0" layout="[N,C,H,W]" />
17
+ </rt_info>
18
+ </port>
19
+ </output>
20
+ </layer>
21
+ <layer id="1" name="Gather_2181_compressed" type="Const" version="opset1">
22
+ <data element_type="f16" shape="1, 3, 1, 1" offset="0" size="6" />
23
+ <rt_info>
24
+ <attribute name="preprocessing" version="0" />
25
+ </rt_info>
26
+ <output>
27
+ <port id="0" precision="FP16">
28
+ <dim>1</dim>
29
+ <dim>3</dim>
30
+ <dim>1</dim>
31
+ <dim>1</dim>
32
+ </port>
33
+ </output>
34
+ </layer>
35
+ <layer id="2" name="Gather_2181" type="Convert" version="opset1">
36
+ <data destination_type="f32" />
37
+ <rt_info>
38
+ <attribute name="decompression" version="0" />
39
+ <attribute name="preprocessing" version="0" />
40
+ </rt_info>
41
+ <input>
42
+ <port id="0" precision="FP16">
43
+ <dim>1</dim>
44
+ <dim>3</dim>
45
+ <dim>1</dim>
46
+ <dim>1</dim>
47
+ </port>
48
+ </input>
49
+ <output>
50
+ <port id="1" precision="FP32">
51
+ <dim>1</dim>
52
+ <dim>3</dim>
53
+ <dim>1</dim>
54
+ <dim>1</dim>
55
+ </port>
56
+ </output>
57
+ </layer>
58
+ <layer id="3" name="Multiply_2060" type="Multiply" version="opset1">
59
+ <data auto_broadcast="numpy" />
60
+ <rt_info>
61
+ <attribute name="preprocessing" version="0" />
62
+ </rt_info>
63
+ <input>
64
+ <port id="0" precision="FP32">
65
+ <dim>1</dim>
66
+ <dim>3</dim>
67
+ <dim>48</dim>
68
+ <dim>48</dim>
69
+ </port>
70
+ <port id="1" precision="FP32">
71
+ <dim>1</dim>
72
+ <dim>3</dim>
73
+ <dim>1</dim>
74
+ <dim>1</dim>
75
+ </port>
76
+ </input>
77
+ <output>
78
+ <port id="2" precision="FP32">
79
+ <dim>1</dim>
80
+ <dim>3</dim>
81
+ <dim>48</dim>
82
+ <dim>48</dim>
83
+ </port>
84
+ </output>
85
+ </layer>
86
+ <layer id="4" name="Gather_2184_compressed" type="Const" version="opset1">
87
+ <data element_type="f16" shape="1, 3, 1, 1" offset="6" size="6" />
88
+ <rt_info>
89
+ <attribute name="preprocessing" version="0" />
90
+ </rt_info>
91
+ <output>
92
+ <port id="0" precision="FP16">
93
+ <dim>1</dim>
94
+ <dim>3</dim>
95
+ <dim>1</dim>
96
+ <dim>1</dim>
97
+ </port>
98
+ </output>
99
+ </layer>
100
+ <layer id="5" name="Gather_2184" type="Convert" version="opset1">
101
+ <data destination_type="f32" />
102
+ <rt_info>
103
+ <attribute name="decompression" version="0" />
104
+ <attribute name="preprocessing" version="0" />
105
+ </rt_info>
106
+ <input>
107
+ <port id="0" precision="FP16">
108
+ <dim>1</dim>
109
+ <dim>3</dim>
110
+ <dim>1</dim>
111
+ <dim>1</dim>
112
+ </port>
113
+ </input>
114
+ <output>
115
+ <port id="1" precision="FP32">
116
+ <dim>1</dim>
117
+ <dim>3</dim>
118
+ <dim>1</dim>
119
+ <dim>1</dim>
120
+ </port>
121
+ </output>
122
+ </layer>
123
+ <layer id="6" name="67" type="Add" version="opset1">
124
+ <data auto_broadcast="numpy" />
125
+ <input>
126
+ <port id="0" precision="FP32">
127
+ <dim>1</dim>
128
+ <dim>3</dim>
129
+ <dim>48</dim>
130
+ <dim>48</dim>
131
+ </port>
132
+ <port id="1" precision="FP32">
133
+ <dim>1</dim>
134
+ <dim>3</dim>
135
+ <dim>1</dim>
136
+ <dim>1</dim>
137
+ </port>
138
+ </input>
139
+ <output>
140
+ <port id="2" precision="FP32" names="67">
141
+ <dim>1</dim>
142
+ <dim>3</dim>
143
+ <dim>48</dim>
144
+ <dim>48</dim>
145
+ </port>
146
+ </output>
147
+ </layer>
148
+ <layer id="7" name="Gather_2187_compressed" type="Const" version="opset1">
149
+ <data element_type="f16" shape="16, 3, 3, 3" offset="12" size="864" />
150
+ <rt_info>
151
+ <attribute name="preprocessing" version="0" />
152
+ </rt_info>
153
+ <output>
154
+ <port id="0" precision="FP16">
155
+ <dim>16</dim>
156
+ <dim>3</dim>
157
+ <dim>3</dim>
158
+ <dim>3</dim>
159
+ </port>
160
+ </output>
161
+ </layer>
162
+ <layer id="8" name="Gather_2187" type="Convert" version="opset1">
163
+ <data destination_type="f32" />
164
+ <rt_info>
165
+ <attribute name="decompression" version="0" />
166
+ <attribute name="preprocessing" version="0" />
167
+ </rt_info>
168
+ <input>
169
+ <port id="0" precision="FP16">
170
+ <dim>16</dim>
171
+ <dim>3</dim>
172
+ <dim>3</dim>
173
+ <dim>3</dim>
174
+ </port>
175
+ </input>
176
+ <output>
177
+ <port id="1" precision="FP32">
178
+ <dim>16</dim>
179
+ <dim>3</dim>
180
+ <dim>3</dim>
181
+ <dim>3</dim>
182
+ </port>
183
+ </output>
184
+ </layer>
185
+ <layer id="9" name="68/WithoutBiases" type="Convolution" version="opset1">
186
+ <data strides="1, 1" dilations="1, 1" pads_begin="1, 1" pads_end="1, 1" auto_pad="explicit" />
187
+ <input>
188
+ <port id="0" precision="FP32">
189
+ <dim>1</dim>
190
+ <dim>3</dim>
191
+ <dim>48</dim>
192
+ <dim>48</dim>
193
+ </port>
194
+ <port id="1" precision="FP32">
195
+ <dim>16</dim>
196
+ <dim>3</dim>
197
+ <dim>3</dim>
198
+ <dim>3</dim>
199
+ </port>
200
+ </input>
201
+ <output>
202
+ <port id="2" precision="FP32">
203
+ <dim>1</dim>
204
+ <dim>16</dim>
205
+ <dim>48</dim>
206
+ <dim>48</dim>
207
+ </port>
208
+ </output>
209
+ </layer>
210
+ <layer id="10" name="Reshape_84_compressed" type="Const" version="opset1">
211
+ <data element_type="f16" shape="1, 16, 1, 1" offset="876" size="32" />
212
+ <output>
213
+ <port id="0" precision="FP16">
214
+ <dim>1</dim>
215
+ <dim>16</dim>
216
+ <dim>1</dim>
217
+ <dim>1</dim>
218
+ </port>
219
+ </output>
220
+ </layer>
221
+ <layer id="11" name="Reshape_84" type="Convert" version="opset1">
222
+ <data destination_type="f32" />
223
+ <rt_info>
224
+ <attribute name="decompression" version="0" />
225
+ </rt_info>
226
+ <input>
227
+ <port id="0" precision="FP16">
228
+ <dim>1</dim>
229
+ <dim>16</dim>
230
+ <dim>1</dim>
231
+ <dim>1</dim>
232
+ </port>
233
+ </input>
234
+ <output>
235
+ <port id="1" precision="FP32">
236
+ <dim>1</dim>
237
+ <dim>16</dim>
238
+ <dim>1</dim>
239
+ <dim>1</dim>
240
+ </port>
241
+ </output>
242
+ </layer>
243
+ <layer id="12" name="68" type="Add" version="opset1">
244
+ <data auto_broadcast="numpy" />
245
+ <input>
246
+ <port id="0" precision="FP32">
247
+ <dim>1</dim>
248
+ <dim>16</dim>
249
+ <dim>48</dim>
250
+ <dim>48</dim>
251
+ </port>
252
+ <port id="1" precision="FP32">
253
+ <dim>1</dim>
254
+ <dim>16</dim>
255
+ <dim>1</dim>
256
+ <dim>1</dim>
257
+ </port>
258
+ </input>
259
+ <output>
260
+ <port id="2" precision="FP32" names="68">
261
+ <dim>1</dim>
262
+ <dim>16</dim>
263
+ <dim>48</dim>
264
+ <dim>48</dim>
265
+ </port>
266
+ </output>
267
+ </layer>
268
+ <layer id="13" name="8_compressed" type="Const" version="opset1">
269
+ <data element_type="f16" shape="1" offset="908" size="2" />
270
+ <output>
271
+ <port id="0" precision="FP16">
272
+ <dim>1</dim>
273
+ </port>
274
+ </output>
275
+ </layer>
276
+ <layer id="14" name="8" type="Convert" version="opset1">
277
+ <data destination_type="f32" />
278
+ <rt_info>
279
+ <attribute name="decompression" version="0" />
280
+ </rt_info>
281
+ <input>
282
+ <port id="0" precision="FP16">
283
+ <dim>1</dim>
284
+ </port>
285
+ </input>
286
+ <output>
287
+ <port id="1" precision="FP32" names="8">
288
+ <dim>1</dim>
289
+ </port>
290
+ </output>
291
+ </layer>
292
+ <layer id="15" name="69" type="PReLU" version="opset1">
293
+ <input>
294
+ <port id="0" precision="FP32">
295
+ <dim>1</dim>
296
+ <dim>16</dim>
297
+ <dim>48</dim>
298
+ <dim>48</dim>
299
+ </port>
300
+ <port id="1" precision="FP32">
301
+ <dim>1</dim>
302
+ </port>
303
+ </input>
304
+ <output>
305
+ <port id="2" precision="FP32" names="69">
306
+ <dim>1</dim>
307
+ <dim>16</dim>
308
+ <dim>48</dim>
309
+ <dim>48</dim>
310
+ </port>
311
+ </output>
312
+ </layer>
313
+ <layer id="16" name="70" type="MaxPool" version="opset1">
314
+ <data strides="2, 2" pads_begin="0, 0" pads_end="0, 0" kernel="2, 2" rounding_type="floor" auto_pad="explicit" />
315
+ <input>
316
+ <port id="0" precision="FP32">
317
+ <dim>1</dim>
318
+ <dim>16</dim>
319
+ <dim>48</dim>
320
+ <dim>48</dim>
321
+ </port>
322
+ </input>
323
+ <output>
324
+ <port id="1" precision="FP32" names="70">
325
+ <dim>1</dim>
326
+ <dim>16</dim>
327
+ <dim>24</dim>
328
+ <dim>24</dim>
329
+ </port>
330
+ </output>
331
+ </layer>
332
+ <layer id="17" name="Constant_1660_compressed" type="Const" version="opset1">
333
+ <data element_type="f16" shape="1, 16, 1, 1" offset="910" size="32" />
334
+ <output>
335
+ <port id="0" precision="FP16">
336
+ <dim>1</dim>
337
+ <dim>16</dim>
338
+ <dim>1</dim>
339
+ <dim>1</dim>
340
+ </port>
341
+ </output>
342
+ </layer>
343
+ <layer id="18" name="Constant_1660" type="Convert" version="opset1">
344
+ <data destination_type="f32" />
345
+ <rt_info>
346
+ <attribute name="decompression" version="0" />
347
+ </rt_info>
348
+ <input>
349
+ <port id="0" precision="FP16">
350
+ <dim>1</dim>
351
+ <dim>16</dim>
352
+ <dim>1</dim>
353
+ <dim>1</dim>
354
+ </port>
355
+ </input>
356
+ <output>
357
+ <port id="1" precision="FP32">
358
+ <dim>1</dim>
359
+ <dim>16</dim>
360
+ <dim>1</dim>
361
+ <dim>1</dim>
362
+ </port>
363
+ </output>
364
+ </layer>
365
+ <layer id="19" name="Multiply_2070" type="Multiply" version="opset1">
366
+ <data auto_broadcast="numpy" />
367
+ <input>
368
+ <port id="0" precision="FP32">
369
+ <dim>1</dim>
370
+ <dim>16</dim>
371
+ <dim>24</dim>
372
+ <dim>24</dim>
373
+ </port>
374
+ <port id="1" precision="FP32">
375
+ <dim>1</dim>
376
+ <dim>16</dim>
377
+ <dim>1</dim>
378
+ <dim>1</dim>
379
+ </port>
380
+ </input>
381
+ <output>
382
+ <port id="2" precision="FP32">
383
+ <dim>1</dim>
384
+ <dim>16</dim>
385
+ <dim>24</dim>
386
+ <dim>24</dim>
387
+ </port>
388
+ </output>
389
+ </layer>
390
+ <layer id="20" name="Constant_2075_compressed" type="Const" version="opset1">
391
+ <data element_type="f16" shape="1, 16, 1, 1" offset="942" size="32" />
392
+ <output>
393
+ <port id="0" precision="FP16">
394
+ <dim>1</dim>
395
+ <dim>16</dim>
396
+ <dim>1</dim>
397
+ <dim>1</dim>
398
+ </port>
399
+ </output>
400
+ </layer>
401
+ <layer id="21" name="Constant_2075" type="Convert" version="opset1">
402
+ <data destination_type="f32" />
403
+ <rt_info>
404
+ <attribute name="decompression" version="0" />
405
+ </rt_info>
406
+ <input>
407
+ <port id="0" precision="FP16">
408
+ <dim>1</dim>
409
+ <dim>16</dim>
410
+ <dim>1</dim>
411
+ <dim>1</dim>
412
+ </port>
413
+ </input>
414
+ <output>
415
+ <port id="1" precision="FP32">
416
+ <dim>1</dim>
417
+ <dim>16</dim>
418
+ <dim>1</dim>
419
+ <dim>1</dim>
420
+ </port>
421
+ </output>
422
+ </layer>
423
+ <layer id="22" name="71" type="Add" version="opset1">
424
+ <data auto_broadcast="numpy" />
425
+ <input>
426
+ <port id="0" precision="FP32">
427
+ <dim>1</dim>
428
+ <dim>16</dim>
429
+ <dim>24</dim>
430
+ <dim>24</dim>
431
+ </port>
432
+ <port id="1" precision="FP32">
433
+ <dim>1</dim>
434
+ <dim>16</dim>
435
+ <dim>1</dim>
436
+ <dim>1</dim>
437
+ </port>
438
+ </input>
439
+ <output>
440
+ <port id="2" precision="FP32" names="71">
441
+ <dim>1</dim>
442
+ <dim>16</dim>
443
+ <dim>24</dim>
444
+ <dim>24</dim>
445
+ </port>
446
+ </output>
447
+ </layer>
448
+ <layer id="23" name="14_compressed" type="Const" version="opset1">
449
+ <data element_type="f16" shape="32, 16, 3, 3" offset="974" size="9216" />
450
+ <output>
451
+ <port id="0" precision="FP16">
452
+ <dim>32</dim>
453
+ <dim>16</dim>
454
+ <dim>3</dim>
455
+ <dim>3</dim>
456
+ </port>
457
+ </output>
458
+ </layer>
459
+ <layer id="24" name="14" type="Convert" version="opset1">
460
+ <data destination_type="f32" />
461
+ <rt_info>
462
+ <attribute name="decompression" version="0" />
463
+ </rt_info>
464
+ <input>
465
+ <port id="0" precision="FP16">
466
+ <dim>32</dim>
467
+ <dim>16</dim>
468
+ <dim>3</dim>
469
+ <dim>3</dim>
470
+ </port>
471
+ </input>
472
+ <output>
473
+ <port id="1" precision="FP32" names="14">
474
+ <dim>32</dim>
475
+ <dim>16</dim>
476
+ <dim>3</dim>
477
+ <dim>3</dim>
478
+ </port>
479
+ </output>
480
+ </layer>
481
+ <layer id="25" name="72/WithoutBiases" type="Convolution" version="opset1">
482
+ <data strides="1, 1" dilations="1, 1" pads_begin="1, 1" pads_end="1, 1" auto_pad="explicit" />
483
+ <input>
484
+ <port id="0" precision="FP32">
485
+ <dim>1</dim>
486
+ <dim>16</dim>
487
+ <dim>24</dim>
488
+ <dim>24</dim>
489
+ </port>
490
+ <port id="1" precision="FP32">
491
+ <dim>32</dim>
492
+ <dim>16</dim>
493
+ <dim>3</dim>
494
+ <dim>3</dim>
495
+ </port>
496
+ </input>
497
+ <output>
498
+ <port id="2" precision="FP32">
499
+ <dim>1</dim>
500
+ <dim>32</dim>
501
+ <dim>24</dim>
502
+ <dim>24</dim>
503
+ </port>
504
+ </output>
505
+ </layer>
506
+ <layer id="26" name="Reshape_107_compressed" type="Const" version="opset1">
507
+ <data element_type="f16" shape="1, 32, 1, 1" offset="10190" size="64" />
508
+ <output>
509
+ <port id="0" precision="FP16">
510
+ <dim>1</dim>
511
+ <dim>32</dim>
512
+ <dim>1</dim>
513
+ <dim>1</dim>
514
+ </port>
515
+ </output>
516
+ </layer>
517
+ <layer id="27" name="Reshape_107" type="Convert" version="opset1">
518
+ <data destination_type="f32" />
519
+ <rt_info>
520
+ <attribute name="decompression" version="0" />
521
+ </rt_info>
522
+ <input>
523
+ <port id="0" precision="FP16">
524
+ <dim>1</dim>
525
+ <dim>32</dim>
526
+ <dim>1</dim>
527
+ <dim>1</dim>
528
+ </port>
529
+ </input>
530
+ <output>
531
+ <port id="1" precision="FP32">
532
+ <dim>1</dim>
533
+ <dim>32</dim>
534
+ <dim>1</dim>
535
+ <dim>1</dim>
536
+ </port>
537
+ </output>
538
+ </layer>
539
+ <layer id="28" name="72" type="Add" version="opset1">
540
+ <data auto_broadcast="numpy" />
541
+ <input>
542
+ <port id="0" precision="FP32">
543
+ <dim>1</dim>
544
+ <dim>32</dim>
545
+ <dim>24</dim>
546
+ <dim>24</dim>
547
+ </port>
548
+ <port id="1" precision="FP32">
549
+ <dim>1</dim>
550
+ <dim>32</dim>
551
+ <dim>1</dim>
552
+ <dim>1</dim>
553
+ </port>
554
+ </input>
555
+ <output>
556
+ <port id="2" precision="FP32" names="72">
557
+ <dim>1</dim>
558
+ <dim>32</dim>
559
+ <dim>24</dim>
560
+ <dim>24</dim>
561
+ </port>
562
+ </output>
563
+ </layer>
564
+ <layer id="29" name="16_compressed" type="Const" version="opset1">
565
+ <data element_type="f16" shape="1" offset="10254" size="2" />
566
+ <output>
567
+ <port id="0" precision="FP16">
568
+ <dim>1</dim>
569
+ </port>
570
+ </output>
571
+ </layer>
572
+ <layer id="30" name="16" type="Convert" version="opset1">
573
+ <data destination_type="f32" />
574
+ <rt_info>
575
+ <attribute name="decompression" version="0" />
576
+ </rt_info>
577
+ <input>
578
+ <port id="0" precision="FP16">
579
+ <dim>1</dim>
580
+ </port>
581
+ </input>
582
+ <output>
583
+ <port id="1" precision="FP32" names="16">
584
+ <dim>1</dim>
585
+ </port>
586
+ </output>
587
+ </layer>
588
+ <layer id="31" name="73" type="PReLU" version="opset1">
589
+ <input>
590
+ <port id="0" precision="FP32">
591
+ <dim>1</dim>
592
+ <dim>32</dim>
593
+ <dim>24</dim>
594
+ <dim>24</dim>
595
+ </port>
596
+ <port id="1" precision="FP32">
597
+ <dim>1</dim>
598
+ </port>
599
+ </input>
600
+ <output>
601
+ <port id="2" precision="FP32" names="73">
602
+ <dim>1</dim>
603
+ <dim>32</dim>
604
+ <dim>24</dim>
605
+ <dim>24</dim>
606
+ </port>
607
+ </output>
608
+ </layer>
609
+ <layer id="32" name="74" type="MaxPool" version="opset1">
610
+ <data strides="2, 2" pads_begin="0, 0" pads_end="0, 0" kernel="2, 2" rounding_type="floor" auto_pad="explicit" />
611
+ <input>
612
+ <port id="0" precision="FP32">
613
+ <dim>1</dim>
614
+ <dim>32</dim>
615
+ <dim>24</dim>
616
+ <dim>24</dim>
617
+ </port>
618
+ </input>
619
+ <output>
620
+ <port id="1" precision="FP32" names="74">
621
+ <dim>1</dim>
622
+ <dim>32</dim>
623
+ <dim>12</dim>
624
+ <dim>12</dim>
625
+ </port>
626
+ </output>
627
+ </layer>
628
+ <layer id="33" name="Constant_1726_compressed" type="Const" version="opset1">
629
+ <data element_type="f16" shape="1, 32, 1, 1" offset="10256" size="64" />
630
+ <output>
631
+ <port id="0" precision="FP16">
632
+ <dim>1</dim>
633
+ <dim>32</dim>
634
+ <dim>1</dim>
635
+ <dim>1</dim>
636
+ </port>
637
+ </output>
638
+ </layer>
639
+ <layer id="34" name="Constant_1726" type="Convert" version="opset1">
640
+ <data destination_type="f32" />
641
+ <rt_info>
642
+ <attribute name="decompression" version="0" />
643
+ </rt_info>
644
+ <input>
645
+ <port id="0" precision="FP16">
646
+ <dim>1</dim>
647
+ <dim>32</dim>
648
+ <dim>1</dim>
649
+ <dim>1</dim>
650
+ </port>
651
+ </input>
652
+ <output>
653
+ <port id="1" precision="FP32">
654
+ <dim>1</dim>
655
+ <dim>32</dim>
656
+ <dim>1</dim>
657
+ <dim>1</dim>
658
+ </port>
659
+ </output>
660
+ </layer>
661
+ <layer id="35" name="Multiply_2077" type="Multiply" version="opset1">
662
+ <data auto_broadcast="numpy" />
663
+ <input>
664
+ <port id="0" precision="FP32">
665
+ <dim>1</dim>
666
+ <dim>32</dim>
667
+ <dim>12</dim>
668
+ <dim>12</dim>
669
+ </port>
670
+ <port id="1" precision="FP32">
671
+ <dim>1</dim>
672
+ <dim>32</dim>
673
+ <dim>1</dim>
674
+ <dim>1</dim>
675
+ </port>
676
+ </input>
677
+ <output>
678
+ <port id="2" precision="FP32">
679
+ <dim>1</dim>
680
+ <dim>32</dim>
681
+ <dim>12</dim>
682
+ <dim>12</dim>
683
+ </port>
684
+ </output>
685
+ </layer>
686
+ <layer id="36" name="Constant_2082_compressed" type="Const" version="opset1">
687
+ <data element_type="f16" shape="1, 32, 1, 1" offset="10320" size="64" />
688
+ <output>
689
+ <port id="0" precision="FP16">
690
+ <dim>1</dim>
691
+ <dim>32</dim>
692
+ <dim>1</dim>
693
+ <dim>1</dim>
694
+ </port>
695
+ </output>
696
+ </layer>
697
+ <layer id="37" name="Constant_2082" type="Convert" version="opset1">
698
+ <data destination_type="f32" />
699
+ <rt_info>
700
+ <attribute name="decompression" version="0" />
701
+ </rt_info>
702
+ <input>
703
+ <port id="0" precision="FP16">
704
+ <dim>1</dim>
705
+ <dim>32</dim>
706
+ <dim>1</dim>
707
+ <dim>1</dim>
708
+ </port>
709
+ </input>
710
+ <output>
711
+ <port id="1" precision="FP32">
712
+ <dim>1</dim>
713
+ <dim>32</dim>
714
+ <dim>1</dim>
715
+ <dim>1</dim>
716
+ </port>
717
+ </output>
718
+ </layer>
719
+ <layer id="38" name="75" type="Add" version="opset1">
720
+ <data auto_broadcast="numpy" />
721
+ <input>
722
+ <port id="0" precision="FP32">
723
+ <dim>1</dim>
724
+ <dim>32</dim>
725
+ <dim>12</dim>
726
+ <dim>12</dim>
727
+ </port>
728
+ <port id="1" precision="FP32">
729
+ <dim>1</dim>
730
+ <dim>32</dim>
731
+ <dim>1</dim>
732
+ <dim>1</dim>
733
+ </port>
734
+ </input>
735
+ <output>
736
+ <port id="2" precision="FP32" names="75">
737
+ <dim>1</dim>
738
+ <dim>32</dim>
739
+ <dim>12</dim>
740
+ <dim>12</dim>
741
+ </port>
742
+ </output>
743
+ </layer>
744
+ <layer id="39" name="22_compressed" type="Const" version="opset1">
745
+ <data element_type="f16" shape="64, 32, 3, 3" offset="10384" size="36864" />
746
+ <output>
747
+ <port id="0" precision="FP16">
748
+ <dim>64</dim>
749
+ <dim>32</dim>
750
+ <dim>3</dim>
751
+ <dim>3</dim>
752
+ </port>
753
+ </output>
754
+ </layer>
755
+ <layer id="40" name="22" type="Convert" version="opset1">
756
+ <data destination_type="f32" />
757
+ <rt_info>
758
+ <attribute name="decompression" version="0" />
759
+ </rt_info>
760
+ <input>
761
+ <port id="0" precision="FP16">
762
+ <dim>64</dim>
763
+ <dim>32</dim>
764
+ <dim>3</dim>
765
+ <dim>3</dim>
766
+ </port>
767
+ </input>
768
+ <output>
769
+ <port id="1" precision="FP32" names="22">
770
+ <dim>64</dim>
771
+ <dim>32</dim>
772
+ <dim>3</dim>
773
+ <dim>3</dim>
774
+ </port>
775
+ </output>
776
+ </layer>
777
+ <layer id="41" name="76/WithoutBiases" type="Convolution" version="opset1">
778
+ <data strides="1, 1" dilations="1, 1" pads_begin="1, 1" pads_end="1, 1" auto_pad="explicit" />
779
+ <input>
780
+ <port id="0" precision="FP32">
781
+ <dim>1</dim>
782
+ <dim>32</dim>
783
+ <dim>12</dim>
784
+ <dim>12</dim>
785
+ </port>
786
+ <port id="1" precision="FP32">
787
+ <dim>64</dim>
788
+ <dim>32</dim>
789
+ <dim>3</dim>
790
+ <dim>3</dim>
791
+ </port>
792
+ </input>
793
+ <output>
794
+ <port id="2" precision="FP32">
795
+ <dim>1</dim>
796
+ <dim>64</dim>
797
+ <dim>12</dim>
798
+ <dim>12</dim>
799
+ </port>
800
+ </output>
801
+ </layer>
802
+ <layer id="42" name="Reshape_130_compressed" type="Const" version="opset1">
803
+ <data element_type="f16" shape="1, 64, 1, 1" offset="47248" size="128" />
804
+ <output>
805
+ <port id="0" precision="FP16">
806
+ <dim>1</dim>
807
+ <dim>64</dim>
808
+ <dim>1</dim>
809
+ <dim>1</dim>
810
+ </port>
811
+ </output>
812
+ </layer>
813
+ <layer id="43" name="Reshape_130" type="Convert" version="opset1">
814
+ <data destination_type="f32" />
815
+ <rt_info>
816
+ <attribute name="decompression" version="0" />
817
+ </rt_info>
818
+ <input>
819
+ <port id="0" precision="FP16">
820
+ <dim>1</dim>
821
+ <dim>64</dim>
822
+ <dim>1</dim>
823
+ <dim>1</dim>
824
+ </port>
825
+ </input>
826
+ <output>
827
+ <port id="1" precision="FP32">
828
+ <dim>1</dim>
829
+ <dim>64</dim>
830
+ <dim>1</dim>
831
+ <dim>1</dim>
832
+ </port>
833
+ </output>
834
+ </layer>
835
+ <layer id="44" name="76" type="Add" version="opset1">
836
+ <data auto_broadcast="numpy" />
837
+ <input>
838
+ <port id="0" precision="FP32">
839
+ <dim>1</dim>
840
+ <dim>64</dim>
841
+ <dim>12</dim>
842
+ <dim>12</dim>
843
+ </port>
844
+ <port id="1" precision="FP32">
845
+ <dim>1</dim>
846
+ <dim>64</dim>
847
+ <dim>1</dim>
848
+ <dim>1</dim>
849
+ </port>
850
+ </input>
851
+ <output>
852
+ <port id="2" precision="FP32" names="76">
853
+ <dim>1</dim>
854
+ <dim>64</dim>
855
+ <dim>12</dim>
856
+ <dim>12</dim>
857
+ </port>
858
+ </output>
859
+ </layer>
860
+ <layer id="45" name="24_compressed" type="Const" version="opset1">
861
+ <data element_type="f16" shape="1" offset="47376" size="2" />
862
+ <output>
863
+ <port id="0" precision="FP16">
864
+ <dim>1</dim>
865
+ </port>
866
+ </output>
867
+ </layer>
868
+ <layer id="46" name="24" type="Convert" version="opset1">
869
+ <data destination_type="f32" />
870
+ <rt_info>
871
+ <attribute name="decompression" version="0" />
872
+ </rt_info>
873
+ <input>
874
+ <port id="0" precision="FP16">
875
+ <dim>1</dim>
876
+ </port>
877
+ </input>
878
+ <output>
879
+ <port id="1" precision="FP32" names="24">
880
+ <dim>1</dim>
881
+ </port>
882
+ </output>
883
+ </layer>
884
+ <layer id="47" name="77" type="PReLU" version="opset1">
885
+ <input>
886
+ <port id="0" precision="FP32">
887
+ <dim>1</dim>
888
+ <dim>64</dim>
889
+ <dim>12</dim>
890
+ <dim>12</dim>
891
+ </port>
892
+ <port id="1" precision="FP32">
893
+ <dim>1</dim>
894
+ </port>
895
+ </input>
896
+ <output>
897
+ <port id="2" precision="FP32" names="77">
898
+ <dim>1</dim>
899
+ <dim>64</dim>
900
+ <dim>12</dim>
901
+ <dim>12</dim>
902
+ </port>
903
+ </output>
904
+ </layer>
905
+ <layer id="48" name="78" type="MaxPool" version="opset1">
906
+ <data strides="2, 2" pads_begin="0, 0" pads_end="0, 0" kernel="2, 2" rounding_type="floor" auto_pad="explicit" />
907
+ <input>
908
+ <port id="0" precision="FP32">
909
+ <dim>1</dim>
910
+ <dim>64</dim>
911
+ <dim>12</dim>
912
+ <dim>12</dim>
913
+ </port>
914
+ </input>
915
+ <output>
916
+ <port id="1" precision="FP32" names="78">
917
+ <dim>1</dim>
918
+ <dim>64</dim>
919
+ <dim>6</dim>
920
+ <dim>6</dim>
921
+ </port>
922
+ </output>
923
+ </layer>
924
+ <layer id="49" name="Constant_1792_compressed" type="Const" version="opset1">
925
+ <data element_type="f16" shape="1, 64, 1, 1" offset="47378" size="128" />
926
+ <output>
927
+ <port id="0" precision="FP16">
928
+ <dim>1</dim>
929
+ <dim>64</dim>
930
+ <dim>1</dim>
931
+ <dim>1</dim>
932
+ </port>
933
+ </output>
934
+ </layer>
935
+ <layer id="50" name="Constant_1792" type="Convert" version="opset1">
936
+ <data destination_type="f32" />
937
+ <rt_info>
938
+ <attribute name="decompression" version="0" />
939
+ </rt_info>
940
+ <input>
941
+ <port id="0" precision="FP16">
942
+ <dim>1</dim>
943
+ <dim>64</dim>
944
+ <dim>1</dim>
945
+ <dim>1</dim>
946
+ </port>
947
+ </input>
948
+ <output>
949
+ <port id="1" precision="FP32">
950
+ <dim>1</dim>
951
+ <dim>64</dim>
952
+ <dim>1</dim>
953
+ <dim>1</dim>
954
+ </port>
955
+ </output>
956
+ </layer>
957
+ <layer id="51" name="Multiply_2084" type="Multiply" version="opset1">
958
+ <data auto_broadcast="numpy" />
959
+ <input>
960
+ <port id="0" precision="FP32">
961
+ <dim>1</dim>
962
+ <dim>64</dim>
963
+ <dim>6</dim>
964
+ <dim>6</dim>
965
+ </port>
966
+ <port id="1" precision="FP32">
967
+ <dim>1</dim>
968
+ <dim>64</dim>
969
+ <dim>1</dim>
970
+ <dim>1</dim>
971
+ </port>
972
+ </input>
973
+ <output>
974
+ <port id="2" precision="FP32">
975
+ <dim>1</dim>
976
+ <dim>64</dim>
977
+ <dim>6</dim>
978
+ <dim>6</dim>
979
+ </port>
980
+ </output>
981
+ </layer>
982
+ <layer id="52" name="Constant_2089_compressed" type="Const" version="opset1">
983
+ <data element_type="f16" shape="1, 64, 1, 1" offset="47506" size="128" />
984
+ <output>
985
+ <port id="0" precision="FP16">
986
+ <dim>1</dim>
987
+ <dim>64</dim>
988
+ <dim>1</dim>
989
+ <dim>1</dim>
990
+ </port>
991
+ </output>
992
+ </layer>
993
+ <layer id="53" name="Constant_2089" type="Convert" version="opset1">
994
+ <data destination_type="f32" />
995
+ <rt_info>
996
+ <attribute name="decompression" version="0" />
997
+ </rt_info>
998
+ <input>
999
+ <port id="0" precision="FP16">
1000
+ <dim>1</dim>
1001
+ <dim>64</dim>
1002
+ <dim>1</dim>
1003
+ <dim>1</dim>
1004
+ </port>
1005
+ </input>
1006
+ <output>
1007
+ <port id="1" precision="FP32">
1008
+ <dim>1</dim>
1009
+ <dim>64</dim>
1010
+ <dim>1</dim>
1011
+ <dim>1</dim>
1012
+ </port>
1013
+ </output>
1014
+ </layer>
1015
+ <layer id="54" name="79" type="Add" version="opset1">
1016
+ <data auto_broadcast="numpy" />
1017
+ <input>
1018
+ <port id="0" precision="FP32">
1019
+ <dim>1</dim>
1020
+ <dim>64</dim>
1021
+ <dim>6</dim>
1022
+ <dim>6</dim>
1023
+ </port>
1024
+ <port id="1" precision="FP32">
1025
+ <dim>1</dim>
1026
+ <dim>64</dim>
1027
+ <dim>1</dim>
1028
+ <dim>1</dim>
1029
+ </port>
1030
+ </input>
1031
+ <output>
1032
+ <port id="2" precision="FP32" names="79">
1033
+ <dim>1</dim>
1034
+ <dim>64</dim>
1035
+ <dim>6</dim>
1036
+ <dim>6</dim>
1037
+ </port>
1038
+ </output>
1039
+ </layer>
1040
+ <layer id="55" name="30_compressed" type="Const" version="opset1">
1041
+ <data element_type="f16" shape="64, 64, 3, 3" offset="47634" size="73728" />
1042
+ <output>
1043
+ <port id="0" precision="FP16">
1044
+ <dim>64</dim>
1045
+ <dim>64</dim>
1046
+ <dim>3</dim>
1047
+ <dim>3</dim>
1048
+ </port>
1049
+ </output>
1050
+ </layer>
1051
+ <layer id="56" name="30" type="Convert" version="opset1">
1052
+ <data destination_type="f32" />
1053
+ <rt_info>
1054
+ <attribute name="decompression" version="0" />
1055
+ </rt_info>
1056
+ <input>
1057
+ <port id="0" precision="FP16">
1058
+ <dim>64</dim>
1059
+ <dim>64</dim>
1060
+ <dim>3</dim>
1061
+ <dim>3</dim>
1062
+ </port>
1063
+ </input>
1064
+ <output>
1065
+ <port id="1" precision="FP32" names="30">
1066
+ <dim>64</dim>
1067
+ <dim>64</dim>
1068
+ <dim>3</dim>
1069
+ <dim>3</dim>
1070
+ </port>
1071
+ </output>
1072
+ </layer>
1073
+ <layer id="57" name="80/WithoutBiases" type="Convolution" version="opset1">
1074
+ <data strides="1, 1" dilations="1, 1" pads_begin="1, 1" pads_end="1, 1" auto_pad="explicit" />
1075
+ <input>
1076
+ <port id="0" precision="FP32">
1077
+ <dim>1</dim>
1078
+ <dim>64</dim>
1079
+ <dim>6</dim>
1080
+ <dim>6</dim>
1081
+ </port>
1082
+ <port id="1" precision="FP32">
1083
+ <dim>64</dim>
1084
+ <dim>64</dim>
1085
+ <dim>3</dim>
1086
+ <dim>3</dim>
1087
+ </port>
1088
+ </input>
1089
+ <output>
1090
+ <port id="2" precision="FP32">
1091
+ <dim>1</dim>
1092
+ <dim>64</dim>
1093
+ <dim>6</dim>
1094
+ <dim>6</dim>
1095
+ </port>
1096
+ </output>
1097
+ </layer>
1098
+ <layer id="58" name="Reshape_153_compressed" type="Const" version="opset1">
1099
+ <data element_type="f16" shape="1, 64, 1, 1" offset="121362" size="128" />
1100
+ <output>
1101
+ <port id="0" precision="FP16">
1102
+ <dim>1</dim>
1103
+ <dim>64</dim>
1104
+ <dim>1</dim>
1105
+ <dim>1</dim>
1106
+ </port>
1107
+ </output>
1108
+ </layer>
1109
+ <layer id="59" name="Reshape_153" type="Convert" version="opset1">
1110
+ <data destination_type="f32" />
1111
+ <rt_info>
1112
+ <attribute name="decompression" version="0" />
1113
+ </rt_info>
1114
+ <input>
1115
+ <port id="0" precision="FP16">
1116
+ <dim>1</dim>
1117
+ <dim>64</dim>
1118
+ <dim>1</dim>
1119
+ <dim>1</dim>
1120
+ </port>
1121
+ </input>
1122
+ <output>
1123
+ <port id="1" precision="FP32">
1124
+ <dim>1</dim>
1125
+ <dim>64</dim>
1126
+ <dim>1</dim>
1127
+ <dim>1</dim>
1128
+ </port>
1129
+ </output>
1130
+ </layer>
1131
+ <layer id="60" name="80" type="Add" version="opset1">
1132
+ <data auto_broadcast="numpy" />
1133
+ <input>
1134
+ <port id="0" precision="FP32">
1135
+ <dim>1</dim>
1136
+ <dim>64</dim>
1137
+ <dim>6</dim>
1138
+ <dim>6</dim>
1139
+ </port>
1140
+ <port id="1" precision="FP32">
1141
+ <dim>1</dim>
1142
+ <dim>64</dim>
1143
+ <dim>1</dim>
1144
+ <dim>1</dim>
1145
+ </port>
1146
+ </input>
1147
+ <output>
1148
+ <port id="2" precision="FP32" names="80">
1149
+ <dim>1</dim>
1150
+ <dim>64</dim>
1151
+ <dim>6</dim>
1152
+ <dim>6</dim>
1153
+ </port>
1154
+ </output>
1155
+ </layer>
1156
+ <layer id="61" name="32_compressed" type="Const" version="opset1">
1157
+ <data element_type="f16" shape="1" offset="121490" size="2" />
1158
+ <output>
1159
+ <port id="0" precision="FP16">
1160
+ <dim>1</dim>
1161
+ </port>
1162
+ </output>
1163
+ </layer>
1164
+ <layer id="62" name="32" type="Convert" version="opset1">
1165
+ <data destination_type="f32" />
1166
+ <rt_info>
1167
+ <attribute name="decompression" version="0" />
1168
+ </rt_info>
1169
+ <input>
1170
+ <port id="0" precision="FP16">
1171
+ <dim>1</dim>
1172
+ </port>
1173
+ </input>
1174
+ <output>
1175
+ <port id="1" precision="FP32" names="32">
1176
+ <dim>1</dim>
1177
+ </port>
1178
+ </output>
1179
+ </layer>
1180
+ <layer id="63" name="81" type="PReLU" version="opset1">
1181
+ <input>
1182
+ <port id="0" precision="FP32">
1183
+ <dim>1</dim>
1184
+ <dim>64</dim>
1185
+ <dim>6</dim>
1186
+ <dim>6</dim>
1187
+ </port>
1188
+ <port id="1" precision="FP32">
1189
+ <dim>1</dim>
1190
+ </port>
1191
+ </input>
1192
+ <output>
1193
+ <port id="2" precision="FP32" names="81">
1194
+ <dim>1</dim>
1195
+ <dim>64</dim>
1196
+ <dim>6</dim>
1197
+ <dim>6</dim>
1198
+ </port>
1199
+ </output>
1200
+ </layer>
1201
+ <layer id="64" name="Constant_1858_compressed" type="Const" version="opset1">
1202
+ <data element_type="f16" shape="1, 64, 1, 1" offset="121492" size="128" />
1203
+ <output>
1204
+ <port id="0" precision="FP16">
1205
+ <dim>1</dim>
1206
+ <dim>64</dim>
1207
+ <dim>1</dim>
1208
+ <dim>1</dim>
1209
+ </port>
1210
+ </output>
1211
+ </layer>
1212
+ <layer id="65" name="Constant_1858" type="Convert" version="opset1">
1213
+ <data destination_type="f32" />
1214
+ <rt_info>
1215
+ <attribute name="decompression" version="0" />
1216
+ </rt_info>
1217
+ <input>
1218
+ <port id="0" precision="FP16">
1219
+ <dim>1</dim>
1220
+ <dim>64</dim>
1221
+ <dim>1</dim>
1222
+ <dim>1</dim>
1223
+ </port>
1224
+ </input>
1225
+ <output>
1226
+ <port id="1" precision="FP32">
1227
+ <dim>1</dim>
1228
+ <dim>64</dim>
1229
+ <dim>1</dim>
1230
+ <dim>1</dim>
1231
+ </port>
1232
+ </output>
1233
+ </layer>
1234
+ <layer id="66" name="Multiply_2091" type="Multiply" version="opset1">
1235
+ <data auto_broadcast="numpy" />
1236
+ <input>
1237
+ <port id="0" precision="FP32">
1238
+ <dim>1</dim>
1239
+ <dim>64</dim>
1240
+ <dim>6</dim>
1241
+ <dim>6</dim>
1242
+ </port>
1243
+ <port id="1" precision="FP32">
1244
+ <dim>1</dim>
1245
+ <dim>64</dim>
1246
+ <dim>1</dim>
1247
+ <dim>1</dim>
1248
+ </port>
1249
+ </input>
1250
+ <output>
1251
+ <port id="2" precision="FP32">
1252
+ <dim>1</dim>
1253
+ <dim>64</dim>
1254
+ <dim>6</dim>
1255
+ <dim>6</dim>
1256
+ </port>
1257
+ </output>
1258
+ </layer>
1259
+ <layer id="67" name="Constant_2096_compressed" type="Const" version="opset1">
1260
+ <data element_type="f16" shape="1, 64, 1, 1" offset="121620" size="128" />
1261
+ <output>
1262
+ <port id="0" precision="FP16">
1263
+ <dim>1</dim>
1264
+ <dim>64</dim>
1265
+ <dim>1</dim>
1266
+ <dim>1</dim>
1267
+ </port>
1268
+ </output>
1269
+ </layer>
1270
+ <layer id="68" name="Constant_2096" type="Convert" version="opset1">
1271
+ <data destination_type="f32" />
1272
+ <rt_info>
1273
+ <attribute name="decompression" version="0" />
1274
+ </rt_info>
1275
+ <input>
1276
+ <port id="0" precision="FP16">
1277
+ <dim>1</dim>
1278
+ <dim>64</dim>
1279
+ <dim>1</dim>
1280
+ <dim>1</dim>
1281
+ </port>
1282
+ </input>
1283
+ <output>
1284
+ <port id="1" precision="FP32">
1285
+ <dim>1</dim>
1286
+ <dim>64</dim>
1287
+ <dim>1</dim>
1288
+ <dim>1</dim>
1289
+ </port>
1290
+ </output>
1291
+ </layer>
1292
+ <layer id="69" name="82" type="Add" version="opset1">
1293
+ <data auto_broadcast="numpy" />
1294
+ <input>
1295
+ <port id="0" precision="FP32">
1296
+ <dim>1</dim>
1297
+ <dim>64</dim>
1298
+ <dim>6</dim>
1299
+ <dim>6</dim>
1300
+ </port>
1301
+ <port id="1" precision="FP32">
1302
+ <dim>1</dim>
1303
+ <dim>64</dim>
1304
+ <dim>1</dim>
1305
+ <dim>1</dim>
1306
+ </port>
1307
+ </input>
1308
+ <output>
1309
+ <port id="2" precision="FP32" names="82">
1310
+ <dim>1</dim>
1311
+ <dim>64</dim>
1312
+ <dim>6</dim>
1313
+ <dim>6</dim>
1314
+ </port>
1315
+ </output>
1316
+ </layer>
1317
+ <layer id="70" name="38_compressed" type="Const" version="opset1">
1318
+ <data element_type="f16" shape="128, 64, 3, 3" offset="121748" size="147456" />
1319
+ <output>
1320
+ <port id="0" precision="FP16">
1321
+ <dim>128</dim>
1322
+ <dim>64</dim>
1323
+ <dim>3</dim>
1324
+ <dim>3</dim>
1325
+ </port>
1326
+ </output>
1327
+ </layer>
1328
+ <layer id="71" name="38" type="Convert" version="opset1">
1329
+ <data destination_type="f32" />
1330
+ <rt_info>
1331
+ <attribute name="decompression" version="0" />
1332
+ </rt_info>
1333
+ <input>
1334
+ <port id="0" precision="FP16">
1335
+ <dim>128</dim>
1336
+ <dim>64</dim>
1337
+ <dim>3</dim>
1338
+ <dim>3</dim>
1339
+ </port>
1340
+ </input>
1341
+ <output>
1342
+ <port id="1" precision="FP32" names="38">
1343
+ <dim>128</dim>
1344
+ <dim>64</dim>
1345
+ <dim>3</dim>
1346
+ <dim>3</dim>
1347
+ </port>
1348
+ </output>
1349
+ </layer>
1350
+ <layer id="72" name="83/WithoutBiases" type="Convolution" version="opset1">
1351
+ <data strides="1, 1" dilations="1, 1" pads_begin="1, 1" pads_end="1, 1" auto_pad="explicit" />
1352
+ <input>
1353
+ <port id="0" precision="FP32">
1354
+ <dim>1</dim>
1355
+ <dim>64</dim>
1356
+ <dim>6</dim>
1357
+ <dim>6</dim>
1358
+ </port>
1359
+ <port id="1" precision="FP32">
1360
+ <dim>128</dim>
1361
+ <dim>64</dim>
1362
+ <dim>3</dim>
1363
+ <dim>3</dim>
1364
+ </port>
1365
+ </input>
1366
+ <output>
1367
+ <port id="2" precision="FP32">
1368
+ <dim>1</dim>
1369
+ <dim>128</dim>
1370
+ <dim>6</dim>
1371
+ <dim>6</dim>
1372
+ </port>
1373
+ </output>
1374
+ </layer>
1375
+ <layer id="73" name="Reshape_174_compressed" type="Const" version="opset1">
1376
+ <data element_type="f16" shape="1, 128, 1, 1" offset="269204" size="256" />
1377
+ <output>
1378
+ <port id="0" precision="FP16">
1379
+ <dim>1</dim>
1380
+ <dim>128</dim>
1381
+ <dim>1</dim>
1382
+ <dim>1</dim>
1383
+ </port>
1384
+ </output>
1385
+ </layer>
1386
+ <layer id="74" name="Reshape_174" type="Convert" version="opset1">
1387
+ <data destination_type="f32" />
1388
+ <rt_info>
1389
+ <attribute name="decompression" version="0" />
1390
+ </rt_info>
1391
+ <input>
1392
+ <port id="0" precision="FP16">
1393
+ <dim>1</dim>
1394
+ <dim>128</dim>
1395
+ <dim>1</dim>
1396
+ <dim>1</dim>
1397
+ </port>
1398
+ </input>
1399
+ <output>
1400
+ <port id="1" precision="FP32">
1401
+ <dim>1</dim>
1402
+ <dim>128</dim>
1403
+ <dim>1</dim>
1404
+ <dim>1</dim>
1405
+ </port>
1406
+ </output>
1407
+ </layer>
1408
+ <layer id="75" name="83" type="Add" version="opset1">
1409
+ <data auto_broadcast="numpy" />
1410
+ <input>
1411
+ <port id="0" precision="FP32">
1412
+ <dim>1</dim>
1413
+ <dim>128</dim>
1414
+ <dim>6</dim>
1415
+ <dim>6</dim>
1416
+ </port>
1417
+ <port id="1" precision="FP32">
1418
+ <dim>1</dim>
1419
+ <dim>128</dim>
1420
+ <dim>1</dim>
1421
+ <dim>1</dim>
1422
+ </port>
1423
+ </input>
1424
+ <output>
1425
+ <port id="2" precision="FP32" names="83">
1426
+ <dim>1</dim>
1427
+ <dim>128</dim>
1428
+ <dim>6</dim>
1429
+ <dim>6</dim>
1430
+ </port>
1431
+ </output>
1432
+ </layer>
1433
+ <layer id="76" name="40_compressed" type="Const" version="opset1">
1434
+ <data element_type="f16" shape="1" offset="269460" size="2" />
1435
+ <output>
1436
+ <port id="0" precision="FP16">
1437
+ <dim>1</dim>
1438
+ </port>
1439
+ </output>
1440
+ </layer>
1441
+ <layer id="77" name="40" type="Convert" version="opset1">
1442
+ <data destination_type="f32" />
1443
+ <rt_info>
1444
+ <attribute name="decompression" version="0" />
1445
+ </rt_info>
1446
+ <input>
1447
+ <port id="0" precision="FP16">
1448
+ <dim>1</dim>
1449
+ </port>
1450
+ </input>
1451
+ <output>
1452
+ <port id="1" precision="FP32" names="40">
1453
+ <dim>1</dim>
1454
+ </port>
1455
+ </output>
1456
+ </layer>
1457
+ <layer id="78" name="84" type="PReLU" version="opset1">
1458
+ <input>
1459
+ <port id="0" precision="FP32">
1460
+ <dim>1</dim>
1461
+ <dim>128</dim>
1462
+ <dim>6</dim>
1463
+ <dim>6</dim>
1464
+ </port>
1465
+ <port id="1" precision="FP32">
1466
+ <dim>1</dim>
1467
+ </port>
1468
+ </input>
1469
+ <output>
1470
+ <port id="2" precision="FP32" names="84">
1471
+ <dim>1</dim>
1472
+ <dim>128</dim>
1473
+ <dim>6</dim>
1474
+ <dim>6</dim>
1475
+ </port>
1476
+ </output>
1477
+ </layer>
1478
+ <layer id="79" name="Constant_1924_compressed" type="Const" version="opset1">
1479
+ <data element_type="f16" shape="1, 128, 1, 1" offset="269462" size="256" />
1480
+ <output>
1481
+ <port id="0" precision="FP16">
1482
+ <dim>1</dim>
1483
+ <dim>128</dim>
1484
+ <dim>1</dim>
1485
+ <dim>1</dim>
1486
+ </port>
1487
+ </output>
1488
+ </layer>
1489
+ <layer id="80" name="Constant_1924" type="Convert" version="opset1">
1490
+ <data destination_type="f32" />
1491
+ <rt_info>
1492
+ <attribute name="decompression" version="0" />
1493
+ </rt_info>
1494
+ <input>
1495
+ <port id="0" precision="FP16">
1496
+ <dim>1</dim>
1497
+ <dim>128</dim>
1498
+ <dim>1</dim>
1499
+ <dim>1</dim>
1500
+ </port>
1501
+ </input>
1502
+ <output>
1503
+ <port id="1" precision="FP32">
1504
+ <dim>1</dim>
1505
+ <dim>128</dim>
1506
+ <dim>1</dim>
1507
+ <dim>1</dim>
1508
+ </port>
1509
+ </output>
1510
+ </layer>
1511
+ <layer id="81" name="Multiply_2098" type="Multiply" version="opset1">
1512
+ <data auto_broadcast="numpy" />
1513
+ <input>
1514
+ <port id="0" precision="FP32">
1515
+ <dim>1</dim>
1516
+ <dim>128</dim>
1517
+ <dim>6</dim>
1518
+ <dim>6</dim>
1519
+ </port>
1520
+ <port id="1" precision="FP32">
1521
+ <dim>1</dim>
1522
+ <dim>128</dim>
1523
+ <dim>1</dim>
1524
+ <dim>1</dim>
1525
+ </port>
1526
+ </input>
1527
+ <output>
1528
+ <port id="2" precision="FP32">
1529
+ <dim>1</dim>
1530
+ <dim>128</dim>
1531
+ <dim>6</dim>
1532
+ <dim>6</dim>
1533
+ </port>
1534
+ </output>
1535
+ </layer>
1536
+ <layer id="82" name="Constant_2103_compressed" type="Const" version="opset1">
1537
+ <data element_type="f16" shape="1, 128, 1, 1" offset="269718" size="256" />
1538
+ <output>
1539
+ <port id="0" precision="FP16">
1540
+ <dim>1</dim>
1541
+ <dim>128</dim>
1542
+ <dim>1</dim>
1543
+ <dim>1</dim>
1544
+ </port>
1545
+ </output>
1546
+ </layer>
1547
+ <layer id="83" name="Constant_2103" type="Convert" version="opset1">
1548
+ <data destination_type="f32" />
1549
+ <rt_info>
1550
+ <attribute name="decompression" version="0" />
1551
+ </rt_info>
1552
+ <input>
1553
+ <port id="0" precision="FP16">
1554
+ <dim>1</dim>
1555
+ <dim>128</dim>
1556
+ <dim>1</dim>
1557
+ <dim>1</dim>
1558
+ </port>
1559
+ </input>
1560
+ <output>
1561
+ <port id="1" precision="FP32">
1562
+ <dim>1</dim>
1563
+ <dim>128</dim>
1564
+ <dim>1</dim>
1565
+ <dim>1</dim>
1566
+ </port>
1567
+ </output>
1568
+ </layer>
1569
+ <layer id="84" name="85" type="Add" version="opset1">
1570
+ <data auto_broadcast="numpy" />
1571
+ <input>
1572
+ <port id="0" precision="FP32">
1573
+ <dim>1</dim>
1574
+ <dim>128</dim>
1575
+ <dim>6</dim>
1576
+ <dim>6</dim>
1577
+ </port>
1578
+ <port id="1" precision="FP32">
1579
+ <dim>1</dim>
1580
+ <dim>128</dim>
1581
+ <dim>1</dim>
1582
+ <dim>1</dim>
1583
+ </port>
1584
+ </input>
1585
+ <output>
1586
+ <port id="2" precision="FP32" names="85">
1587
+ <dim>1</dim>
1588
+ <dim>128</dim>
1589
+ <dim>6</dim>
1590
+ <dim>6</dim>
1591
+ </port>
1592
+ </output>
1593
+ </layer>
1594
+ <layer id="85" name="Reshape_191_compressed" type="Const" version="opset1">
1595
+ <data element_type="f16" shape="128, 1, 1, 6, 6" offset="269974" size="9216" />
1596
+ <output>
1597
+ <port id="0" precision="FP16">
1598
+ <dim>128</dim>
1599
+ <dim>1</dim>
1600
+ <dim>1</dim>
1601
+ <dim>6</dim>
1602
+ <dim>6</dim>
1603
+ </port>
1604
+ </output>
1605
+ </layer>
1606
+ <layer id="86" name="Reshape_191" type="Convert" version="opset1">
1607
+ <data destination_type="f32" />
1608
+ <rt_info>
1609
+ <attribute name="decompression" version="0" />
1610
+ </rt_info>
1611
+ <input>
1612
+ <port id="0" precision="FP16">
1613
+ <dim>128</dim>
1614
+ <dim>1</dim>
1615
+ <dim>1</dim>
1616
+ <dim>6</dim>
1617
+ <dim>6</dim>
1618
+ </port>
1619
+ </input>
1620
+ <output>
1621
+ <port id="1" precision="FP32">
1622
+ <dim>128</dim>
1623
+ <dim>1</dim>
1624
+ <dim>1</dim>
1625
+ <dim>6</dim>
1626
+ <dim>6</dim>
1627
+ </port>
1628
+ </output>
1629
+ </layer>
1630
+ <layer id="87" name="86/WithoutBiases" type="GroupConvolution" version="opset1">
1631
+ <data strides="1, 1" pads_begin="0, 0" pads_end="0, 0" dilations="1, 1" auto_pad="explicit" />
1632
+ <input>
1633
+ <port id="0" precision="FP32">
1634
+ <dim>1</dim>
1635
+ <dim>128</dim>
1636
+ <dim>6</dim>
1637
+ <dim>6</dim>
1638
+ </port>
1639
+ <port id="1" precision="FP32">
1640
+ <dim>128</dim>
1641
+ <dim>1</dim>
1642
+ <dim>1</dim>
1643
+ <dim>6</dim>
1644
+ <dim>6</dim>
1645
+ </port>
1646
+ </input>
1647
+ <output>
1648
+ <port id="2" precision="FP32">
1649
+ <dim>1</dim>
1650
+ <dim>128</dim>
1651
+ <dim>1</dim>
1652
+ <dim>1</dim>
1653
+ </port>
1654
+ </output>
1655
+ </layer>
1656
+ <layer id="88" name="Reshape_243_compressed" type="Const" version="opset1">
1657
+ <data element_type="f16" shape="1, 128, 1, 1" offset="279190" size="256" />
1658
+ <output>
1659
+ <port id="0" precision="FP16">
1660
+ <dim>1</dim>
1661
+ <dim>128</dim>
1662
+ <dim>1</dim>
1663
+ <dim>1</dim>
1664
+ </port>
1665
+ </output>
1666
+ </layer>
1667
+ <layer id="89" name="Reshape_243" type="Convert" version="opset1">
1668
+ <data destination_type="f32" />
1669
+ <rt_info>
1670
+ <attribute name="decompression" version="0" />
1671
+ </rt_info>
1672
+ <input>
1673
+ <port id="0" precision="FP16">
1674
+ <dim>1</dim>
1675
+ <dim>128</dim>
1676
+ <dim>1</dim>
1677
+ <dim>1</dim>
1678
+ </port>
1679
+ </input>
1680
+ <output>
1681
+ <port id="1" precision="FP32">
1682
+ <dim>1</dim>
1683
+ <dim>128</dim>
1684
+ <dim>1</dim>
1685
+ <dim>1</dim>
1686
+ </port>
1687
+ </output>
1688
+ </layer>
1689
+ <layer id="90" name="86" type="Add" version="opset1">
1690
+ <data auto_broadcast="numpy" />
1691
+ <input>
1692
+ <port id="0" precision="FP32">
1693
+ <dim>1</dim>
1694
+ <dim>128</dim>
1695
+ <dim>1</dim>
1696
+ <dim>1</dim>
1697
+ </port>
1698
+ <port id="1" precision="FP32">
1699
+ <dim>1</dim>
1700
+ <dim>128</dim>
1701
+ <dim>1</dim>
1702
+ <dim>1</dim>
1703
+ </port>
1704
+ </input>
1705
+ <output>
1706
+ <port id="2" precision="FP32" names="86">
1707
+ <dim>1</dim>
1708
+ <dim>128</dim>
1709
+ <dim>1</dim>
1710
+ <dim>1</dim>
1711
+ </port>
1712
+ </output>
1713
+ </layer>
1714
+ <layer id="91" name="48_compressed" type="Const" version="opset1">
1715
+ <data element_type="f16" shape="1" offset="279446" size="2" />
1716
+ <output>
1717
+ <port id="0" precision="FP16">
1718
+ <dim>1</dim>
1719
+ </port>
1720
+ </output>
1721
+ </layer>
1722
+ <layer id="92" name="48" type="Convert" version="opset1">
1723
+ <data destination_type="f32" />
1724
+ <rt_info>
1725
+ <attribute name="decompression" version="0" />
1726
+ </rt_info>
1727
+ <input>
1728
+ <port id="0" precision="FP16">
1729
+ <dim>1</dim>
1730
+ </port>
1731
+ </input>
1732
+ <output>
1733
+ <port id="1" precision="FP32" names="48">
1734
+ <dim>1</dim>
1735
+ </port>
1736
+ </output>
1737
+ </layer>
1738
+ <layer id="93" name="87" type="PReLU" version="opset1">
1739
+ <input>
1740
+ <port id="0" precision="FP32">
1741
+ <dim>1</dim>
1742
+ <dim>128</dim>
1743
+ <dim>1</dim>
1744
+ <dim>1</dim>
1745
+ </port>
1746
+ <port id="1" precision="FP32">
1747
+ <dim>1</dim>
1748
+ </port>
1749
+ </input>
1750
+ <output>
1751
+ <port id="2" precision="FP32" names="87">
1752
+ <dim>1</dim>
1753
+ <dim>128</dim>
1754
+ <dim>1</dim>
1755
+ <dim>1</dim>
1756
+ </port>
1757
+ </output>
1758
+ </layer>
1759
+ <layer id="94" name="Constant_1990_compressed" type="Const" version="opset1">
1760
+ <data element_type="f16" shape="1, 128, 1, 1" offset="279448" size="256" />
1761
+ <output>
1762
+ <port id="0" precision="FP16">
1763
+ <dim>1</dim>
1764
+ <dim>128</dim>
1765
+ <dim>1</dim>
1766
+ <dim>1</dim>
1767
+ </port>
1768
+ </output>
1769
+ </layer>
1770
+ <layer id="95" name="Constant_1990" type="Convert" version="opset1">
1771
+ <data destination_type="f32" />
1772
+ <rt_info>
1773
+ <attribute name="decompression" version="0" />
1774
+ </rt_info>
1775
+ <input>
1776
+ <port id="0" precision="FP16">
1777
+ <dim>1</dim>
1778
+ <dim>128</dim>
1779
+ <dim>1</dim>
1780
+ <dim>1</dim>
1781
+ </port>
1782
+ </input>
1783
+ <output>
1784
+ <port id="1" precision="FP32">
1785
+ <dim>1</dim>
1786
+ <dim>128</dim>
1787
+ <dim>1</dim>
1788
+ <dim>1</dim>
1789
+ </port>
1790
+ </output>
1791
+ </layer>
1792
+ <layer id="96" name="Multiply_2105" type="Multiply" version="opset1">
1793
+ <data auto_broadcast="numpy" />
1794
+ <input>
1795
+ <port id="0" precision="FP32">
1796
+ <dim>1</dim>
1797
+ <dim>128</dim>
1798
+ <dim>1</dim>
1799
+ <dim>1</dim>
1800
+ </port>
1801
+ <port id="1" precision="FP32">
1802
+ <dim>1</dim>
1803
+ <dim>128</dim>
1804
+ <dim>1</dim>
1805
+ <dim>1</dim>
1806
+ </port>
1807
+ </input>
1808
+ <output>
1809
+ <port id="2" precision="FP32">
1810
+ <dim>1</dim>
1811
+ <dim>128</dim>
1812
+ <dim>1</dim>
1813
+ <dim>1</dim>
1814
+ </port>
1815
+ </output>
1816
+ </layer>
1817
+ <layer id="97" name="Constant_2110_compressed" type="Const" version="opset1">
1818
+ <data element_type="f16" shape="1, 128, 1, 1" offset="279704" size="256" />
1819
+ <output>
1820
+ <port id="0" precision="FP16">
1821
+ <dim>1</dim>
1822
+ <dim>128</dim>
1823
+ <dim>1</dim>
1824
+ <dim>1</dim>
1825
+ </port>
1826
+ </output>
1827
+ </layer>
1828
+ <layer id="98" name="Constant_2110" type="Convert" version="opset1">
1829
+ <data destination_type="f32" />
1830
+ <rt_info>
1831
+ <attribute name="decompression" version="0" />
1832
+ </rt_info>
1833
+ <input>
1834
+ <port id="0" precision="FP16">
1835
+ <dim>1</dim>
1836
+ <dim>128</dim>
1837
+ <dim>1</dim>
1838
+ <dim>1</dim>
1839
+ </port>
1840
+ </input>
1841
+ <output>
1842
+ <port id="1" precision="FP32">
1843
+ <dim>1</dim>
1844
+ <dim>128</dim>
1845
+ <dim>1</dim>
1846
+ <dim>1</dim>
1847
+ </port>
1848
+ </output>
1849
+ </layer>
1850
+ <layer id="99" name="88" type="Add" version="opset1">
1851
+ <data auto_broadcast="numpy" />
1852
+ <input>
1853
+ <port id="0" precision="FP32">
1854
+ <dim>1</dim>
1855
+ <dim>128</dim>
1856
+ <dim>1</dim>
1857
+ <dim>1</dim>
1858
+ </port>
1859
+ <port id="1" precision="FP32">
1860
+ <dim>1</dim>
1861
+ <dim>128</dim>
1862
+ <dim>1</dim>
1863
+ <dim>1</dim>
1864
+ </port>
1865
+ </input>
1866
+ <output>
1867
+ <port id="2" precision="FP32" names="88">
1868
+ <dim>1</dim>
1869
+ <dim>128</dim>
1870
+ <dim>1</dim>
1871
+ <dim>1</dim>
1872
+ </port>
1873
+ </output>
1874
+ </layer>
1875
+ <layer id="100" name="54_compressed" type="Const" version="opset1">
1876
+ <data element_type="f16" shape="256, 128, 1, 1" offset="279960" size="65536" />
1877
+ <output>
1878
+ <port id="0" precision="FP16">
1879
+ <dim>256</dim>
1880
+ <dim>128</dim>
1881
+ <dim>1</dim>
1882
+ <dim>1</dim>
1883
+ </port>
1884
+ </output>
1885
+ </layer>
1886
+ <layer id="101" name="54" type="Convert" version="opset1">
1887
+ <data destination_type="f32" />
1888
+ <rt_info>
1889
+ <attribute name="decompression" version="0" />
1890
+ </rt_info>
1891
+ <input>
1892
+ <port id="0" precision="FP16">
1893
+ <dim>256</dim>
1894
+ <dim>128</dim>
1895
+ <dim>1</dim>
1896
+ <dim>1</dim>
1897
+ </port>
1898
+ </input>
1899
+ <output>
1900
+ <port id="1" precision="FP32" names="54">
1901
+ <dim>256</dim>
1902
+ <dim>128</dim>
1903
+ <dim>1</dim>
1904
+ <dim>1</dim>
1905
+ </port>
1906
+ </output>
1907
+ </layer>
1908
+ <layer id="102" name="89/WithoutBiases" type="Convolution" version="opset1">
1909
+ <data strides="1, 1" dilations="1, 1" pads_begin="0, 0" pads_end="0, 0" auto_pad="explicit" />
1910
+ <input>
1911
+ <port id="0" precision="FP32">
1912
+ <dim>1</dim>
1913
+ <dim>128</dim>
1914
+ <dim>1</dim>
1915
+ <dim>1</dim>
1916
+ </port>
1917
+ <port id="1" precision="FP32">
1918
+ <dim>256</dim>
1919
+ <dim>128</dim>
1920
+ <dim>1</dim>
1921
+ <dim>1</dim>
1922
+ </port>
1923
+ </input>
1924
+ <output>
1925
+ <port id="2" precision="FP32">
1926
+ <dim>1</dim>
1927
+ <dim>256</dim>
1928
+ <dim>1</dim>
1929
+ <dim>1</dim>
1930
+ </port>
1931
+ </output>
1932
+ </layer>
1933
+ <layer id="103" name="Reshape_264_compressed" type="Const" version="opset1">
1934
+ <data element_type="f16" shape="1, 256, 1, 1" offset="345496" size="512" />
1935
+ <output>
1936
+ <port id="0" precision="FP16">
1937
+ <dim>1</dim>
1938
+ <dim>256</dim>
1939
+ <dim>1</dim>
1940
+ <dim>1</dim>
1941
+ </port>
1942
+ </output>
1943
+ </layer>
1944
+ <layer id="104" name="Reshape_264" type="Convert" version="opset1">
1945
+ <data destination_type="f32" />
1946
+ <rt_info>
1947
+ <attribute name="decompression" version="0" />
1948
+ </rt_info>
1949
+ <input>
1950
+ <port id="0" precision="FP16">
1951
+ <dim>1</dim>
1952
+ <dim>256</dim>
1953
+ <dim>1</dim>
1954
+ <dim>1</dim>
1955
+ </port>
1956
+ </input>
1957
+ <output>
1958
+ <port id="1" precision="FP32">
1959
+ <dim>1</dim>
1960
+ <dim>256</dim>
1961
+ <dim>1</dim>
1962
+ <dim>1</dim>
1963
+ </port>
1964
+ </output>
1965
+ </layer>
1966
+ <layer id="105" name="89" type="Add" version="opset1">
1967
+ <data auto_broadcast="numpy" />
1968
+ <input>
1969
+ <port id="0" precision="FP32">
1970
+ <dim>1</dim>
1971
+ <dim>256</dim>
1972
+ <dim>1</dim>
1973
+ <dim>1</dim>
1974
+ </port>
1975
+ <port id="1" precision="FP32">
1976
+ <dim>1</dim>
1977
+ <dim>256</dim>
1978
+ <dim>1</dim>
1979
+ <dim>1</dim>
1980
+ </port>
1981
+ </input>
1982
+ <output>
1983
+ <port id="2" precision="FP32" names="89">
1984
+ <dim>1</dim>
1985
+ <dim>256</dim>
1986
+ <dim>1</dim>
1987
+ <dim>1</dim>
1988
+ </port>
1989
+ </output>
1990
+ </layer>
1991
+ <layer id="106" name="56_compressed" type="Const" version="opset1">
1992
+ <data element_type="f16" shape="1" offset="346008" size="2" />
1993
+ <output>
1994
+ <port id="0" precision="FP16">
1995
+ <dim>1</dim>
1996
+ </port>
1997
+ </output>
1998
+ </layer>
1999
+ <layer id="107" name="56" type="Convert" version="opset1">
2000
+ <data destination_type="f32" />
2001
+ <rt_info>
2002
+ <attribute name="decompression" version="0" />
2003
+ </rt_info>
2004
+ <input>
2005
+ <port id="0" precision="FP16">
2006
+ <dim>1</dim>
2007
+ </port>
2008
+ </input>
2009
+ <output>
2010
+ <port id="1" precision="FP32" names="56">
2011
+ <dim>1</dim>
2012
+ </port>
2013
+ </output>
2014
+ </layer>
2015
+ <layer id="108" name="90" type="PReLU" version="opset1">
2016
+ <input>
2017
+ <port id="0" precision="FP32">
2018
+ <dim>1</dim>
2019
+ <dim>256</dim>
2020
+ <dim>1</dim>
2021
+ <dim>1</dim>
2022
+ </port>
2023
+ <port id="1" precision="FP32">
2024
+ <dim>1</dim>
2025
+ </port>
2026
+ </input>
2027
+ <output>
2028
+ <port id="2" precision="FP32" names="90">
2029
+ <dim>1</dim>
2030
+ <dim>256</dim>
2031
+ <dim>1</dim>
2032
+ <dim>1</dim>
2033
+ </port>
2034
+ </output>
2035
+ </layer>
2036
+ <layer id="109" name="Constant_2056_compressed" type="Const" version="opset1">
2037
+ <data element_type="f16" shape="1, 256, 1, 1" offset="346010" size="512" />
2038
+ <output>
2039
+ <port id="0" precision="FP16">
2040
+ <dim>1</dim>
2041
+ <dim>256</dim>
2042
+ <dim>1</dim>
2043
+ <dim>1</dim>
2044
+ </port>
2045
+ </output>
2046
+ </layer>
2047
+ <layer id="110" name="Constant_2056" type="Convert" version="opset1">
2048
+ <data destination_type="f32" />
2049
+ <rt_info>
2050
+ <attribute name="decompression" version="0" />
2051
+ </rt_info>
2052
+ <input>
2053
+ <port id="0" precision="FP16">
2054
+ <dim>1</dim>
2055
+ <dim>256</dim>
2056
+ <dim>1</dim>
2057
+ <dim>1</dim>
2058
+ </port>
2059
+ </input>
2060
+ <output>
2061
+ <port id="1" precision="FP32">
2062
+ <dim>1</dim>
2063
+ <dim>256</dim>
2064
+ <dim>1</dim>
2065
+ <dim>1</dim>
2066
+ </port>
2067
+ </output>
2068
+ </layer>
2069
+ <layer id="111" name="Multiply_2112" type="Multiply" version="opset1">
2070
+ <data auto_broadcast="numpy" />
2071
+ <input>
2072
+ <port id="0" precision="FP32">
2073
+ <dim>1</dim>
2074
+ <dim>256</dim>
2075
+ <dim>1</dim>
2076
+ <dim>1</dim>
2077
+ </port>
2078
+ <port id="1" precision="FP32">
2079
+ <dim>1</dim>
2080
+ <dim>256</dim>
2081
+ <dim>1</dim>
2082
+ <dim>1</dim>
2083
+ </port>
2084
+ </input>
2085
+ <output>
2086
+ <port id="2" precision="FP32">
2087
+ <dim>1</dim>
2088
+ <dim>256</dim>
2089
+ <dim>1</dim>
2090
+ <dim>1</dim>
2091
+ </port>
2092
+ </output>
2093
+ </layer>
2094
+ <layer id="112" name="Constant_2117_compressed" type="Const" version="opset1">
2095
+ <data element_type="f16" shape="1, 256, 1, 1" offset="346522" size="512" />
2096
+ <output>
2097
+ <port id="0" precision="FP16">
2098
+ <dim>1</dim>
2099
+ <dim>256</dim>
2100
+ <dim>1</dim>
2101
+ <dim>1</dim>
2102
+ </port>
2103
+ </output>
2104
+ </layer>
2105
+ <layer id="113" name="Constant_2117" type="Convert" version="opset1">
2106
+ <data destination_type="f32" />
2107
+ <rt_info>
2108
+ <attribute name="decompression" version="0" />
2109
+ </rt_info>
2110
+ <input>
2111
+ <port id="0" precision="FP16">
2112
+ <dim>1</dim>
2113
+ <dim>256</dim>
2114
+ <dim>1</dim>
2115
+ <dim>1</dim>
2116
+ </port>
2117
+ </input>
2118
+ <output>
2119
+ <port id="1" precision="FP32">
2120
+ <dim>1</dim>
2121
+ <dim>256</dim>
2122
+ <dim>1</dim>
2123
+ <dim>1</dim>
2124
+ </port>
2125
+ </output>
2126
+ </layer>
2127
+ <layer id="114" name="91" type="Add" version="opset1">
2128
+ <data auto_broadcast="numpy" />
2129
+ <input>
2130
+ <port id="0" precision="FP32">
2131
+ <dim>1</dim>
2132
+ <dim>256</dim>
2133
+ <dim>1</dim>
2134
+ <dim>1</dim>
2135
+ </port>
2136
+ <port id="1" precision="FP32">
2137
+ <dim>1</dim>
2138
+ <dim>256</dim>
2139
+ <dim>1</dim>
2140
+ <dim>1</dim>
2141
+ </port>
2142
+ </input>
2143
+ <output>
2144
+ <port id="2" precision="FP32" names="91">
2145
+ <dim>1</dim>
2146
+ <dim>256</dim>
2147
+ <dim>1</dim>
2148
+ <dim>1</dim>
2149
+ </port>
2150
+ </output>
2151
+ </layer>
2152
+ <layer id="115" name="62_compressed" type="Const" version="opset1">
2153
+ <data element_type="f16" shape="64, 256, 1, 1" offset="347034" size="32768" />
2154
+ <output>
2155
+ <port id="0" precision="FP16">
2156
+ <dim>64</dim>
2157
+ <dim>256</dim>
2158
+ <dim>1</dim>
2159
+ <dim>1</dim>
2160
+ </port>
2161
+ </output>
2162
+ </layer>
2163
+ <layer id="116" name="62" type="Convert" version="opset1">
2164
+ <data destination_type="f32" />
2165
+ <rt_info>
2166
+ <attribute name="decompression" version="0" />
2167
+ </rt_info>
2168
+ <input>
2169
+ <port id="0" precision="FP16">
2170
+ <dim>64</dim>
2171
+ <dim>256</dim>
2172
+ <dim>1</dim>
2173
+ <dim>1</dim>
2174
+ </port>
2175
+ </input>
2176
+ <output>
2177
+ <port id="1" precision="FP32" names="62">
2178
+ <dim>64</dim>
2179
+ <dim>256</dim>
2180
+ <dim>1</dim>
2181
+ <dim>1</dim>
2182
+ </port>
2183
+ </output>
2184
+ </layer>
2185
+ <layer id="117" name="92/WithoutBiases" type="Convolution" version="opset1">
2186
+ <data strides="1, 1" dilations="1, 1" pads_begin="0, 0" pads_end="0, 0" auto_pad="explicit" />
2187
+ <input>
2188
+ <port id="0" precision="FP32">
2189
+ <dim>1</dim>
2190
+ <dim>256</dim>
2191
+ <dim>1</dim>
2192
+ <dim>1</dim>
2193
+ </port>
2194
+ <port id="1" precision="FP32">
2195
+ <dim>64</dim>
2196
+ <dim>256</dim>
2197
+ <dim>1</dim>
2198
+ <dim>1</dim>
2199
+ </port>
2200
+ </input>
2201
+ <output>
2202
+ <port id="2" precision="FP32">
2203
+ <dim>1</dim>
2204
+ <dim>64</dim>
2205
+ <dim>1</dim>
2206
+ <dim>1</dim>
2207
+ </port>
2208
+ </output>
2209
+ </layer>
2210
+ <layer id="118" name="Reshape_285_compressed" type="Const" version="opset1">
2211
+ <data element_type="f16" shape="1, 64, 1, 1" offset="379802" size="128" />
2212
+ <output>
2213
+ <port id="0" precision="FP16">
2214
+ <dim>1</dim>
2215
+ <dim>64</dim>
2216
+ <dim>1</dim>
2217
+ <dim>1</dim>
2218
+ </port>
2219
+ </output>
2220
+ </layer>
2221
+ <layer id="119" name="Reshape_285" type="Convert" version="opset1">
2222
+ <data destination_type="f32" />
2223
+ <rt_info>
2224
+ <attribute name="decompression" version="0" />
2225
+ </rt_info>
2226
+ <input>
2227
+ <port id="0" precision="FP16">
2228
+ <dim>1</dim>
2229
+ <dim>64</dim>
2230
+ <dim>1</dim>
2231
+ <dim>1</dim>
2232
+ </port>
2233
+ </input>
2234
+ <output>
2235
+ <port id="1" precision="FP32">
2236
+ <dim>1</dim>
2237
+ <dim>64</dim>
2238
+ <dim>1</dim>
2239
+ <dim>1</dim>
2240
+ </port>
2241
+ </output>
2242
+ </layer>
2243
+ <layer id="120" name="92" type="Add" version="opset1">
2244
+ <data auto_broadcast="numpy" />
2245
+ <input>
2246
+ <port id="0" precision="FP32">
2247
+ <dim>1</dim>
2248
+ <dim>64</dim>
2249
+ <dim>1</dim>
2250
+ <dim>1</dim>
2251
+ </port>
2252
+ <port id="1" precision="FP32">
2253
+ <dim>1</dim>
2254
+ <dim>64</dim>
2255
+ <dim>1</dim>
2256
+ <dim>1</dim>
2257
+ </port>
2258
+ </input>
2259
+ <output>
2260
+ <port id="2" precision="FP32" names="92">
2261
+ <dim>1</dim>
2262
+ <dim>64</dim>
2263
+ <dim>1</dim>
2264
+ <dim>1</dim>
2265
+ </port>
2266
+ </output>
2267
+ </layer>
2268
+ <layer id="121" name="64_compressed" type="Const" version="opset1">
2269
+ <data element_type="f16" shape="1" offset="379930" size="2" />
2270
+ <output>
2271
+ <port id="0" precision="FP16">
2272
+ <dim>1</dim>
2273
+ </port>
2274
+ </output>
2275
+ </layer>
2276
+ <layer id="122" name="64" type="Convert" version="opset1">
2277
+ <data destination_type="f32" />
2278
+ <rt_info>
2279
+ <attribute name="decompression" version="0" />
2280
+ </rt_info>
2281
+ <input>
2282
+ <port id="0" precision="FP16">
2283
+ <dim>1</dim>
2284
+ </port>
2285
+ </input>
2286
+ <output>
2287
+ <port id="1" precision="FP32" names="64">
2288
+ <dim>1</dim>
2289
+ </port>
2290
+ </output>
2291
+ </layer>
2292
+ <layer id="123" name="93" type="PReLU" version="opset1">
2293
+ <input>
2294
+ <port id="0" precision="FP32">
2295
+ <dim>1</dim>
2296
+ <dim>64</dim>
2297
+ <dim>1</dim>
2298
+ <dim>1</dim>
2299
+ </port>
2300
+ <port id="1" precision="FP32">
2301
+ <dim>1</dim>
2302
+ </port>
2303
+ </input>
2304
+ <output>
2305
+ <port id="2" precision="FP32" names="93">
2306
+ <dim>1</dim>
2307
+ <dim>64</dim>
2308
+ <dim>1</dim>
2309
+ <dim>1</dim>
2310
+ </port>
2311
+ </output>
2312
+ </layer>
2313
+ <layer id="124" name="65_compressed" type="Const" version="opset1">
2314
+ <data element_type="f16" shape="10, 64, 1, 1" offset="379932" size="1280" />
2315
+ <output>
2316
+ <port id="0" precision="FP16">
2317
+ <dim>10</dim>
2318
+ <dim>64</dim>
2319
+ <dim>1</dim>
2320
+ <dim>1</dim>
2321
+ </port>
2322
+ </output>
2323
+ </layer>
2324
+ <layer id="125" name="65" type="Convert" version="opset1">
2325
+ <data destination_type="f32" />
2326
+ <rt_info>
2327
+ <attribute name="decompression" version="0" />
2328
+ </rt_info>
2329
+ <input>
2330
+ <port id="0" precision="FP16">
2331
+ <dim>10</dim>
2332
+ <dim>64</dim>
2333
+ <dim>1</dim>
2334
+ <dim>1</dim>
2335
+ </port>
2336
+ </input>
2337
+ <output>
2338
+ <port id="1" precision="FP32" names="65">
2339
+ <dim>10</dim>
2340
+ <dim>64</dim>
2341
+ <dim>1</dim>
2342
+ <dim>1</dim>
2343
+ </port>
2344
+ </output>
2345
+ </layer>
2346
+ <layer id="126" name="94/WithoutBiases" type="Convolution" version="opset1">
2347
+ <data strides="1, 1" dilations="1, 1" pads_begin="0, 0" pads_end="0, 0" auto_pad="explicit" />
2348
+ <input>
2349
+ <port id="0" precision="FP32">
2350
+ <dim>1</dim>
2351
+ <dim>64</dim>
2352
+ <dim>1</dim>
2353
+ <dim>1</dim>
2354
+ </port>
2355
+ <port id="1" precision="FP32">
2356
+ <dim>10</dim>
2357
+ <dim>64</dim>
2358
+ <dim>1</dim>
2359
+ <dim>1</dim>
2360
+ </port>
2361
+ </input>
2362
+ <output>
2363
+ <port id="2" precision="FP32">
2364
+ <dim>1</dim>
2365
+ <dim>10</dim>
2366
+ <dim>1</dim>
2367
+ <dim>1</dim>
2368
+ </port>
2369
+ </output>
2370
+ </layer>
2371
+ <layer id="127" name="Reshape_301_compressed" type="Const" version="opset1">
2372
+ <data element_type="f16" shape="1, 10, 1, 1" offset="381212" size="20" />
2373
+ <output>
2374
+ <port id="0" precision="FP16">
2375
+ <dim>1</dim>
2376
+ <dim>10</dim>
2377
+ <dim>1</dim>
2378
+ <dim>1</dim>
2379
+ </port>
2380
+ </output>
2381
+ </layer>
2382
+ <layer id="128" name="Reshape_301" type="Convert" version="opset1">
2383
+ <data destination_type="f32" />
2384
+ <rt_info>
2385
+ <attribute name="decompression" version="0" />
2386
+ </rt_info>
2387
+ <input>
2388
+ <port id="0" precision="FP16">
2389
+ <dim>1</dim>
2390
+ <dim>10</dim>
2391
+ <dim>1</dim>
2392
+ <dim>1</dim>
2393
+ </port>
2394
+ </input>
2395
+ <output>
2396
+ <port id="1" precision="FP32">
2397
+ <dim>1</dim>
2398
+ <dim>10</dim>
2399
+ <dim>1</dim>
2400
+ <dim>1</dim>
2401
+ </port>
2402
+ </output>
2403
+ </layer>
2404
+ <layer id="129" name="94" type="Add" version="opset1">
2405
+ <data auto_broadcast="numpy" />
2406
+ <input>
2407
+ <port id="0" precision="FP32">
2408
+ <dim>1</dim>
2409
+ <dim>10</dim>
2410
+ <dim>1</dim>
2411
+ <dim>1</dim>
2412
+ </port>
2413
+ <port id="1" precision="FP32">
2414
+ <dim>1</dim>
2415
+ <dim>10</dim>
2416
+ <dim>1</dim>
2417
+ <dim>1</dim>
2418
+ </port>
2419
+ </input>
2420
+ <output>
2421
+ <port id="2" precision="FP32" names="94">
2422
+ <dim>1</dim>
2423
+ <dim>10</dim>
2424
+ <dim>1</dim>
2425
+ <dim>1</dim>
2426
+ </port>
2427
+ </output>
2428
+ </layer>
2429
+ <layer id="130" name="95" type="Sigmoid" version="opset1">
2430
+ <input>
2431
+ <port id="0" precision="FP32">
2432
+ <dim>1</dim>
2433
+ <dim>10</dim>
2434
+ <dim>1</dim>
2435
+ <dim>1</dim>
2436
+ </port>
2437
+ </input>
2438
+ <output>
2439
+ <port id="1" precision="FP32" names="95">
2440
+ <dim>1</dim>
2441
+ <dim>10</dim>
2442
+ <dim>1</dim>
2443
+ <dim>1</dim>
2444
+ </port>
2445
+ </output>
2446
+ </layer>
2447
+ <layer id="131" name="95/sink_port_0" type="Result" version="opset1">
2448
+ <input>
2449
+ <port id="0" precision="FP32">
2450
+ <dim>1</dim>
2451
+ <dim>10</dim>
2452
+ <dim>1</dim>
2453
+ <dim>1</dim>
2454
+ </port>
2455
+ </input>
2456
+ </layer>
2457
+ </layers>
2458
+ <edges>
2459
+ <edge from-layer="0" from-port="0" to-layer="3" to-port="0" />
2460
+ <edge from-layer="1" from-port="0" to-layer="2" to-port="0" />
2461
+ <edge from-layer="2" from-port="1" to-layer="3" to-port="1" />
2462
+ <edge from-layer="3" from-port="2" to-layer="6" to-port="0" />
2463
+ <edge from-layer="4" from-port="0" to-layer="5" to-port="0" />
2464
+ <edge from-layer="5" from-port="1" to-layer="6" to-port="1" />
2465
+ <edge from-layer="6" from-port="2" to-layer="9" to-port="0" />
2466
+ <edge from-layer="7" from-port="0" to-layer="8" to-port="0" />
2467
+ <edge from-layer="8" from-port="1" to-layer="9" to-port="1" />
2468
+ <edge from-layer="9" from-port="2" to-layer="12" to-port="0" />
2469
+ <edge from-layer="10" from-port="0" to-layer="11" to-port="0" />
2470
+ <edge from-layer="11" from-port="1" to-layer="12" to-port="1" />
2471
+ <edge from-layer="12" from-port="2" to-layer="15" to-port="0" />
2472
+ <edge from-layer="13" from-port="0" to-layer="14" to-port="0" />
2473
+ <edge from-layer="14" from-port="1" to-layer="15" to-port="1" />
2474
+ <edge from-layer="15" from-port="2" to-layer="16" to-port="0" />
2475
+ <edge from-layer="16" from-port="1" to-layer="19" to-port="0" />
2476
+ <edge from-layer="17" from-port="0" to-layer="18" to-port="0" />
2477
+ <edge from-layer="18" from-port="1" to-layer="19" to-port="1" />
2478
+ <edge from-layer="19" from-port="2" to-layer="22" to-port="0" />
2479
+ <edge from-layer="20" from-port="0" to-layer="21" to-port="0" />
2480
+ <edge from-layer="21" from-port="1" to-layer="22" to-port="1" />
2481
+ <edge from-layer="22" from-port="2" to-layer="25" to-port="0" />
2482
+ <edge from-layer="23" from-port="0" to-layer="24" to-port="0" />
2483
+ <edge from-layer="24" from-port="1" to-layer="25" to-port="1" />
2484
+ <edge from-layer="25" from-port="2" to-layer="28" to-port="0" />
2485
+ <edge from-layer="26" from-port="0" to-layer="27" to-port="0" />
2486
+ <edge from-layer="27" from-port="1" to-layer="28" to-port="1" />
2487
+ <edge from-layer="28" from-port="2" to-layer="31" to-port="0" />
2488
+ <edge from-layer="29" from-port="0" to-layer="30" to-port="0" />
2489
+ <edge from-layer="30" from-port="1" to-layer="31" to-port="1" />
2490
+ <edge from-layer="31" from-port="2" to-layer="32" to-port="0" />
2491
+ <edge from-layer="32" from-port="1" to-layer="35" to-port="0" />
2492
+ <edge from-layer="33" from-port="0" to-layer="34" to-port="0" />
2493
+ <edge from-layer="34" from-port="1" to-layer="35" to-port="1" />
2494
+ <edge from-layer="35" from-port="2" to-layer="38" to-port="0" />
2495
+ <edge from-layer="36" from-port="0" to-layer="37" to-port="0" />
2496
+ <edge from-layer="37" from-port="1" to-layer="38" to-port="1" />
2497
+ <edge from-layer="38" from-port="2" to-layer="41" to-port="0" />
2498
+ <edge from-layer="39" from-port="0" to-layer="40" to-port="0" />
2499
+ <edge from-layer="40" from-port="1" to-layer="41" to-port="1" />
2500
+ <edge from-layer="41" from-port="2" to-layer="44" to-port="0" />
2501
+ <edge from-layer="42" from-port="0" to-layer="43" to-port="0" />
2502
+ <edge from-layer="43" from-port="1" to-layer="44" to-port="1" />
2503
+ <edge from-layer="44" from-port="2" to-layer="47" to-port="0" />
2504
+ <edge from-layer="45" from-port="0" to-layer="46" to-port="0" />
2505
+ <edge from-layer="46" from-port="1" to-layer="47" to-port="1" />
2506
+ <edge from-layer="47" from-port="2" to-layer="48" to-port="0" />
2507
+ <edge from-layer="48" from-port="1" to-layer="51" to-port="0" />
2508
+ <edge from-layer="49" from-port="0" to-layer="50" to-port="0" />
2509
+ <edge from-layer="50" from-port="1" to-layer="51" to-port="1" />
2510
+ <edge from-layer="51" from-port="2" to-layer="54" to-port="0" />
2511
+ <edge from-layer="52" from-port="0" to-layer="53" to-port="0" />
2512
+ <edge from-layer="53" from-port="1" to-layer="54" to-port="1" />
2513
+ <edge from-layer="54" from-port="2" to-layer="57" to-port="0" />
2514
+ <edge from-layer="55" from-port="0" to-layer="56" to-port="0" />
2515
+ <edge from-layer="56" from-port="1" to-layer="57" to-port="1" />
2516
+ <edge from-layer="57" from-port="2" to-layer="60" to-port="0" />
2517
+ <edge from-layer="58" from-port="0" to-layer="59" to-port="0" />
2518
+ <edge from-layer="59" from-port="1" to-layer="60" to-port="1" />
2519
+ <edge from-layer="60" from-port="2" to-layer="63" to-port="0" />
2520
+ <edge from-layer="61" from-port="0" to-layer="62" to-port="0" />
2521
+ <edge from-layer="62" from-port="1" to-layer="63" to-port="1" />
2522
+ <edge from-layer="63" from-port="2" to-layer="66" to-port="0" />
2523
+ <edge from-layer="64" from-port="0" to-layer="65" to-port="0" />
2524
+ <edge from-layer="65" from-port="1" to-layer="66" to-port="1" />
2525
+ <edge from-layer="66" from-port="2" to-layer="69" to-port="0" />
2526
+ <edge from-layer="67" from-port="0" to-layer="68" to-port="0" />
2527
+ <edge from-layer="68" from-port="1" to-layer="69" to-port="1" />
2528
+ <edge from-layer="69" from-port="2" to-layer="72" to-port="0" />
2529
+ <edge from-layer="70" from-port="0" to-layer="71" to-port="0" />
2530
+ <edge from-layer="71" from-port="1" to-layer="72" to-port="1" />
2531
+ <edge from-layer="72" from-port="2" to-layer="75" to-port="0" />
2532
+ <edge from-layer="73" from-port="0" to-layer="74" to-port="0" />
2533
+ <edge from-layer="74" from-port="1" to-layer="75" to-port="1" />
2534
+ <edge from-layer="75" from-port="2" to-layer="78" to-port="0" />
2535
+ <edge from-layer="76" from-port="0" to-layer="77" to-port="0" />
2536
+ <edge from-layer="77" from-port="1" to-layer="78" to-port="1" />
2537
+ <edge from-layer="78" from-port="2" to-layer="81" to-port="0" />
2538
+ <edge from-layer="79" from-port="0" to-layer="80" to-port="0" />
2539
+ <edge from-layer="80" from-port="1" to-layer="81" to-port="1" />
2540
+ <edge from-layer="81" from-port="2" to-layer="84" to-port="0" />
2541
+ <edge from-layer="82" from-port="0" to-layer="83" to-port="0" />
2542
+ <edge from-layer="83" from-port="1" to-layer="84" to-port="1" />
2543
+ <edge from-layer="84" from-port="2" to-layer="87" to-port="0" />
2544
+ <edge from-layer="85" from-port="0" to-layer="86" to-port="0" />
2545
+ <edge from-layer="86" from-port="1" to-layer="87" to-port="1" />
2546
+ <edge from-layer="87" from-port="2" to-layer="90" to-port="0" />
2547
+ <edge from-layer="88" from-port="0" to-layer="89" to-port="0" />
2548
+ <edge from-layer="89" from-port="1" to-layer="90" to-port="1" />
2549
+ <edge from-layer="90" from-port="2" to-layer="93" to-port="0" />
2550
+ <edge from-layer="91" from-port="0" to-layer="92" to-port="0" />
2551
+ <edge from-layer="92" from-port="1" to-layer="93" to-port="1" />
2552
+ <edge from-layer="93" from-port="2" to-layer="96" to-port="0" />
2553
+ <edge from-layer="94" from-port="0" to-layer="95" to-port="0" />
2554
+ <edge from-layer="95" from-port="1" to-layer="96" to-port="1" />
2555
+ <edge from-layer="96" from-port="2" to-layer="99" to-port="0" />
2556
+ <edge from-layer="97" from-port="0" to-layer="98" to-port="0" />
2557
+ <edge from-layer="98" from-port="1" to-layer="99" to-port="1" />
2558
+ <edge from-layer="99" from-port="2" to-layer="102" to-port="0" />
2559
+ <edge from-layer="100" from-port="0" to-layer="101" to-port="0" />
2560
+ <edge from-layer="101" from-port="1" to-layer="102" to-port="1" />
2561
+ <edge from-layer="102" from-port="2" to-layer="105" to-port="0" />
2562
+ <edge from-layer="103" from-port="0" to-layer="104" to-port="0" />
2563
+ <edge from-layer="104" from-port="1" to-layer="105" to-port="1" />
2564
+ <edge from-layer="105" from-port="2" to-layer="108" to-port="0" />
2565
+ <edge from-layer="106" from-port="0" to-layer="107" to-port="0" />
2566
+ <edge from-layer="107" from-port="1" to-layer="108" to-port="1" />
2567
+ <edge from-layer="108" from-port="2" to-layer="111" to-port="0" />
2568
+ <edge from-layer="109" from-port="0" to-layer="110" to-port="0" />
2569
+ <edge from-layer="110" from-port="1" to-layer="111" to-port="1" />
2570
+ <edge from-layer="111" from-port="2" to-layer="114" to-port="0" />
2571
+ <edge from-layer="112" from-port="0" to-layer="113" to-port="0" />
2572
+ <edge from-layer="113" from-port="1" to-layer="114" to-port="1" />
2573
+ <edge from-layer="114" from-port="2" to-layer="117" to-port="0" />
2574
+ <edge from-layer="115" from-port="0" to-layer="116" to-port="0" />
2575
+ <edge from-layer="116" from-port="1" to-layer="117" to-port="1" />
2576
+ <edge from-layer="117" from-port="2" to-layer="120" to-port="0" />
2577
+ <edge from-layer="118" from-port="0" to-layer="119" to-port="0" />
2578
+ <edge from-layer="119" from-port="1" to-layer="120" to-port="1" />
2579
+ <edge from-layer="120" from-port="2" to-layer="123" to-port="0" />
2580
+ <edge from-layer="121" from-port="0" to-layer="122" to-port="0" />
2581
+ <edge from-layer="122" from-port="1" to-layer="123" to-port="1" />
2582
+ <edge from-layer="123" from-port="2" to-layer="126" to-port="0" />
2583
+ <edge from-layer="124" from-port="0" to-layer="125" to-port="0" />
2584
+ <edge from-layer="125" from-port="1" to-layer="126" to-port="1" />
2585
+ <edge from-layer="126" from-port="2" to-layer="129" to-port="0" />
2586
+ <edge from-layer="127" from-port="0" to-layer="128" to-port="0" />
2587
+ <edge from-layer="128" from-port="1" to-layer="129" to-port="1" />
2588
+ <edge from-layer="129" from-port="2" to-layer="130" to-port="0" />
2589
+ <edge from-layer="130" from-port="1" to-layer="131" to-port="0" />
2590
+ </edges>
2591
+ <rt_info>
2592
+ <MO_version value="custom_HEAD_f6ee6e92f846a8c665e4a7089c51481f9689a3b5" />
2593
+ <Runtime_version value="2023.0.0-10521-f6ee6e92f84-HEAD" />
2594
+ <conversion_parameters>
2595
+ <framework value="onnx" />
2596
+ <input value="0" />
2597
+ <input_model value="DIR/Landnet_wing_blur2_21000.onnx" />
2598
+ <input_shape value="[1,3,48,48]" />
2599
+ <layout value="0(nchw)" />
2600
+ <model_name value="landmarks-regression-retail-0009" />
2601
+ <output_dir value="DIR" />
2602
+ <reverse_input_channels value="True" />
2603
+ <scale_values value="0[255.0]" />
2604
+ </conversion_parameters>
2605
+ <legacy_frontend value="False" />
2606
+ </rt_info>
2607
+ </net>
ov_models.py ADDED
@@ -0,0 +1,89 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """ov_models.py — thin OpenVINO runtime wrappers for the Intel face models.
2
+
3
+ Three models, all CPU, all from Open Model Zoo:
4
+ - face-detection-retail-0004 : SSD face detector
5
+ - landmarks-regression-retail-0009 : 5 keypoints (for alignment)
6
+ - face-reidentification-retail-0095 : 256-d embedding, cosine-comparable
7
+
8
+ [OpenVINO 101] An OV model ships as two files: <name>.xml (graph) + <name>.bin
9
+ (weights), the "IR" format. You read it, compile it for a target device once
10
+ (slow), then call the compiled model many times (fast). compile_model("CPU")
11
+ picks CPU; nothing here ever touches a GPU, so it won't fight the VLM for
12
+ Zero GPU quota.
13
+
14
+ The IR files are pulled straight from Intel's public OMZ storage at boot — no
15
+ `omz_downloader` / `openvino-dev` needed (that package is deprecated and fights
16
+ modern `openvino`). Nothing to commit; first boot fetches ~5 MB total.
17
+ """
18
+
19
+ import urllib.request
20
+ from pathlib import Path
21
+
22
+ import cv2
23
+ import numpy as np
24
+ import openvino as ov
25
+
26
+ MODELS_DIR = Path(__file__).parent / "models"
27
+ PRECISION = "FP16" # FP16 IR runs fine on CPU; smaller download than FP32
28
+
29
+ MODEL_NAMES = [
30
+ "face-detection-retail-0004", # detector
31
+ "landmarks-regression-retail-0009", # 5 keypoints -> alignment
32
+ "face-reidentification-retail-0095", # 256-d embedding
33
+ ]
34
+
35
+ # All three live under the same 2023.0 / models_bin/1 path on OMZ storage.
36
+ _OMZ_BASE = ("https://storage.openvinotoolkit.org/repositories/open_model_zoo/"
37
+ "2023.0/models_bin/1")
38
+
39
+ _core = ov.Core()
40
+
41
+
42
+ def download_models():
43
+ """Fetch the FP16 IR (.xml + .bin) for each model into models/<name>/.
44
+
45
+ Idempotent: skips anything already on disk, so it's safe to call at every
46
+ boot. Downloads to a .part file first, then renames, so an interrupted
47
+ download never leaves a half-written file that looks complete.
48
+ """
49
+ for name in MODEL_NAMES:
50
+ dst = MODELS_DIR / name
51
+ xml, binf = dst / f"{name}.xml", dst / f"{name}.bin"
52
+ if xml.exists() and binf.exists():
53
+ continue
54
+ dst.mkdir(parents=True, exist_ok=True)
55
+ for target in (xml, binf):
56
+ url = f"{_OMZ_BASE}/{name}/{PRECISION}/{target.name}"
57
+ print(f"[ov] downloading {target.name} ...")
58
+ part = target.with_suffix(target.suffix + ".part")
59
+ urllib.request.urlretrieve(url, part)
60
+ part.replace(target)
61
+
62
+
63
+ def _find_xml(name: str) -> str:
64
+ hits = list(MODELS_DIR.rglob(f"{name}.xml"))
65
+ if not hits:
66
+ raise FileNotFoundError(
67
+ f"{name}.xml not found under {MODELS_DIR}. Run download_models().")
68
+ return str(hits[0])
69
+
70
+
71
+ class OVModel:
72
+ """Compile once, infer many. Handles NCHW resize + BGR for you."""
73
+
74
+ def __init__(self, name: str):
75
+ model = _core.read_model(_find_xml(name))
76
+ self.compiled = _core.compile_model(model, "CPU")
77
+ self.input = self.compiled.input(0)
78
+ # IR input layout is N,C,H,W — pull the model's expected H,W.
79
+ _, _, self.h, self.w = self.input.shape
80
+
81
+ def _prep(self, bgr: np.ndarray) -> np.ndarray:
82
+ img = cv2.resize(bgr, (self.w, self.h))
83
+ img = img.transpose(2, 0, 1)[None].astype(np.float32) # HWC->NCHW
84
+ return img
85
+
86
+ def infer(self, bgr: np.ndarray):
87
+ """Returns the first output blob as a numpy array."""
88
+ out = self.compiled({self.input: self._prep(bgr)})
89
+ return out[self.compiled.output(0)]
packages.txt ADDED
@@ -0,0 +1 @@
 
 
1
+ ffmpeg
qwen_download.py ADDED
@@ -0,0 +1,23 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from transformers import AutoProcessor, AutoModelForMultimodalLM
2
+
3
+ processor = AutoProcessor.from_pretrained("Qwen/Qwen2.5-VL-3B-Instruct")
4
+ model = AutoModelForMultimodalLM.from_pretrained("Qwen/Qwen2.5-VL-3B-Instruct")
5
+ messages = [
6
+ {
7
+ "role": "user",
8
+ "content": [
9
+ {"type": "image", "url": "https://huggingface.co/datasets/huggingface/documentation-images/resolve/main/p-blog/candy.JPG"},
10
+ {"type": "text", "text": "What animal is on the candy?"}
11
+ ]
12
+ },
13
+ ]
14
+ inputs = processor.apply_chat_template(
15
+ messages,
16
+ add_generation_prompt=True,
17
+ tokenize=True,
18
+ return_dict=True,
19
+ return_tensors="pt",
20
+ ).to(model.device)
21
+
22
+ outputs = model.generate(**inputs, max_new_tokens=40)
23
+ print(processor.decode(outputs[0][inputs["input_ids"].shape[-1]:]))
requirements.txt ADDED
@@ -0,0 +1,20 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # --- Gradio runtime ---
2
+ gradio>=5.0
3
+ spaces
4
+
5
+ # --- VLM (Qwen2.5-VL-7B, auto-downloads from HF Hub at first run) ---
6
+ torch
7
+ transformers>=4.49
8
+ accelerate
9
+ qwen-vl-utils
10
+
11
+ # --- OpenVINO face models (runtime only; IR files are committed in models/) ---
12
+ openvino>=2024.4
13
+
14
+ # --- media / utils ---
15
+ opencv-python-headless
16
+ pillow
17
+ numpy
18
+ uvicorn
19
+ fastapi
20
+ python-multipart
video.py ADDED
@@ -0,0 +1,76 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """video.py — preprocessing: probe (rotation-aware), compute 720p target,
2
+ normalize to a single upright browser-friendly mp4 that BOTH cv2 and the
3
+ player use. One source of truth kills the cv2-ignores-rotation bug.
4
+ """
5
+
6
+ import json
7
+ import subprocess
8
+ import tempfile
9
+
10
+ SHORT_CAP = 720 # shorter side never exceeds this
11
+ LONG_CAP = 1280 # longer side never exceeds this
12
+
13
+
14
+ def _probe(video_path: str) -> dict:
15
+ out = subprocess.run(
16
+ ["ffprobe", "-v", "quiet", "-print_format", "json",
17
+ "-show_format", "-show_streams", video_path],
18
+ capture_output=True, text=True, check=True,
19
+ )
20
+ return json.loads(out.stdout)
21
+
22
+
23
+ def get_duration(video_path: str) -> float:
24
+ return float(_probe(video_path)["format"]["duration"])
25
+
26
+
27
+ def get_display_dims(video_path: str) -> tuple[int, int]:
28
+ """Return (width, height) as DISPLAYED — i.e. after rotation metadata is
29
+ applied. Phone portrait clips are often stored landscape + a rotate flag;
30
+ if we ignored it the target size (and detection) would be sideways.
31
+ """
32
+ info = _probe(video_path)
33
+ v = next(s for s in info["streams"] if s["codec_type"] == "video")
34
+ w, h = int(v["width"]), int(v["height"])
35
+
36
+ rot = 0
37
+ if "tags" in v and "rotate" in v["tags"]:
38
+ rot = int(v["tags"]["rotate"])
39
+ for sd in v.get("side_data_list", []):
40
+ if "rotation" in sd:
41
+ rot = int(sd["rotation"])
42
+ if abs(rot) % 180 == 90: # 90 or 270 -> swap
43
+ w, h = h, w
44
+ return w, h
45
+
46
+
47
+ def compute_target_size(w: int, h: int) -> tuple[int, int]:
48
+ """Scale = min(720/short, 1280/long, 1.0); dims forced even; no upscale."""
49
+ short, long = min(w, h), max(w, h)
50
+ s = min(SHORT_CAP / short, LONG_CAP / long, 1.0)
51
+ out_w = max(2, int(round(w * s / 2)) * 2)
52
+ out_h = max(2, int(round(h * s / 2)) * 2)
53
+ return out_w, out_h
54
+
55
+
56
+ def normalize_video(video_path: str) -> tuple[str, int, int]:
57
+ """ffmpeg: autorotate + scale to target + re-encode H.264 (+ keep audio).
58
+ Returns (normalized_path, out_w, out_h). Everything downstream uses this.
59
+ """
60
+ w, h = get_display_dims(video_path)
61
+ out_w, out_h = compute_target_size(w, h)
62
+
63
+ # NamedTemporaryFile gives us a safe unique path (mktemp is deprecated /
64
+ # racy); close it and let ffmpeg overwrite via -y.
65
+ tmp = tempfile.NamedTemporaryFile(delete=False, suffix=".mp4")
66
+ out_path = tmp.name
67
+ tmp.close()
68
+
69
+ subprocess.run(
70
+ ["ffmpeg", "-y", "-i", video_path,
71
+ "-vf", f"scale={out_w}:{out_h}",
72
+ "-c:v", "libx264", "-preset", "veryfast", "-pix_fmt", "yuv420p",
73
+ "-c:a", "aac", "-movflags", "+faststart", out_path],
74
+ check=True, capture_output=True,
75
+ )
76
+ return out_path, out_w, out_h