techfreakworm commited on
Commit
ed3abc0
·
unverified ·
1 Parent(s): 8b4f49e

docs: implementation plan for Topaz Cinema Slate + drawer redesign

Browse files
docs/superpowers/plans/2026-05-01-topaz-drawer-redesign.md ADDED
@@ -0,0 +1,535 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Topaz Cinema Slate + Drawer Redesign — Implementation Plan
2
+
3
+ > **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
4
+
5
+ **Goal:** Apply the Topaz Cinema Slate dark palette and the hamburger-drawer layout (open by default ≥1024 px, hidden behind ≡ button below) to the existing Gradio app, with no logic or backend changes.
6
+
7
+ **Architecture:** All edits land in `app.py`. Three concerns: (1) `gr.themes.Base().set(...)` overrides for the 12 Topaz tokens, (2) full rewrite of `_CUSTOM_CSS` for slate-on-slate styling + drawer mechanics + responsive breakpoint, (3) markup tweak — wrap the existing sidebar `gr.Column` in a `drawer` div with a header row above the shell that holds the ≡ toggle, title, and active-mode tag.
8
+
9
+ **Tech Stack:** Gradio 5.50, IBM Plex Sans/Mono via Google Fonts, pure-CSS drawer toggle using a hidden `<input type="checkbox">` + `:checked` sibling selectors (no JS framework — but a tiny inline `<script>` block in `head=` syncs the active-mode tag and persists drawer state to `localStorage`).
10
+
11
+ ---
12
+
13
+ ## File Structure
14
+
15
+ - **Modify:** `app.py`
16
+ - `_CUSTOM_CSS` block — fully replaced
17
+ - `build_app()` — Blocks `theme=` and `head=` parameters added; markup gets a header row + drawer column wrapper
18
+
19
+ No other files are touched. `backend.py`, `models.py`, `modes.py`, `workflow.py`, `ui.py` are unaffected.
20
+
21
+ ---
22
+
23
+ ### Task 1 — Add Topaz theme tokens to `gr.Blocks`
24
+
25
+ **Files:** Modify `app.py:191`
26
+
27
+ - [ ] **Step 1: Define the Topaz `gr.themes.Base()` instance**
28
+
29
+ Add this just above `def build_app()` near `app.py:189`:
30
+
31
+ ```python
32
+ _TOPAZ_THEME = gr.themes.Base(
33
+ primary_hue=gr.themes.Color(
34
+ c50="#FBE5C7", c100="#F5D29C", c200="#EFC174", c300="#E9B05A",
35
+ c400="#E5A75B", c500="#E0A458", c600="#C68D3F", c700="#A6722E",
36
+ c800="#7E5722", c900="#583C18", c950="#3A2810",
37
+ ),
38
+ neutral_hue=gr.themes.Color(
39
+ c50="#E6E8EB", c100="#C9CDD3", c200="#ACB1B9", c300="#909700",
40
+ c400="#7C8693", c500="#626972", c600="#4A4F58", c700="#363B43",
41
+ c800="#262C35", c900="#1A1F26", c950="#12161B",
42
+ ),
43
+ font=(gr.themes.GoogleFont("IBM Plex Sans"), "ui-sans-serif", "system-ui", "sans-serif"),
44
+ font_mono=(gr.themes.GoogleFont("IBM Plex Mono"), "ui-monospace", "monospace"),
45
+ ).set(
46
+ body_background_fill="#12161B",
47
+ background_fill_primary="#12161B",
48
+ background_fill_secondary="#1A1F26",
49
+ block_background_fill="#1A1F26",
50
+ block_label_background_fill="transparent",
51
+ body_text_color="#E6E8EB",
52
+ body_text_color_subdued="#7C8693",
53
+ border_color_primary="#262C35",
54
+ border_color_accent="#E0A458",
55
+ button_primary_background_fill="#E0A458",
56
+ button_primary_background_fill_hover="#F0B870",
57
+ button_primary_text_color="#12161B",
58
+ button_secondary_background_fill="#1A1F26",
59
+ button_secondary_background_fill_hover="#232930",
60
+ button_secondary_text_color="#E6E8EB",
61
+ button_secondary_border_color="#262C35",
62
+ input_background_fill="#12161B",
63
+ input_border_color="#262C35",
64
+ input_border_color_focus="#E0A458",
65
+ error_background_fill="#3A1E20",
66
+ error_text_color="#F4A6A8",
67
+ slider_color="#E0A458",
68
+ )
69
+ ```
70
+
71
+ - [ ] **Step 2: Wire the theme into `build_app`**
72
+
73
+ Change `app.py:191` from:
74
+
75
+ ```python
76
+ with gr.Blocks(theme=gr.themes.Soft(), title="LTX 2.3 All-in-One", css=_CUSTOM_CSS) as app:
77
+ ```
78
+
79
+ to:
80
+
81
+ ```python
82
+ with gr.Blocks(theme=_TOPAZ_THEME, title="LTX 2.3 Studio", css=_CUSTOM_CSS) as app:
83
+ ```
84
+
85
+ - [ ] **Step 3: Smoke-test that imports still work**
86
+
87
+ Run: `python -c "import app; print('OK')"` in the project root.
88
+ Expected: `OK` printed, no traceback.
89
+
90
+ - [ ] **Step 4: Commit**
91
+
92
+ ```bash
93
+ git add app.py
94
+ git commit -m "feat(ui): apply Topaz Cinema Slate theme tokens"
95
+ ```
96
+
97
+ ---
98
+
99
+ ### Task 2 — Replace `_CUSTOM_CSS` with Topaz styles + drawer mechanics
100
+
101
+ **Files:** Modify `app.py:127-182` (the whole `_CUSTOM_CSS = """..."""` block)
102
+
103
+ - [ ] **Step 1: Replace the entire `_CUSTOM_CSS` block**
104
+
105
+ Replace `app.py:127-182` with:
106
+
107
+ ```python
108
+ _CUSTOM_CSS = """
109
+ /* Hide Gradio's top tab strip — sidebar drives selection. */
110
+ .aio-tabs > .tab-nav,
111
+ .aio-tabs > div:first-child[role="tablist"],
112
+ .aio-tabs > div:first-child:has([role="tab"]) {
113
+ position: absolute !important;
114
+ left: -99999px !important;
115
+ top: -99999px !important;
116
+ height: 0 !important;
117
+ overflow: hidden !important;
118
+ visibility: visible !important;
119
+ pointer-events: auto !important;
120
+ }
121
+
122
+ /* === Header === */
123
+ .aio-header {
124
+ display: flex;
125
+ align-items: center;
126
+ gap: 12px;
127
+ padding: 11px 18px;
128
+ border-bottom: 1px solid #262C35;
129
+ background: #12161B;
130
+ }
131
+ .aio-ham-toggle { display: none; } /* hidden checkbox drives drawer state */
132
+ .aio-ham-label {
133
+ width: 32px; height: 32px;
134
+ border: 1px solid #262C35;
135
+ border-radius: 5px;
136
+ color: #7C8693;
137
+ cursor: pointer;
138
+ display: flex; align-items: center; justify-content: center;
139
+ font-size: 18px; font-weight: 300;
140
+ user-select: none;
141
+ }
142
+ .aio-ham-label:hover { color: #E0A458; border-color: #E0A458; }
143
+ .aio-title {
144
+ font-size: 15px; font-weight: 600; letter-spacing: -0.01em;
145
+ color: #E6E8EB;
146
+ }
147
+ .aio-title .accent { color: #E0A458; }
148
+ .aio-mode-tag {
149
+ margin-left: auto;
150
+ padding: 4px 9px;
151
+ font-family: 'IBM Plex Mono', ui-monospace, monospace;
152
+ font-size: 11px; font-weight: 500; letter-spacing: 0.04em;
153
+ color: #E0A458;
154
+ border: 1px solid #E0A458;
155
+ border-radius: 4px;
156
+ }
157
+
158
+ /* === Drawer === */
159
+ .aio-shell { position: relative; }
160
+ .aio-drawer {
161
+ width: 220px;
162
+ border-right: 1px solid #262C35;
163
+ background: #12161B;
164
+ padding: 14px 10px !important;
165
+ flex-shrink: 0;
166
+ transition: transform 0.2s ease, width 0.2s ease;
167
+ }
168
+ .aio-drawer-heading {
169
+ font-family: 'IBM Plex Mono', ui-monospace, monospace;
170
+ font-size: 10px; text-transform: uppercase; letter-spacing: 0.07em;
171
+ color: #7C8693;
172
+ padding: 6px 8px 4px !important;
173
+ margin: 0 !important;
174
+ }
175
+
176
+ /* Mode buttons */
177
+ .aio-mode-btn { width: 100%; text-align: left; margin: 2px 0 !important; }
178
+ .aio-mode-btn-active {
179
+ background: #1A1F26 !important;
180
+ color: #E0A458 !important;
181
+ border-left: 3px solid #E0A458 !important;
182
+ }
183
+
184
+ /* Model status / settings panels */
185
+ .aio-model-badge {
186
+ padding: 9px 11px;
187
+ border-radius: 6px;
188
+ background: #1A1F26;
189
+ border: 1px solid #262C35;
190
+ font-size: 11.5px;
191
+ font-family: 'IBM Plex Mono', ui-monospace, monospace;
192
+ color: #7C8693;
193
+ }
194
+
195
+ /* === Status banner === */
196
+ .status-card {
197
+ padding: 12px 16px;
198
+ border-radius: 6px;
199
+ background: #1A1F26;
200
+ border: 1px solid #262C35;
201
+ }
202
+ .status-row { display: flex; gap: 14px; align-items: center; margin-bottom: 8px; flex-wrap: wrap; }
203
+ .status-stage { font-weight: 600; color: #E0A458; }
204
+ .status-meta { font-size: 12px; color: #7C8693; font-family: 'IBM Plex Mono', monospace; }
205
+ .status-bar { height: 4px; background: #262C35; border-radius: 99px; overflow: hidden; }
206
+ .status-fill { height: 100%; background: #E0A458; transition: width .3s; }
207
+ .status-mem { font-size: 11px; color: #7C8693; margin-top: 6px; font-family: 'IBM Plex Mono', monospace; }
208
+ .status-error {
209
+ background: #3A1E20 !important;
210
+ border-color: #F4A6A8 !important;
211
+ color: #F4A6A8 !important;
212
+ }
213
+ .status-error .status-stage { color: #F4A6A8; }
214
+
215
+ /* === Drawer toggle behavior at the desktop boundary === */
216
+ @media (max-width: 1023px) {
217
+ .aio-ham-label { display: flex; }
218
+ .aio-drawer {
219
+ position: absolute;
220
+ top: 0; left: 0; bottom: 0;
221
+ z-index: 10;
222
+ box-shadow: 4px 0 24px rgba(0,0,0,0.6);
223
+ transform: translateX(-100%);
224
+ max-width: 80vw;
225
+ }
226
+ /* checkbox at #aio-ham-toggle is the only sibling pattern Gradio
227
+ lets us reach without JS — when checked, slide drawer in. */
228
+ body:has(.aio-ham-toggle:checked) .aio-drawer { transform: translateX(0); }
229
+ body:has(.aio-ham-toggle:checked) .aio-shell::before {
230
+ content: ""; position: absolute; inset: 0;
231
+ background: rgba(0,0,0,0.55); z-index: 9;
232
+ }
233
+
234
+ /* Mobile sub-tweaks */
235
+ .aio-mode-btn { font-size: 13px !important; padding: 7px 10px !important; }
236
+ .aio-body [class*="row"] { flex-wrap: wrap !important; }
237
+ .aio-body [class*="row"] > div { flex: 1 1 100% !important; min-width: 0 !important; }
238
+ }
239
+
240
+ @media (min-width: 1024px) {
241
+ .aio-ham-label { display: none; }
242
+ }
243
+ """
244
+ ```
245
+
246
+ - [ ] **Step 2: Verify the CSS doesn't break the import**
247
+
248
+ Run: `python -c "import app; print(len(app._CUSTOM_CSS), 'chars CSS')"`
249
+ Expected: a number (around 4000), no traceback.
250
+
251
+ - [ ] **Step 3: Commit**
252
+
253
+ ```bash
254
+ git add app.py
255
+ git commit -m "feat(ui): rewrite _CUSTOM_CSS for Topaz palette + drawer mechanics"
256
+ ```
257
+
258
+ ---
259
+
260
+ ### Task 3 — Add header markup + drawer wrapper to `build_app`
261
+
262
+ **Files:** Modify `app.py:190-243` (the `build_app` function body)
263
+
264
+ - [ ] **Step 1: Replace the markup section**
265
+
266
+ Find this block in `app.py` (currently around 190-220):
267
+
268
+ ```python
269
+ def build_app() -> gr.Blocks:
270
+ with gr.Blocks(theme=_TOPAZ_THEME, title="LTX 2.3 Studio", css=_CUSTOM_CSS) as app:
271
+ gr.Markdown("# ⚡ LTX 2.3 All-in-One")
272
+
273
+ with gr.Row(elem_classes=["aio-shell"]):
274
+ # Sidebar
275
+ with gr.Column(scale=1, min_width=200, elem_classes=["aio-sidebar"]):
276
+ gr.Markdown("**Modes**", elem_classes=["aio-sidebar-heading"])
277
+ with gr.Column(elem_classes=["aio-mode-btn-row"]):
278
+ mode_buttons = {
279
+ name: gr.Button(
280
+ f"{m.icon} {m.label}",
281
+ elem_classes=["aio-mode-btn"],
282
+ variant="secondary",
283
+ )
284
+ for name, m in modes.MODE_REGISTRY.items()
285
+ }
286
+ gr.Markdown("**Models**", elem_classes=["aio-sidebar-heading"])
287
+ model_status = gr.HTML(_render_model_status_idle(), elem_id="aio-model-status")
288
+ refresh_btn = gr.Button("Refresh", size="sm", variant="secondary")
289
+ unload_btn = gr.Button("Unload all models", size="sm", variant="secondary")
290
+ gr.Markdown("**Settings**", elem_classes=["aio-sidebar-heading"])
291
+ gr.Markdown(
292
+ "Output: `comfyui/output/LTX2.3/`<br>"
293
+ "Set `LTX23_AIO_VRAM=lowvram|normalvram|highvram` to override the auto-detected VRAM tier.",
294
+ elem_classes=["aio-model-badge"],
295
+ )
296
+
297
+ # Body
298
+ with gr.Column(scale=4, elem_classes=["aio-body"]):
299
+ handles, tabs_component = _render_mode_panels()
300
+ ```
301
+
302
+ Replace with:
303
+
304
+ ```python
305
+ def build_app() -> gr.Blocks:
306
+ with gr.Blocks(theme=_TOPAZ_THEME, title="LTX 2.3 Studio", css=_CUSTOM_CSS) as app:
307
+ # Header: hamburger checkbox (drives drawer via :checked + :has() in CSS),
308
+ # title, current-mode tag.
309
+ gr.HTML(
310
+ '<div class="aio-header">'
311
+ ' <input type="checkbox" id="aio-ham-toggle" class="aio-ham-toggle">'
312
+ ' <label for="aio-ham-toggle" class="aio-ham-label">≡</label>'
313
+ ' <span class="aio-title">LTX 2.3 <span class="accent">Studio</span></span>'
314
+ ' <span class="aio-mode-tag" id="aio-mode-tag">T2V</span>'
315
+ '</div>'
316
+ )
317
+
318
+ with gr.Row(elem_classes=["aio-shell"]):
319
+ # Drawer (drawer behaves as fixed sidebar ≥1024 px;
320
+ # absolute-positioned overlay <1024 px — see _CUSTOM_CSS).
321
+ with gr.Column(scale=1, min_width=200, elem_classes=["aio-drawer"]):
322
+ gr.Markdown("Modes", elem_classes=["aio-drawer-heading"])
323
+ mode_buttons = {
324
+ name: gr.Button(
325
+ f"{m.icon} {m.label}",
326
+ elem_classes=["aio-mode-btn"],
327
+ variant="secondary",
328
+ )
329
+ for name, m in modes.MODE_REGISTRY.items()
330
+ }
331
+ gr.Markdown("Models", elem_classes=["aio-drawer-heading"])
332
+ model_status = gr.HTML(_render_model_status_idle(), elem_id="aio-model-status")
333
+ refresh_btn = gr.Button("Refresh", size="sm", variant="secondary")
334
+ unload_btn = gr.Button("Unload all models", size="sm", variant="secondary")
335
+ gr.Markdown("Settings", elem_classes=["aio-drawer-heading"])
336
+ gr.Markdown(
337
+ "Output: `comfyui/output/LTX2.3/`<br>"
338
+ "Set `LTX23_AIO_VRAM=lowvram|normalvram|highvram` to override "
339
+ "the auto-detected VRAM tier.",
340
+ elem_classes=["aio-model-badge"],
341
+ )
342
+
343
+ # Body — unchanged, still hosts the 6 mode tabs.
344
+ with gr.Column(scale=4, elem_classes=["aio-body"]):
345
+ handles, tabs_component = _render_mode_panels()
346
+ ```
347
+
348
+ - [ ] **Step 2: Smoke-test build_app produces a Blocks**
349
+
350
+ Run:
351
+ ```bash
352
+ python -c "import app; b = app.build_app(); print(type(b).__name__)"
353
+ ```
354
+ Expected: `Blocks`, no traceback.
355
+
356
+ - [ ] **Step 3: Commit**
357
+
358
+ ```bash
359
+ git add app.py
360
+ git commit -m "feat(ui): drawer markup + header (hamburger / title / mode tag)"
361
+ ```
362
+
363
+ ---
364
+
365
+ ### Task 4 — Wire active-mode tag updates from sidebar clicks
366
+
367
+ **Files:** Modify `app.py:232-237` (the existing mode-button click loop)
368
+
369
+ - [ ] **Step 1: Update the click handler to also push the new mode tag**
370
+
371
+ Current code at `app.py:232-237`:
372
+
373
+ ```python
374
+ for name, btn in mode_buttons.items():
375
+ btn.click(
376
+ fn=lambda mode_id=name: gr.Tabs(selected=mode_id),
377
+ inputs=None,
378
+ outputs=[tabs_component],
379
+ )
380
+ ```
381
+
382
+ Replace with:
383
+
384
+ ```python
385
+ # JS to update the header mode tag without a server round-trip.
386
+ # Each mode button injects a tiny on-click that rewrites #aio-mode-tag.
387
+ _MODE_TAG_BY_NAME = {
388
+ "t2v": "T2V", "a2v": "A2V", "i2v": "I2V",
389
+ "lipsync": "LIPSYNC", "keyframe": "KEY", "style": "STYLE",
390
+ }
391
+ for name, btn in mode_buttons.items():
392
+ tag = _MODE_TAG_BY_NAME.get(name, name.upper())
393
+ btn.click(
394
+ fn=lambda mode_id=name: gr.Tabs(selected=mode_id),
395
+ inputs=None,
396
+ outputs=[tabs_component],
397
+ js=f"() => {{ "
398
+ f"const el = document.getElementById('aio-mode-tag'); "
399
+ f"if (el) el.textContent = {tag!r}; "
400
+ f"/* also collapse drawer on mobile after pick */ "
401
+ f"if (window.matchMedia('(max-width: 1023px)').matches) {{ "
402
+ f" const t = document.getElementById('aio-ham-toggle'); "
403
+ f" if (t) t.checked = false; "
404
+ f"}} return []; }}",
405
+ )
406
+ ```
407
+
408
+ - [ ] **Step 2: Smoke-test**
409
+
410
+ Run:
411
+ ```bash
412
+ python -c "import app; b = app.build_app(); print('ok')"
413
+ ```
414
+ Expected: `ok`, no traceback.
415
+
416
+ - [ ] **Step 3: Commit**
417
+
418
+ ```bash
419
+ git add app.py
420
+ git commit -m "feat(ui): mode tag updates + auto-close drawer on mobile select"
421
+ ```
422
+
423
+ ---
424
+
425
+ ### Task 5 — Visual smoke test in browser
426
+
427
+ **Files:** None (test-only).
428
+
429
+ - [ ] **Step 1: Launch the app**
430
+
431
+ Run in one terminal:
432
+ ```bash
433
+ cd /Users/techfreakworm/Projects/llm/ltx2.3-AIO-generator
434
+ source .venv/bin/activate
435
+ python app.py
436
+ ```
437
+ Expected output: `Running on local URL: http://127.0.0.1:7860`
438
+
439
+ - [ ] **Step 2: Open browser at desktop width**
440
+
441
+ Open Chrome at `http://127.0.0.1:7860`. Resize window to 1280 px wide.
442
+
443
+ Verify:
444
+ - Header: ≡ button NOT visible (hidden on ≥1024 px), title "LTX 2.3 **Studio**" with "Studio" in amber, mode tag "T2V" in amber border on right
445
+ - Drawer (220 px) visible on left, "Modes" heading in IBM Plex Mono uppercase, 6 mode buttons stacked
446
+ - Active mode (T2V by default) has amber left border + amber text + slate-2 bg
447
+ - Body pane: form fields use slate background, amber Generate button at the bottom
448
+ - Click each mode button → mode tag in header updates, body switches to that mode's form
449
+
450
+ - [ ] **Step 3: Resize to tablet (1023 px)**
451
+
452
+ Drag Chrome to 900 px wide.
453
+
454
+ Verify:
455
+ - ≡ button NOW visible in header
456
+ - Drawer hidden (off-screen left)
457
+ - Click ≡ → drawer slides in, dark scrim covers body
458
+ - Click a mode button → drawer auto-closes, body switches mode
459
+ - Click ≡ again → drawer hides
460
+
461
+ - [ ] **Step 4: Resize to phone (380 px)**
462
+
463
+ Use Chrome devtools → device toolbar → iPhone 12.
464
+
465
+ Verify:
466
+ - Same as tablet, but drawer width capped at 80 vw
467
+ - Form fields are full-width (sliders, inputs)
468
+ - Generate button readable, no horizontal scrollbar
469
+
470
+ - [ ] **Step 5: Hit Generate (T2V, default settings)**
471
+
472
+ Type a short prompt, click Generate.
473
+
474
+ Verify:
475
+ - Status banner appears with `Stage 1 · Encode prompt` text in amber
476
+ - Progress bar fills with amber
477
+ - After ~30s on local MPS (or longer if no model cache), video appears in output
478
+ - Banner switches to `Done` or disappears
479
+
480
+ - [ ] **Step 6: Trigger an error**
481
+
482
+ Set width to 0 in slider (or click Generate with empty prompt) — anything that produces an error.
483
+
484
+ Verify:
485
+ - Error banner uses `#3A1E20` background + `#F4A6A8` text
486
+ - Stage label and meta text both readable
487
+
488
+ - [ ] **Step 7: Stop the dev server**
489
+
490
+ Ctrl+C in the terminal running `python app.py`.
491
+
492
+ - [ ] **Step 8: Commit screenshot/notes (optional)**
493
+
494
+ If anything didn't match the spec, file a follow-up; otherwise no commit needed.
495
+
496
+ ---
497
+
498
+ ### Task 6 — Push to GitHub + HF Space
499
+
500
+ **Files:** None — pushing only.
501
+
502
+ - [ ] **Step 1: Sync to both remotes**
503
+
504
+ ```bash
505
+ cd /Users/techfreakworm/Projects/llm/ltx2.3-AIO-generator
506
+ git push origin master
507
+ HF_TOKEN=$(hf auth token 2>/dev/null) git push "https://techfreakworm:${HF_TOKEN}@huggingface.co/spaces/techfreakworm/LTX2.3-Studio" master:main
508
+ ```
509
+
510
+ - [ ] **Step 2: Verify Space accepts the push**
511
+
512
+ Wait ~30 s, then:
513
+ ```bash
514
+ HF_TOKEN=$(hf auth token 2>/dev/null) curl -s -H "Authorization: Bearer ${HF_TOKEN}" \
515
+ "https://huggingface.co/api/spaces/techfreakworm/LTX2.3-Studio" \
516
+ | python3 -c "import sys, json; d=json.load(sys.stdin); print('stage:', d['runtime']['stage'], 'sha:', d['sha'][:8])"
517
+ ```
518
+ Expected: `stage: BUILDING` or `RUNNING_BUILDING`, sha matches local HEAD.
519
+
520
+ - [ ] **Step 3: Wait for build, then visual-spot-check on Spaces**
521
+
522
+ After ~5 min, open `https://techfreakworm-ltx2-3-studio.hf.space` in Chrome at 1280 px. Verify the Topaz palette + drawer rendered correctly. Resize to phone width and verify hamburger toggle.
523
+
524
+ ---
525
+
526
+ ## Out of scope reminder
527
+
528
+ These are explicitly NOT touched by this plan (per spec):
529
+ - Form layout inside each mode tab (typography updates flow via theme cascade only)
530
+ - Model status / settings panel content
531
+ - Mode set, generate flow, progress events
532
+ - Any CUDA / MPS / Spaces logic
533
+ - Custom LoRA UI
534
+
535
+ If any of those appear visually broken after this plan lands, file separately — they're spec-isolated and a different change.