返回 html-video
index.html
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>html-video · Studio</title>
7 <link rel="stylesheet" href="/tokens.css?v=0.9-hyperframes" />
8 <link rel="preconnect" href="https://fonts.googleapis.com" />
9 <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
10 <link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&family=JetBrains+Mono:wght@400;500;600&family=Source+Serif+4:opsz,wght@8..60,400;8..60,500;8..60,600&display=swap" rel="stylesheet" />
11 <script src="https://cdn.jsdelivr.net/npm/marked@14.1.4/marked.min.js"></script>
12 <script src="https://cdn.jsdelivr.net/npm/dompurify@3.1.7/dist/purify.min.js"></script>
13 <style>
14 * { box-sizing: border-box; margin: 0; padding: 0; }
15 html, body { background: var(--bg); color: var(--text); font-family: var(--font-sans);
16 height: 100vh; overflow: hidden; font-size: 13.5px; line-height: 1.5; }
17 button, input, textarea, select { font-family: inherit; font-size: inherit; }
18 a { color: var(--accent); }
19
20 .app { display: grid; grid-template-rows: 56px 1fr; height: 100vh; }
21
22 /* ===== Top toolbar ===== */
23 .toolbar { background: var(--bg-panel); border-bottom: 1px solid var(--border);
24 display: flex; align-items: center; gap: 18px; padding: 0 22px; min-width: 0; }
25 .toolbar .brand { display: flex; align-items: center; gap: 10px; padding-right: 14px;
26 border-right: 1px solid var(--border); margin-right: 6px; }
27 .toolbar .brand .logo { width: 28px; height: 28px; border-radius: var(--radius-sm);
28 background: var(--accent); color: var(--accent-fg); display: grid; place-items: center;
29 font-weight: 700; font-family: var(--font-serif); font-size: 16px; }
30 .toolbar .brand h1 { font-size: 14px; font-weight: 600; letter-spacing: -.005em; }
31 .toolbar .brand .v { font-size: 10px; color: var(--text-muted); font-family: var(--font-mono); }
32 .toolbar .name-input { background: transparent; border: 0; color: var(--text);
33 font-size: 14px; font-weight: 500; padding: 5px 8px; border-radius: var(--radius-sm);
34 width: 220px; outline: none; transition: background .12s; }
35 .toolbar .name-input:hover { background: var(--bg-subtle); }
36
37 .toolbar-section { display: flex; align-items: center; gap: 8px; }
38 .sec-label { font-size: 10px; letter-spacing: .12em; text-transform: uppercase;
39 color: var(--text-muted); font-family: var(--font-mono); }
40 .toolbar .pick-btn { background: var(--bg); border: 1px solid var(--border);
41 color: var(--text); padding: 6px 12px; border-radius: var(--radius-sm);
42 font-size: 12.5px; cursor: pointer; display: flex; align-items: center; gap: 6px;
43 min-width: 200px; transition: border-color .12s; }
44 .toolbar .pick-btn:hover:not(:disabled) { border-color: var(--accent); }
45 .toolbar .pick-btn:disabled { opacity: .4; cursor: not-allowed; }
46 .toolbar .pick-btn .arrow { color: var(--text-muted); }
47 .toolbar .pick-btn.empty .label { color: var(--text-muted); }
48 /* Agent switcher pill (top bar) */
49 .agent-switch { position: relative; }
50 .toolbar .pick-btn.agent-pill { min-width: 0; }
51 .toolbar-section.model-switch { display: flex; align-items: center; gap: 6px; }
52 /* [hidden] sets display:none, but the flex rule above would override it —
53 restore it so the picker truly hides for non-AMR agents. */
54 .toolbar-section.model-switch[hidden] { display: none; }
55 .model-select { font-size: 12px; padding: 5px 8px; border-radius: var(--radius-sm);
56 background: var(--bg); border: 1px solid var(--border); color: var(--text); cursor: pointer;
57 max-width: 180px; }
58 .model-select:focus { outline: none; border-color: var(--accent); }
59 .agent-pill .agent-dot { width: 7px; height: 7px; border-radius: 50%; flex: 0 0 auto;
60 background: var(--text-faint); }
61 .agent-pill .agent-dot.ok { background: var(--green); }
62 .agent-pill .agent-dot.missing { background: var(--text-faint); }
63 .agent-pill .agent-pill-logo { display: inline-flex; }
64 .agent-pill .agent-pill-logo img { width: 16px; height: 16px; object-fit: contain;
65 display: block; }
66 .agent-menu { position: absolute; top: calc(100% + 6px); left: 0; z-index: 60;
67 min-width: 240px; background: var(--bg-panel); border: 1px solid var(--border);
68 border-radius: var(--radius); box-shadow: var(--shadow-md); padding: 6px;
69 display: flex; flex-direction: column; gap: 2px; }
70 .agent-menu[hidden] { display: none; }
71 .agent-menu .agent-menu-item { display: flex; align-items: center; gap: 9px;
72 padding: 8px 10px; border-radius: var(--radius-sm); cursor: pointer; border: 0;
73 background: transparent; color: var(--text); font: inherit; font-size: 12.5px;
74 text-align: left; width: 100%; }
75 .agent-menu .agent-menu-item:hover:not(.is-unselectable) { background: var(--bg-subtle); }
76 /* Unselectable rows (missing CLI / needs login) are dimmed but still let the
77 inner Sign-in button receive clicks — so NOT the native :disabled. */
78 .agent-menu .agent-menu-item.is-unselectable { opacity: .5; cursor: not-allowed; }
79 .agent-menu .agent-menu-item.is-unselectable .mi-login { opacity: 1; cursor: pointer; }
80 .agent-menu .agent-menu-item.current { background: var(--accent-soft); }
81 .agent-menu .agent-menu-item .mi-dot { width: 7px; height: 7px; border-radius: 50%;
82 flex: 0 0 auto; background: var(--text-faint); }
83 .agent-menu .agent-menu-item .mi-dot.ok { background: var(--green); }
84 .agent-menu .agent-menu-item .mi-logo img { width: 16px; height: 16px;
85 object-fit: contain; display: block; }
86 .agent-menu .agent-menu-item .mi-logo { width: 16px; display: inline-flex; flex: 0 0 auto; }
87 .agent-menu .agent-menu-item .mi-name { flex: 1; min-width: 0; white-space: nowrap;
88 overflow: hidden; text-overflow: ellipsis; }
89 .agent-menu .agent-menu-item .mi-tag { font-size: 10px; color: var(--text-muted);
90 font-family: var(--font-mono); }
91 .agent-menu .agent-menu-item .mi-star { font-size: 12px; color: #f5a623; flex: 0 0 auto;
92 line-height: 1; text-shadow: 0 0 1px rgba(245,166,35,.4); }
93 /* "Sign in" button sits inside a disabled agent item (AMR-needs-login) but
94 must stay clickable + full-opacity. */
95 .agent-menu .agent-menu-item .mi-login { font-size: 11px; font-weight: 600; padding: 4px 13px;
96 border-radius: 7px; background: var(--accent); color: var(--accent-fg); cursor: pointer;
97 display: inline-flex; align-items: center; gap: 4px; pointer-events: auto; opacity: 1;
98 flex: 0 0 auto; user-select: none; border: 0; transition: filter .12s, transform .08s;
99 box-shadow: 0 1px 3px rgba(0,0,0,0.18); }
100 .agent-menu .agent-menu-item .mi-login::before { content: '→'; font-weight: 700; }
101 .agent-menu .agent-menu-item .mi-login:hover { filter: brightness(1.08); }
102 .agent-menu .agent-menu-item .mi-login:active { transform: translateY(1px); }
103 .agent-menu .agent-menu-item .mi-login.busy { opacity: .6; cursor: wait; box-shadow: none; }
104 /* The needs-login row itself shouldn't look fully greyed — the action is live */
105 .agent-menu .agent-menu-item.is-unselectable:has(.mi-login) { opacity: .85; cursor: default; }
106 .toolbar select { background: var(--bg); border: 1px solid var(--border);
107 color: var(--text); padding: 6px 10px; border-radius: var(--radius-sm);
108 font-size: 12.5px; cursor: pointer; min-width: 160px; }
109 .toolbar select:focus { outline: none; border-color: var(--accent); }
110 .toolbar select:disabled { opacity: .4; cursor: not-allowed; }
111 .toolbar .agent-status { font-size: 10px; padding: 2px 8px; border-radius: var(--radius-pill);
112 font-family: var(--font-mono); }
113 .toolbar .agent-status.connected { background: var(--green-bg); color: var(--green); }
114 .toolbar .agent-status.missing { background: var(--bg-subtle); color: var(--text-muted); }
115 .toolbar .grow { flex: 1; }
116 .toolbar .export-btn { background: var(--accent); color: var(--accent-fg); border: 0;
117 padding: 8px 18px; border-radius: var(--radius-sm); font-weight: 500; cursor: pointer;
118 font-size: 12.5px; transition: background .15s;
119 box-shadow: inset 0 -1px 0 rgba(0,0,0,.08), 0 4px 10px rgba(201, 100, 66, .18); }
120 .toolbar .export-btn:hover:not(:disabled) { background: var(--accent-hover); }
121 .toolbar .export-btn:disabled { opacity: .35; cursor: not-allowed; box-shadow: none; }
122 /* Top-bar external links + settings gear — sit on the right, before the gear. */
123 .toolbar .toolbar-links { gap: 2px; margin-right: 4px; }
124 .toolbar .tb-link { display: inline-flex; align-items: center; gap: 6px;
125 padding: 5px 9px; border-radius: var(--radius-sm); color: var(--text-muted);
126 font-size: 11.5px; font-family: var(--font-mono); text-decoration: none;
127 transition: background .12s, color .12s; }
128 .toolbar .tb-link:hover { background: var(--bg-subtle); color: var(--text); }
129 .toolbar .tb-link svg { flex: 0 0 auto; }
130 .toolbar .tb-link-label { white-space: nowrap; }
131 /* Discord stays icon-only no matter the width. */
132 .toolbar .tb-link.tb-link-icon-only { padding: 6px; }
133 .toolbar .tb-link.tb-link-icon-only .tb-link-label { display: none; }
134 @media (max-width: 1100px) {
135 .toolbar .tb-link-label { display: none; }
136 .toolbar .tb-link { padding: 6px; }
137 }
138 .toolbar .icon-btn-toolbar { background: transparent; border: 1px solid transparent;
139 color: var(--text-muted); cursor: pointer; padding: 6px;
140 border-radius: var(--radius-sm); display: inline-flex; align-items: center;
141 justify-content: center; transition: background .12s, color .12s, border-color .12s; }
142 .toolbar .icon-btn-toolbar:hover { background: var(--bg-subtle);
143 color: var(--text); border-color: var(--border); }
144
145 /* Settings modal */
146 .settings-modal .modal.settings { width: min(880px, 92vw); height: min(620px, 88vh);
147 padding: 0; display: flex; flex-direction: column; gap: 0; }
148 .settings-head { display: flex; align-items: center; justify-content: space-between;
149 padding: 16px 20px; border-bottom: 1px solid var(--border); }
150 .settings-head h2 { margin: 0; font-size: 15px; }
151 .settings-head .modal-close { background: transparent; border: 0; color: var(--text-muted);
152 font-size: 16px; cursor: pointer; padding: 4px 8px; border-radius: var(--radius-sm); }
153 .settings-head .modal-close:hover { color: var(--text); background: var(--bg-subtle); }
154 .settings-body { flex: 1; display: grid; grid-template-columns: 200px 1fr;
155 overflow: hidden; }
156 .settings-nav { padding: 12px 8px; border-right: 1px solid var(--border);
157 background: var(--bg-panel); display: flex; flex-direction: column; gap: 2px;
158 overflow-y: auto; }
159 .settings-nav-item { display: flex; align-items: center; gap: 10px;
160 background: transparent; border: 0; color: var(--text-muted);
161 padding: 8px 10px; border-radius: var(--radius-sm); cursor: pointer;
162 text-align: left; font-size: 12.5px; font-family: inherit;
163 transition: background .12s, color .12s; }
164 .settings-nav-item:hover { background: var(--bg-subtle); color: var(--text); }
165 .settings-nav-item.active { background: var(--accent-soft); color: var(--accent);
166 font-weight: 500; }
167 .settings-panel { padding: 22px 26px; overflow-y: auto; }
168
169 /* Settings: Agent panel */
170 .settings-panel h3 { margin: 0 0 4px; font-size: 16px; font-weight: 500; }
171 .settings-panel .panel-sub { color: var(--text-muted); font-size: 12.5px;
172 margin-bottom: 18px; }
173 .settings-mode-tabs { display: grid; grid-template-columns: 1fr 1fr;
174 background: var(--bg-subtle); border-radius: var(--radius-sm); padding: 3px;
175 gap: 3px; margin-bottom: 16px; }
176 .settings-mode-tabs button { background: transparent; border: 0; color: var(--text-muted);
177 padding: 7px 0; border-radius: calc(var(--radius-sm) - 2px); cursor: pointer;
178 font-size: 12.5px; font-family: inherit; transition: background .12s, color .12s; }
179 .settings-mode-tabs button.active { background: var(--bg-panel); color: var(--text);
180 box-shadow: 0 1px 2px rgba(0,0,0,.04); }
181 .agent-card { display: grid; grid-template-columns: 36px 1fr auto;
182 gap: 12px; align-items: center; padding: 12px 14px;
183 border: 1px solid var(--border); border-radius: var(--radius);
184 background: var(--bg-panel); margin-bottom: 8px; transition: border-color .12s; }
185 .agent-card:hover { border-color: var(--accent); }
186 .agent-card.selected { border-color: var(--accent);
187 box-shadow: 0 0 0 2px var(--accent-soft); }
188 .agent-card .agent-icon { width: 36px; height: 36px; border-radius: var(--radius-sm);
189 display: grid; place-items: center; background: var(--bg-subtle); font-size: 18px; }
190 .agent-card .agent-icon .agent-logo { width: 22px; height: 22px; object-fit: contain;
191 display: block; }
192 .agent-card .agent-meta .agent-name { font-size: 13.5px; font-weight: 500; color: var(--text); }
193 .agent-card .agent-meta .agent-desc { font-size: 11.5px; color: var(--text-muted); margin-top: 2px; }
194 .agent-card .agent-meta .agent-version { font-size: 11px; color: var(--text-faint);
195 font-family: var(--font-mono); margin-top: 4px; }
196 .agent-card .agent-actions { display: flex; gap: 6px; align-items: center; }
197 .agent-card .agent-actions button { background: var(--bg-subtle); color: var(--text-muted);
198 border: 1px solid var(--border); padding: 5px 10px; border-radius: var(--radius-sm);
199 cursor: pointer; font-size: 11.5px; transition: border-color .12s, color .12s; }
200 .agent-card .agent-actions button:hover { border-color: var(--accent); color: var(--text); }
201 .agent-card .agent-status-dot { display: inline-block; width: 6px; height: 6px;
202 border-radius: 50%; margin-right: 4px; }
203 .agent-card .agent-status-dot.ok { background: var(--green, #4ade80); }
204 .agent-card .agent-status-dot.missing { background: var(--text-faint); }
205 .settings-panel .agent-test-result { font-size: 11px; color: var(--text-muted);
206 font-family: var(--font-mono); margin-top: 6px; padding: 6px 10px;
207 background: var(--bg-subtle); border-radius: var(--radius-sm); }
208 .settings-panel .agent-test-result.ok { color: var(--green, #4ade80); }
209 .settings-panel .agent-test-result.error { color: rgb(220, 60, 60); }
210 .audio-config { display: flex; flex-direction: column; gap: 14px; margin-top: 8px; }
211 .audio-status { font-size: 12.5px; color: var(--text-muted); display: flex;
212 align-items: center; gap: 8px; }
213 .audio-field { display: flex; flex-direction: column; gap: 5px; }
214 .audio-field > span { font-size: 11.5px; color: var(--text-muted); }
215 .audio-field input { background: var(--bg); border: 1px solid var(--border);
216 color: var(--text); border-radius: var(--radius-sm); padding: 9px 12px;
217 font: inherit; font-size: 12.5px; font-family: var(--font-mono); }
218 .audio-field input:focus { outline: none; border-color: var(--accent); }
219 .audio-actions { display: flex; align-items: center; gap: 10px; }
220 .audio-actions button { padding: 7px 16px; border-radius: var(--radius-sm);
221 cursor: pointer; font-size: 12.5px; border: 1px solid var(--border);
222 background: var(--bg); color: var(--text); }
223 .audio-actions .audio-save { border: 0; font-weight: 500; }
224 .audio-actions .audio-save-state { font-size: 11.5px; color: var(--text-muted);
225 font-family: var(--font-mono); }
226
227 /* Settings: Language panel */
228 .lang-options { display: grid; grid-template-columns: 1fr 1fr; gap: 10px; }
229 .lang-options button { background: var(--bg-panel); border: 1px solid var(--border);
230 color: var(--text); padding: 14px 16px; border-radius: var(--radius);
231 cursor: pointer; font-family: inherit; font-size: 13.5px;
232 text-align: left; transition: border-color .12s; }
233 .lang-options button:hover { border-color: var(--accent); }
234 .lang-options button.active { border-color: var(--accent);
235 box-shadow: 0 0 0 2px var(--accent-soft); }
236 .lang-options button .lang-name { font-weight: 500; margin-bottom: 4px; }
237 .lang-options button .lang-sub { font-size: 11.5px; color: var(--text-muted);
238 font-family: var(--font-mono); }
239
240 /* Settings: About */
241 .about-block { display: flex; flex-direction: column; gap: 10px; }
242 .about-block .about-line { display: grid;
243 grid-template-columns: 100px 1fr; align-items: baseline; gap: 12px;
244 padding: 8px 0; border-bottom: 1px dashed var(--border); }
245 .about-block .about-line .k { font-size: 11.5px; color: var(--text-muted);
246 font-family: var(--font-mono); }
247 .about-block .about-line .v { font-size: 12.5px; color: var(--text); }
248 .about-block .about-line .v a { color: var(--accent); text-decoration: none; }
249 .about-block .about-line .v a:hover { text-decoration: underline; }
250
251 /* ===== Body: 4 columns ===== */
252 /* sidebar / CHAT / PREVIEW (centre, biggest) / text-fields (right, collapsible) */
253 .body { display: grid;
254 grid-template-columns:
255 var(--sidebar-w, 200px)
256 minmax(320px, 0.9fr)
257 minmax(420px, 1.4fr)
258 var(--textfields-w, 280px);
259 grid-template-rows: 100%; /* keep children bounded by viewport — composer must stay visible */
260 overflow: hidden;
261 transition: grid-template-columns .18s ease; }
262 .body > * { min-height: 0; min-width: 0; }
263 body.sidebar-collapsed .body { --sidebar-w: 40px; }
264 body.textfields-collapsed .body { --textfields-w: 40px; }
265
266 /* ===== Sidebar (Projects) ===== */
267 .sidebar { background: var(--bg-panel); border-right: 1px solid var(--border);
268 display: flex; flex-direction: column; min-width: 0; overflow: hidden; }
269 .sidebar-head { display: flex; align-items: center; padding: 12px 14px;
270 border-bottom: 1px solid var(--border); gap: 6px; }
271 .sidebar-head h2 { font-size: 11px; letter-spacing: .12em; text-transform: uppercase;
272 color: var(--text-muted); font-family: var(--font-mono); flex: 1;
273 white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
274 .sidebar-toggle { background: transparent; border: 0; color: var(--text-muted);
275 cursor: pointer; padding: 4px 6px; border-radius: var(--radius-sm); font-size: 12px;
276 line-height: 1; }
277 .sidebar-toggle:hover { color: var(--text); background: var(--bg-subtle); }
278 body.sidebar-collapsed .sidebar-head h2,
279 body.sidebar-collapsed .new-project { display: none; }
280 body.sidebar-collapsed .project-list { display: none; }
281 body.sidebar-collapsed .sidebar-toggle { transform: rotate(180deg); }
282 .new-project { background: var(--accent); color: var(--accent-fg); border: 0;
283 padding: 5px 10px; border-radius: var(--radius-sm); font-size: 12px; cursor: pointer;
284 font-weight: 500; }
285 .new-project:hover { background: var(--accent-hover); }
286 .project-list { flex: 1; overflow-y: auto; padding: 8px 6px; }
287 .project-row { padding: 8px 10px; cursor: pointer; border-radius: var(--radius-sm);
288 transition: background .12s; margin-bottom: 1px;
289 display: grid; grid-template-columns: 1fr auto; grid-template-areas:
290 "name menu" "meta menu"; align-items: center; column-gap: 6px;
291 position: relative; }
292 .project-row:hover { background: var(--bg-subtle); }
293 .project-row.active { background: var(--accent-soft); }
294 .project-row .name { grid-area: name; font-size: 13px; font-weight: 500;
295 margin-bottom: 2px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
296 .project-row .meta { grid-area: meta; font-size: 10.5px; color: var(--text-muted);
297 white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
298 .project-row .row-menu-btn { grid-area: menu; opacity: 0; background: transparent;
299 border: 0; color: var(--text-muted); cursor: pointer; font-size: 16px;
300 padding: 2px 6px; border-radius: var(--radius-sm); line-height: 1;
301 transition: opacity .12s, background .12s, color .12s; }
302 .project-row:hover .row-menu-btn,
303 .project-row.active .row-menu-btn { opacity: 1; }
304 .project-row .row-menu-btn:hover { background: var(--bg);
305 color: var(--text); }
306 .row-menu { position: fixed; min-width: 140px; background: var(--bg-elevated);
307 border: 1px solid var(--border); border-radius: var(--radius-sm);
308 box-shadow: var(--shadow-lg); padding: 4px; z-index: 9000;
309 display: flex; flex-direction: column; gap: 2px; }
310 .row-menu button { background: transparent; border: 0; color: var(--text);
311 text-align: left; padding: 7px 10px; border-radius: var(--radius-sm);
312 cursor: pointer; font-size: 12.5px; }
313 .row-menu button:hover { background: var(--bg-subtle); }
314 .row-menu button[data-act="delete"]:hover { background: rgba(220, 60, 60, .12);
315 color: rgb(220, 60, 60); }
316 .empty-list { padding: 16px 14px; text-align: center; color: var(--text-faint);
317 font-size: 11.5px; line-height: 1.7; }
318
319 /* ===== Empty state ===== */
320 .empty-state { display: grid; place-items: center; padding: 8vh 6vw;
321 text-align: center; color: var(--text-muted); grid-column: 2 / -1; }
322 .empty-state .ico { font-size: 40px; margin-bottom: 14px; opacity: .6; }
323 .empty-state h2 { font-family: var(--font-serif); font-size: 22px; color: var(--text);
324 margin-bottom: 12px; font-weight: 500; }
325 .empty-state p { font-size: 13.5px; line-height: 1.7; max-width: 520px; }
326
327 /* ===== Chat pane ===== */
328 .chat-pane { display: grid; grid-template-rows: 1fr auto; min-width: 0;
329 background: var(--bg-panel); border-right: 1px solid var(--border); }
330
331 .chat-log { overflow-y: auto; padding: 18px 22px; display: flex;
332 flex-direction: column; gap: 14px; }
333 .msg { max-width: 92%; }
334 .msg.user { align-self: flex-end; background: var(--selected); color: var(--selected-fg);
335 padding: 9px 14px; border-radius: 14px 14px 4px 14px; font-size: 13.5px; line-height: 1.5; }
336 .msg.assistant { align-self: stretch; display: block;
337 width: 100%; max-width: 100%; min-width: 0;
338 font-size: 13.5px; line-height: 1.6; color: var(--text); }
339 .msg.assistant .body { display: block; max-width: 100%; min-width: 0;
340 overflow-wrap: anywhere; word-break: break-word; }
341 /* Force prose paragraphs and the option card to stack vertically. */
342 .msg.assistant .body > * { display: block; max-width: 100%;
343 box-sizing: border-box; margin-bottom: 8px; }
344 .msg.assistant .body > *:last-child { margin-bottom: 0; }
345 .msg.assistant .role { font-size: 10.5px; color: var(--text-muted); letter-spacing: .12em;
346 text-transform: uppercase; margin-bottom: 5px; font-family: var(--font-mono); }
347 .msg.assistant .body { word-wrap: break-word; }
348 .msg.assistant .body p { margin: 0 0 8px; }
349 .msg.assistant .body p:last-child { margin-bottom: 0; }
350 .msg.assistant .body strong { font-weight: 600; color: var(--text); }
351 .msg.assistant .body em { font-style: italic; }
352 .msg.assistant .body h1, .msg.assistant .body h2, .msg.assistant .body h3 {
353 font-weight: 600; margin: 10px 0 6px; line-height: 1.3; }
354 .msg.assistant .body h1 { font-size: 16px; }
355 .msg.assistant .body h2 { font-size: 14.5px; }
356 .msg.assistant .body h3 { font-size: 13.5px; color: var(--text-muted); }
357 .msg.assistant .body ul, .msg.assistant .body ol { margin: 6px 0; padding-left: 22px; }
358 .msg.assistant .body li { margin: 3px 0; }
359 .msg.assistant .body a { color: var(--accent); text-decoration: underline;
360 text-underline-offset: 2px; }
361 .msg.assistant .body hr { border: 0; border-top: 1px solid var(--border); margin: 10px 0; }
362 .msg.assistant .body blockquote { border-left: 3px solid var(--border-strong);
363 padding: 0 0 0 12px; margin: 6px 0; color: var(--text-muted); font-style: italic; }
364 .msg.assistant .body pre { background: var(--bg-subtle); border: 1px solid var(--border);
365 padding: 10px 14px; border-radius: var(--radius-sm); overflow-x: auto;
366 font-family: var(--font-mono); font-size: 11.5px; margin: 6px 0;
367 white-space: pre-wrap; word-break: break-all; }
368 .msg.assistant .body :not(pre) > code { background: var(--bg-subtle); padding: 1px 5px;
369 border-radius: 3px; font-family: var(--font-mono); font-size: 12px; }
370
371 /* Option cards (hv-options) */
372 .opt-card { margin-top: 8px; background: var(--bg-subtle);
373 border: 1px solid var(--border); border-radius: var(--radius);
374 padding: 12px 14px; width: 100%; max-width: 100%;
375 box-sizing: border-box; overflow: hidden; }
376 .opt-card .question { font-size: 13.5px; font-weight: 500; margin-bottom: 10px;
377 color: var(--text); }
378 .opt-card .opts { display: flex; flex-direction: column; gap: 6px;
379 align-items: stretch; }
380 .opt-card .opt {
381 display: flex; flex-direction: column; gap: 2px;
382 text-align: left; background: var(--bg-panel); border: 1px solid var(--border);
383 color: var(--text); padding: 8px 12px; border-radius: var(--radius-sm); cursor: pointer;
384 transition: all .12s; font-family: inherit;
385 width: 100%; max-width: 100%; box-sizing: border-box;
386 word-break: break-word; overflow-wrap: anywhere; }
387 .opt-card .opt:hover:not(:disabled) { border-color: var(--accent);
388 background: var(--accent-soft); }
389 .opt-card .opt.picked { border-color: var(--accent); background: var(--accent);
390 color: var(--accent-fg); cursor: default; }
391 .opt-card .opt.picked .hint { color: rgba(255,255,255,.85); }
392 .opt-card .opt:disabled { opacity: .55; cursor: not-allowed; }
393 .opt-card .opt .label { font-size: 13px; font-weight: 500;
394 word-break: break-word; }
395 .opt-card .opt .hint { font-size: 11.5px; color: var(--text-muted);
396 word-break: break-word; }
397 .opt-card .freeform-hint { margin-top: 8px; font-size: 11px; color: var(--text-faint);
398 font-style: italic; }
399 /* v0.8.1: inline open-ended answer below option buttons */
400 .opt-card .freeform-input { display: flex; align-items: flex-end; gap: 6px;
401 margin-top: 8px; }
402 .opt-card .freeform-input textarea { flex: 1; resize: none;
403 background: var(--bg-panel); border: 1px solid var(--border);
404 border-radius: var(--radius-sm); color: var(--text);
405 padding: 7px 10px; font-size: 12.5px; font-family: inherit;
406 line-height: 1.5; min-height: 32px; max-height: 160px;
407 transition: border-color .15s; }
408 .opt-card .freeform-input textarea:focus { outline: none;
409 border-color: var(--accent); }
410 .opt-card .freeform-input textarea::placeholder { color: var(--text-faint);
411 font-style: italic; }
412 .opt-card .freeform-input .freeform-send { background: var(--bg);
413 border: 1px solid var(--border); color: var(--text-muted);
414 padding: 6px 10px; border-radius: var(--radius-sm); cursor: pointer;
415 font-size: 11.5px; font-family: var(--font-mono); white-space: nowrap;
416 transition: border-color .15s, color .15s; }
417 .opt-card .freeform-input .freeform-send:hover:not(:disabled) {
418 border-color: var(--accent); color: var(--accent); }
419 .opt-card .freeform-input .freeform-send:disabled { opacity: .4;
420 cursor: not-allowed; }
421
422 /* ===== hv-form (multi-field input card) ===== */
423 .form-card { margin-top: 8px; background: var(--bg-subtle);
424 border: 1px solid var(--border); border-radius: var(--radius);
425 padding: 14px 14px 12px; }
426 .form-card.submitted { opacity: .65; }
427 .form-card .form-title { font-size: 13.5px; font-weight: 500;
428 margin-bottom: 12px; color: var(--text); }
429 .form-card .form-fields { display: flex; flex-direction: column; gap: 10px; }
430 .form-card .form-field { display: flex; flex-direction: column; gap: 4px; }
431 .form-card .form-field label { font-family: var(--font-mono); font-size: 10.5px;
432 letter-spacing: .08em; color: var(--text-muted); text-transform: uppercase;
433 display: flex; align-items: center; gap: 4px; }
434 .form-card .form-field label .req { color: var(--accent); margin-left: 2px; }
435 .form-card .form-field label .form-hint { font-family: inherit; font-size: 10px;
436 font-weight: 400; color: var(--text-muted); margin-left: 8px; text-transform: none; letter-spacing: 0; }
437 .form-card .form-total { font-size: 12px; font-weight: 600; color: var(--accent);
438 margin: 4px 0 2px; font-family: var(--font-mono); }
439 .form-card .form-field input,
440 .form-card .form-field textarea,
441 .form-card .form-field select {
442 background: var(--bg-panel); border: 1px solid var(--border); color: var(--text);
443 padding: 7px 10px; border-radius: var(--radius-sm); font-size: 13px;
444 font-family: var(--font-sans); outline: none; line-height: 1.55;
445 width: 100%; }
446 .form-card .form-field textarea { resize: vertical; min-height: 38px; }
447 .form-card .form-field input:focus,
448 .form-card .form-field textarea:focus,
449 .form-card .form-field select:focus {
450 border-color: var(--accent); box-shadow: 0 0 0 3px var(--accent-soft); }
451 .form-card .form-field input:disabled,
452 .form-card .form-field textarea:disabled,
453 .form-card .form-field select:disabled { opacity: .7; cursor: not-allowed; }
454 /* Segmented buttons (kind: "buttons") */
455 .form-card .form-field .form-seg { display: flex; flex-wrap: wrap; gap: 6px; }
456 .form-card .form-field .form-seg-btn { background: var(--bg);
457 border: 1px solid var(--border); color: var(--text);
458 padding: 7px 12px; border-radius: var(--radius-sm); cursor: pointer;
459 font-size: 12.5px; font-family: inherit; transition: border-color .15s,
460 background .15s, color .15s; }
461 .form-card .form-field .form-seg-btn:hover:not(:disabled) {
462 border-color: var(--accent); }
463 .form-card .form-field .form-seg-btn.selected {
464 background: var(--accent); border-color: var(--accent);
465 color: var(--accent-fg); box-shadow: 0 2px 6px rgba(201, 100, 66, .2); }
466 .form-card .form-field .form-seg-btn:disabled { opacity: .55; cursor: not-allowed; }
467 .form-card .form-attachments { margin-top: 12px; padding-top: 10px;
468 border-top: 1px dashed var(--border); }
469 .form-card .form-drop-hint { font-size: 11.5px; color: var(--text-muted);
470 margin-bottom: 6px; }
471 .form-card .form-attach-btn { background: var(--bg);
472 border: 1px solid var(--border); color: var(--text); padding: 5px 10px;
473 border-radius: var(--radius-sm); cursor: pointer; font-size: 11.5px;
474 font-family: var(--font-mono); }
475 .form-card .form-attach-btn:hover { border-color: var(--accent); }
476 .form-card .form-actions { margin-top: 14px; display: flex;
477 justify-content: flex-end; }
478 .form-card .form-submit { background: var(--accent); color: var(--accent-fg);
479 border: 0; padding: 7px 16px; border-radius: var(--radius-sm);
480 cursor: pointer; font-size: 12.5px; font-weight: 500;
481 box-shadow: 0 4px 10px rgba(201, 100, 66, .18); }
482 .form-card .form-submit:hover { background: var(--accent-hover); }
483
484 /* ===== hv-confirm (start-or-edit card) ===== */
485 .confirm-card { margin-top: 8px; background: var(--bg-subtle);
486 border: 1px solid var(--border); border-radius: var(--radius);
487 padding: 14px; }
488 .confirm-card.resolved { opacity: .7; }
489 .confirm-card .confirm-title { font-size: 13.5px; font-weight: 500;
490 margin-bottom: 10px; }
491 .confirm-card .confirm-summary { display: flex; flex-direction: column;
492 gap: 4px; padding: 10px 12px; background: var(--bg);
493 border: 1px solid var(--border); border-radius: var(--radius-sm);
494 margin-bottom: 12px; }
495 .confirm-card .confirm-row { display: grid;
496 grid-template-columns: minmax(80px, max-content) 1fr; gap: 12px;
497 font-size: 12px; line-height: 1.55; align-items: baseline; }
498 .confirm-card .confirm-label { font-family: var(--font-mono);
499 color: var(--text-muted); letter-spacing: .04em; }
500 .confirm-card .confirm-value { color: var(--text); word-break: break-word; }
501 .confirm-card .confirm-value .muted { color: var(--text-faint); font-style: italic; }
502 .confirm-card .confirm-actions { display: flex; gap: 8px; }
503 .confirm-card .confirm-go { background: var(--accent); color: var(--accent-fg);
504 border: 0; padding: 7px 14px; border-radius: var(--radius-sm);
505 cursor: pointer; font-size: 12.5px; font-weight: 500; flex: 1;
506 box-shadow: 0 4px 10px rgba(201, 100, 66, .18); }
507 .confirm-card .confirm-go:hover { background: var(--accent-hover); }
508 .confirm-card .confirm-edit { background: var(--bg);
509 border: 1px solid var(--border); color: var(--text);
510 padding: 7px 14px; border-radius: var(--radius-sm); cursor: pointer;
511 font-size: 12.5px; }
512 .confirm-card .confirm-edit:hover { border-color: var(--accent); }
513 .confirm-card .confirm-resolved-mark { margin-top: 8px;
514 font-family: var(--font-mono); font-size: 11.5px; color: var(--accent); }
515
516 .msg.system { align-self: center; font-size: 11px; color: var(--text-faint);
517 font-family: var(--font-mono); }
518 .msg.preview-event { align-self: center; background: var(--green-bg); color: var(--green);
519 padding: 4px 12px; border-radius: var(--radius-pill); font-size: 11px;
520 font-family: var(--font-mono); letter-spacing: .04em; }
521 .msg.thinking { align-self: flex-start; color: var(--text-muted); font-style: italic;
522 font-size: 12px; padding: 4px 0; }
523 .msg.thinking::after { content: ''; display: inline-block; width: 4px; height: 4px;
524 background: var(--text-muted); border-radius: 50%; margin-left: 6px;
525 animation: pulse 1s infinite; }
526 @keyframes pulse { 0%, 100% { opacity: .3; } 50% { opacity: 1; } }
527 /* Export-done card: show path + "reveal" / "copy" actions */
528 .msg.export-done { align-self: stretch; background: var(--bg-subtle);
529 border: 1px solid var(--border); border-radius: var(--radius);
530 padding: 14px 14px 12px; }
531 .msg.export-done .export-title { font-size: 13.5px; font-weight: 500;
532 margin-bottom: 8px; }
533 .msg.export-done .export-path { background: var(--bg-panel);
534 border: 1px solid var(--border); border-radius: var(--radius-sm);
535 padding: 6px 10px; margin-bottom: 10px; }
536 .msg.export-done .export-path code { font-size: 11.5px; color: var(--text);
537 font-family: var(--font-mono); white-space: nowrap; overflow: hidden;
538 text-overflow: ellipsis; display: block; }
539 .msg.export-done .export-actions { display: flex; gap: 6px; flex-wrap: wrap; }
540 .msg.export-done .export-actions button { background: var(--bg);
541 border: 1px solid var(--border); color: var(--text); padding: 5px 10px;
542 border-radius: var(--radius-sm); cursor: pointer; font-size: 11.5px;
543 font-family: var(--font-mono); transition: border-color .12s; }
544 .msg.export-done .export-actions button:hover { border-color: var(--accent); }
545 .msg.export-done .export-actions .btn-reveal { background: var(--accent);
546 border-color: var(--accent); color: var(--accent-fg); }
547 .msg.export-done .export-actions .btn-reveal:hover { background: var(--accent-hover); }
548 .msg.export-done .export-fname { margin-top: 8px; font-size: 10.5px;
549 color: var(--text-muted); font-family: var(--font-mono); letter-spacing: .04em; }
550
551 .chat-empty { display: grid; place-items: center; height: 100%; color: var(--text-faint);
552 font-size: 13px; padding: 0 18px; text-align: center; line-height: 1.7; }
553 .chat-empty .ico { font-size: 32px; margin-bottom: 12px; opacity: .5; }
554 .chat-empty .examples { margin-top: 16px; display: flex; flex-direction: column;
555 gap: 6px; align-items: center; }
556 .chat-empty .examples b { color: var(--text-muted); font-weight: 500; font-style: italic; }
557
558 .composer { padding: 12px 22px 16px; background: var(--bg-panel);
559 border-top: 1px solid var(--border); }
560 .composer-shell { background: var(--bg-panel); border: 1px solid var(--border-strong);
561 border-radius: var(--radius); padding: 8px 10px; transition: border-color .15s;
562 display: flex; flex-direction: column; gap: 6px; }
563 /* Inline chip indicating which frame the next message will rewrite */
564 .composer-shell .focus-chip { display: inline-flex; align-items: center;
565 gap: 6px; align-self: flex-start; background: var(--accent-soft);
566 color: var(--accent); border-radius: var(--radius-pill);
567 padding: 4px 6px 4px 10px; font-size: 11.5px; font-family: var(--font-mono);
568 border: 1px solid var(--accent); }
569 .composer-shell .focus-chip .fid { background: var(--bg-panel); padding: 1px 6px;
570 border-radius: var(--radius-sm); font-size: 10.5px; }
571 .composer-shell .focus-chip button { background: transparent; border: 0;
572 color: var(--accent); cursor: pointer; padding: 0 4px; line-height: 1;
573 font-size: 13px; }
574 .composer-shell .focus-chip button:hover { color: var(--text); }
575 .composer-shell:focus-within { border-color: var(--accent);
576 box-shadow: 0 0 0 3px var(--accent-soft); }
577 .composer-shell.dragging { border-color: var(--accent);
578 background: var(--accent-soft); }
579 .composer .attachments { display: flex; flex-wrap: wrap; gap: 4px; padding-bottom: 4px;
580 border-bottom: 1px dashed var(--border); margin-bottom: 4px; }
581 .composer .attachments:empty { display: none; }
582 .composer .att-chip { display: inline-flex; align-items: center; gap: 5px;
583 padding: 3px 8px 3px 6px; background: var(--bg-subtle); border: 1px solid var(--border);
584 border-radius: var(--radius-pill); font-size: 11px; color: var(--text); max-width: 220px; }
585 .composer .att-chip .ico { font-size: 12px; }
586 .composer .att-chip .name { white-space: nowrap; overflow: hidden;
587 text-overflow: ellipsis; flex: 1; }
588 .composer .att-chip button { background: none; border: 0; color: var(--text-muted);
589 font-size: 13px; padding: 0; cursor: pointer; line-height: 1; }
590 .composer .att-chip button:hover { color: var(--red); }
591 .composer .att-chip img { width: 18px; height: 18px; object-fit: cover;
592 border-radius: 2px; background: var(--bg); }
593 .composer textarea { width: 100%; background: transparent; border: 0; outline: none;
594 color: var(--text); resize: none; min-height: 40px; max-height: 220px;
595 padding: 4px 4px; line-height: 1.5; }
596 .composer .actions { display: flex; align-items: center; gap: 6px; }
597 .composer .hint { font-size: 10.5px; color: var(--text-faint);
598 font-family: var(--font-mono); margin-right: auto; padding-left: 4px; }
599 .composer .icon-btn { background: transparent; border: 0; color: var(--text-muted);
600 cursor: pointer; padding: 4px 6px; border-radius: var(--radius-sm); font-size: 13px; }
601 .composer .icon-btn:hover { color: var(--text); background: var(--bg-subtle); }
602 .composer .send-btn { background: var(--accent); color: var(--accent-fg); border: 0;
603 padding: 6px 18px; border-radius: var(--radius-sm); font-weight: 500; cursor: pointer; }
604 .composer .send-btn:hover:not(:disabled) { background: var(--accent-hover); }
605 .composer .send-btn:disabled { opacity: .4; cursor: not-allowed; }
606
607 /* ===== Right column: text editor (data-hv-text fields) ===== */
608 /* Note: now sits to the right of the preview, hence border-LEFT not -right. */
609 .text-pane { background: var(--bg-panel); border-left: 1px solid var(--border);
610 display: flex; flex-direction: column; min-width: 0; overflow: hidden; }
611 .text-pane-head { display: flex; align-items: center; gap: 8px;
612 padding: 12px 16px; border-bottom: 1px solid var(--border); }
613 .text-pane-head h2 { font-size: 11px; letter-spacing: .12em; text-transform: uppercase;
614 color: var(--text-muted); font-family: var(--font-mono); flex: 1;
615 white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
616 .text-pane-head .save-state { font-size: 10.5px; color: var(--text-muted);
617 font-family: var(--font-mono); }
618 .text-pane-head .save-state.saving { color: var(--accent); }
619 .text-pane-head .save-state.saved { color: var(--green); }
620 .text-pane-head .textfields-toggle { background: transparent; border: 0;
621 color: var(--text-muted); cursor: pointer; padding: 4px 6px;
622 border-radius: var(--radius-sm); font-size: 12px; line-height: 1; }
623 .text-pane-head .textfields-toggle:hover { color: var(--text); background: var(--bg-subtle); }
624 body.textfields-collapsed .text-pane-head h2,
625 body.textfields-collapsed .text-pane-head .save-state,
626 body.textfields-collapsed .text-fields { display: none; }
627 body.textfields-collapsed .text-pane-head .textfields-toggle { transform: rotate(180deg); }
628 .text-fields { flex: 1; overflow-y: auto; padding: 14px 16px; }
629 .text-empty { color: var(--text-faint); font-size: 12.5px; line-height: 1.7;
630 text-align: center; padding: 30px 20px; }
631 .text-field { margin-bottom: 14px; }
632 .text-field .key { font-family: var(--font-mono); font-size: 10.5px;
633 letter-spacing: .08em; color: var(--text-muted); text-transform: uppercase;
634 margin-bottom: 5px; display: flex; align-items: center; gap: 6px; }
635 .text-field .key .badge { font-size: 9px; background: var(--bg-subtle);
636 padding: 1px 6px; border-radius: var(--radius-pill); color: var(--text-faint);
637 text-transform: lowercase; letter-spacing: 0; }
638 .text-field input, .text-field textarea {
639 width: 100%; background: var(--bg-panel); border: 1px solid var(--border); color: var(--text);
640 padding: 7px 10px; border-radius: var(--radius-sm); font-size: 13px; outline: none;
641 font-family: var(--font-sans); line-height: 1.55; }
642 .text-field input:focus, .text-field textarea:focus {
643 border-color: var(--accent); box-shadow: 0 0 0 3px var(--accent-soft); }
644 .text-field textarea { resize: vertical; min-height: 38px; }
645
646 /* ===== Centre pane: preview + frames timeline + footer ===== */
647 /* Order: preview stage on top (largest), frames below it, footer at bottom. */
648 .right-pane { display: grid; grid-template-rows: 1fr auto auto auto; background: var(--bg);
649 overflow: hidden; }
650 /* v0.8: frames timeline (multi-frame projects only) */
651 /* v0.8.4: each chip carries a real iframe thumbnail, scaled-down 16:9. */
652 .frames-strip { display: none; padding: 12px 22px; gap: 10px; overflow-x: auto;
653 border-top: 1px solid var(--border); background: var(--bg-panel);
654 align-items: stretch; }
655 .frames-strip.has-frames { display: flex; }
656 .frames-strip .label { font-family: var(--font-mono); font-size: 10px;
657 color: var(--text-muted); letter-spacing: .14em; text-transform: uppercase;
658 margin-right: 4px; align-self: center; }
659 .frames-strip button.frame-tab { background: var(--bg); border: 1px solid var(--border);
660 color: var(--text); padding: 0; border-radius: var(--radius-sm);
661 cursor: pointer; transition: border-color .15s, transform .15s;
662 overflow: hidden; flex: 0 0 auto; display: flex; flex-direction: column;
663 width: 180px; }
664 .frames-strip button.frame-tab:hover { border-color: var(--accent);
665 transform: translateY(-1px); }
666 .frames-strip button.frame-tab.active { border-color: var(--accent);
667 box-shadow: 0 0 0 2px var(--accent-soft); }
668 .frames-strip button.frame-tab.focus { border-color: var(--accent);
669 box-shadow: 0 0 0 2px var(--accent), 0 4px 14px rgba(201, 100, 66, .25); }
670 .frames-strip .frame-thumb .focus-mark { position: absolute; top: 4px;
671 right: 4px; width: 22px; height: 22px; display: grid; place-items: center;
672 background: var(--accent); color: var(--accent-fg); border-radius: 50%;
673 font-size: 12px; font-weight: 700; box-shadow: 0 2px 6px rgba(0,0,0,.3);
674 z-index: 2; }
675 /* The actual mini render: iframe rendered at full size then transform-
676 scaled so a 1920×1080 page fits 178×100 (the 178 = 180 - 2px border). */
677 .frames-strip .frame-thumb { width: 178px; height: 100px; background: #000;
678 overflow: hidden; position: relative; }
679 .frames-strip .frame-thumb iframe { position: absolute; top: 0; left: 0;
680 width: 1920px; height: 1080px; border: 0;
681 transform: scale(calc(178 / 1920)); transform-origin: top left;
682 pointer-events: none; }
683 /* Native (enhanced) frame plays a rendered MP4 instead of an HTML iframe. */
684 .frames-strip .frame-thumb video { position: absolute; top: 0; left: 0;
685 width: 100%; height: 100%; object-fit: cover; pointer-events: none; }
686 /* "⚡ Enhance" — an overlay badge ON the data-frame thumbnail (top-center) so
687 it's obvious and always visible, not a tiny line under the chip. */
688 .frames-strip button.frame-tab .frame-enhance { position: absolute; z-index: 3;
689 top: 6px; left: 50%; transform: translateX(-50%);
690 display: inline-flex; align-items: center; gap: 3px;
691 font-size: 11px; font-weight: 700; font-family: var(--font-mono);
692 line-height: 1; white-space: nowrap; padding: 5px 9px; border-radius: 999px;
693 cursor: pointer; color: #fff; background: var(--accent);
694 box-shadow: 0 2px 8px rgba(0,0,0,.45); border: 1px solid rgba(255,255,255,.25); }
695 /* Pulse so the eye lands on it the first time. */
696 .frames-strip button.frame-tab.is-data .frame-enhance:not(.on):not(.busy) {
697 animation: hvEnhancePulse 1.8s ease-in-out infinite; }
698 @keyframes hvEnhancePulse {
699 0%,100% { box-shadow: 0 2px 8px rgba(0,0,0,.45); }
700 50% { box-shadow: 0 0 0 4px rgba(255,90,44,.35), 0 2px 8px rgba(0,0,0,.45); } }
701 .frames-strip button.frame-tab .frame-enhance:hover { filter: brightness(1.12);
702 transform: translateX(-50%) scale(1.04); }
703 .frames-strip button.frame-tab .frame-enhance.on { background: rgba(20,20,24,.82);
704 color: #fff; font-weight: 600; animation: none; }
705 .frames-strip button.frame-tab .frame-enhance.busy { background: var(--accent);
706 cursor: default; opacity: .9; animation: none; }
707 .frames-strip button.frame-tab .frame-tab-label { display: flex;
708 align-items: center; gap: 6px; padding: 6px 8px; font-size: 11px;
709 font-family: var(--font-mono); white-space: nowrap; overflow: hidden;
710 text-overflow: ellipsis; background: var(--bg-panel); }
711 .frames-strip button.frame-tab.active .frame-tab-label { color: var(--accent); }
712 .frames-strip button.frame-tab .order { opacity: .5; }
713 .frames-strip button.frame-tab .fid { overflow: hidden;
714 text-overflow: ellipsis; white-space: nowrap; }
715 .frames-strip .frame-graph-btn { margin-left: auto; background: var(--bg);
716 border: 1px solid var(--border); color: var(--text); padding: 6px 10px;
717 border-radius: var(--radius-sm); cursor: pointer; font-size: 11px; }
718 .frames-strip .frame-graph-btn:hover { border-color: var(--accent); }
719
720 /* v0.8: graph viewer modal */
721 .graph-modal { position: fixed; inset: 0; background: rgba(0,0,0,.5);
722 display: none; align-items: center; justify-content: center; z-index: 999;
723 padding: 30px; }
724 .graph-modal.open { display: flex; }
725 .graph-modal .panel { background: var(--bg-elevated); border: 1px solid var(--border);
726 border-radius: var(--radius); box-shadow: var(--shadow-lg);
727 width: 100%; max-width: 760px; max-height: 80vh; display: flex; flex-direction: column; }
728 .graph-modal .panel header { padding: 14px 20px; border-bottom: 1px solid var(--border);
729 display: flex; align-items: center; gap: 12px; }
730 .graph-modal .panel header h3 { margin: 0; font-size: 14px; }
731 .graph-modal .panel header .grow { flex: 1; }
732 .graph-modal .panel header .download-btn,
733 .graph-modal .panel header .close-btn { background: var(--bg); border: 1px solid var(--border);
734 color: var(--text); padding: 5px 10px; border-radius: var(--radius-sm);
735 cursor: pointer; font-size: 11px; }
736 .graph-modal .panel pre { margin: 0; padding: 18px 20px; overflow: auto;
737 font-family: var(--font-mono); font-size: 12px; line-height: 1.55;
738 color: var(--text); flex: 1; }
739
740 .preview-stage { padding: 22px; overflow: auto; display: flex;
741 align-items: center; justify-content: center; min-height: 0; }
742 /* aspect-ratio + max-width are set inline per project resolution (16:9 / 9:16
743 / 1:1 / 4:5); 16/9 here is just a fallback before JS runs. */
744 .preview-frame { aspect-ratio: 16/9; background: #000; border-radius: var(--radius);
745 overflow: hidden; box-shadow: var(--shadow-md); width: 100%; max-width: 1280px;
746 position: relative; }
747 /* iframe / native-frame <video> render at the design's native pixel size (set
748 inline) and are scaled down with a CSS variable so the full frame stays
749 visible instead of overflowing the box (Remotion frames are 1920×1080). */
750 .preview-frame iframe, .preview-frame video { position: absolute; top: 0; left: 0; border: 0;
751 transform: scale(var(--preview-scale, 1));
752 transform-origin: top left; }
753 .preview-frame .stamp { position: absolute; bottom: 12px; right: 14px;
754 font-family: var(--font-mono); font-size: 10px; color: rgba(255,255,255,.6);
755 letter-spacing: .14em; text-transform: uppercase; pointer-events: none;
756 text-shadow: 0 1px 4px rgba(0,0,0,.6); }
757 /* In-place text edit toggle (top-right of the preview) */
758 .preview-frame .edit-toggle { position: absolute; top: 10px; right: 10px;
759 background: rgba(0,0,0,.55); color: #fff; border: 1px solid rgba(255,255,255,.15);
760 border-radius: var(--radius-pill); padding: 5px 12px; font-size: 11.5px;
761 font-family: var(--font-mono); cursor: pointer; backdrop-filter: blur(8px);
762 transition: background .15s, border-color .15s; z-index: 5; }
763 .preview-frame .edit-toggle:hover { background: rgba(0,0,0,.75);
764 border-color: rgba(255,255,255,.35); }
765 .preview-frame.editing .edit-toggle { background: var(--accent);
766 border-color: var(--accent); }
767 .preview-frame.editing .edit-toggle:hover { background: var(--accent-hover); }
768 /* While editing, dim the stamp so it doesn't compete with the edit ring */
769 .preview-frame.editing .stamp { opacity: .25; }
770 .preview-placeholder { aspect-ratio: 16/9; background: var(--bg-subtle);
771 border: 2px dashed var(--border-strong); border-radius: var(--radius);
772 display: grid; place-items: center; padding: 30px; text-align: center;
773 color: var(--text-muted); font-size: 13.5px; line-height: 1.7; width: 100%;
774 max-width: 1280px; }
775 .preview-placeholder .ico { font-size: 32px; opacity: .5; margin-bottom: 12px; }
776
777 .right-footer { padding: 10px 22px; border-top: 1px solid var(--border);
778 display: flex; align-items: center; gap: 12px; background: var(--bg-panel); font-size: 11px; }
779 .right-footer .status { color: var(--text-muted); font-family: var(--font-mono); }
780 .right-footer .status b { color: var(--text); }
781 .right-footer .grow { flex: 1; }
782 .right-footer .reload-btn { background: var(--bg); border: 1px solid var(--border);
783 color: var(--text); padding: 4px 10px; border-radius: var(--radius-sm);
784 cursor: pointer; font-size: 11px; }
785 .right-footer .reload-btn:hover { border-color: var(--accent); }
786
787 /* ===== Soundtrack panel ===== */
788 .soundtrack-panel { border-top: 1px solid var(--border); background: var(--bg-panel);
789 max-height: 42vh; overflow-y: auto; }
790 /* Prominent, clearly-clickable collapsed bar (collapsed by default so it
791 never crowds the preview). */
792 .soundtrack-panel > summary { padding: 13px 22px; cursor: pointer; font-size: 13px;
793 font-weight: 600; color: var(--text); user-select: none; list-style: none;
794 display: flex; align-items: center; gap: 10px;
795 background: var(--accent-soft); border: 1px dashed var(--accent);
796 border-radius: var(--radius-sm); margin: 8px 16px; transition: background .12s, box-shadow .12s; }
797 .soundtrack-panel > summary::-webkit-details-marker { display: none; }
798 .soundtrack-panel > summary::after { content: '⌄'; margin-left: auto; font-size: 15px;
799 color: var(--accent); transition: transform .15s; flex: 0 0 auto; }
800 .soundtrack-panel[open] > summary::after { transform: rotate(180deg); }
801 .soundtrack-panel[open] > summary { border-style: solid; }
802 .soundtrack-panel > summary:hover { background: var(--accent); }
803 .soundtrack-panel > summary:hover .st-summary-main,
804 .soundtrack-panel > summary:hover .st-summary-sub { color: var(--accent-fg); }
805 .soundtrack-panel > summary:hover::after { color: var(--accent-fg); }
806 .st-summary-main { flex: 0 0 auto; }
807 .st-summary-sub { font-size: 11px; font-weight: 400; color: var(--text-muted); flex: 1; min-width: 0;
808 white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
809 .soundtrack-badge { font-size: 10px; font-weight: 600; text-transform: uppercase;
810 letter-spacing: .05em; color: var(--text-muted); border: 1px solid var(--border);
811 border-radius: 999px; padding: 1px 7px; margin-left: 6px; vertical-align: middle; }
812 /* music style preset chips */
813 .st-presets { display: flex; flex-wrap: wrap; gap: 6px; margin-bottom: 6px; }
814 .st-preset { font-size: 11px; padding: 4px 11px; border-radius: 999px; cursor: pointer;
815 background: var(--bg); border: 1px solid var(--border); color: var(--text);
816 transition: all .12s; }
817 .st-preset:hover { border-color: var(--accent); color: var(--accent); }
818 .st-preset.active { background: var(--accent); color: var(--accent-fg); border-color: var(--accent); }
819 /* narration voice picker */
820 .st-voice-row { display: flex; align-items: center; gap: 8px; margin-top: 6px; }
821 .st-voice-label { font-size: 11px; color: var(--text-muted); }
822 .st-voice-select { font-size: 11px; padding: 3px 8px; border-radius: var(--radius-sm);
823 background: var(--bg); border: 1px solid var(--border); color: var(--text); cursor: pointer; }
824 .st-voice-select:focus { outline: none; border-color: var(--accent); }
825 .st-fit { font-size: 11px; padding: 3px 10px; border-radius: var(--radius-sm); cursor: pointer;
826 background: var(--bg); border: 1px solid var(--accent); color: var(--accent); margin-left: auto;
827 transition: all .12s; }
828 .st-fit:hover:not(:disabled) { background: var(--accent); color: var(--accent-fg); }
829 .st-fit:disabled { opacity: .4; cursor: not-allowed; border-color: var(--border); color: var(--text-muted); }
830 /* narration field header with inline draft buttons */
831 .st-field-head { display: flex; align-items: center; justify-content: space-between; gap: 8px; }
832 .st-draft-group { display: inline-flex; gap: 6px; margin-top: 2px; }
833 .st-narration-which { font-size: 11px; font-weight: 600; color: var(--accent); white-space: nowrap; }
834 /* Two numbered sub-steps inside narration: write (text) → synthesize (audio),
835 so the ✨ draft buttons and the 🎙 synth button read as distinct actions. */
836 .st-substep { display: flex; flex-direction: column; gap: 7px; padding: 9px 10px;
837 border-radius: var(--radius-sm); background: var(--bg-subtle); border: 1px solid var(--border); }
838 .st-substep-head { display: flex; align-items: center; gap: 7px; }
839 .st-step-badge { display: inline-flex; align-items: center; justify-content: center;
840 width: 17px; height: 17px; border-radius: 999px; background: var(--accent); color: var(--accent-fg);
841 font-size: 10px; font-weight: 700; flex: 0 0 auto; }
842 .st-step-label { font-size: 11.5px; font-weight: 600; color: var(--text); }
843 .audio-region { display: flex; gap: 6px; flex-wrap: wrap; }
844 .st-draft { font-size: 11px; padding: 3px 10px; border-radius: var(--radius-sm); cursor: pointer;
845 background: var(--bg); border: 1px solid var(--accent); color: var(--accent); transition: all .12s; }
846 .st-draft:hover:not(:disabled) { background: var(--accent); color: var(--accent-fg); }
847 .st-draft:disabled { opacity: .4; cursor: not-allowed; border-color: var(--border); color: var(--text-muted); }
848 .soundtrack-body { padding: 4px 22px 16px; display: flex; flex-direction: column; gap: 14px; }
849 /* Two clearly separated sections: music (its own Generate) + narration (its own). */
850 .st-section { display: flex; flex-direction: column; gap: 8px; padding: 12px; border-radius: var(--radius-sm);
851 background: var(--bg); border: 1px solid var(--border); }
852 .st-section-title { font-size: 12px; font-weight: 600; color: var(--text);
853 display: flex; align-items: center; justify-content: space-between; gap: 8px; }
854 .st-section-actions { display: flex; align-items: center; gap: 10px; margin-top: 2px; }
855 .st-vol-row label { display: flex; align-items: center; gap: 8px; font-size: 11px; color: var(--text-muted); }
856 .st-vol-row input[type=range] { flex: 1; }
857 .st-vol-row b { color: var(--text); font-family: var(--font-mono); min-width: 52px; text-align: right; }
858 .soundtrack-hint { font-size: 11px; color: var(--text-muted); margin: 0; }
859 .soundtrack-field { display: flex; flex-direction: column; gap: 4px; }
860 .soundtrack-field > span { font-size: 11px; color: var(--text-muted); }
861 .soundtrack-field textarea { width: 100%; resize: vertical; background: var(--bg);
862 border: 1px solid var(--border); color: var(--text); border-radius: var(--radius-sm);
863 padding: 8px 10px; font: inherit; font-size: 12px; box-sizing: border-box; }
864 .soundtrack-field textarea:focus { outline: none; border-color: var(--accent); }
865 .soundtrack-vols { display: flex; flex-direction: column; gap: 6px; }
866 .soundtrack-vols label { display: flex; align-items: center; gap: 8px; font-size: 11px;
867 color: var(--text-muted); }
868 .soundtrack-vols input[type=range] { flex: 1; }
869 .soundtrack-vols b { color: var(--text); font-family: var(--font-mono); min-width: 52px;
870 text-align: right; }
871 .soundtrack-actions { display: flex; align-items: center; gap: 8px; }
872 /* Primary generate buttons — match the green primary buttons used elsewhere
873 (form-submit / send). The earlier rule was scoped to .soundtrack-actions, so
874 after the section split these rendered as unstyled grey defaults. */
875 .st-generate { background: var(--accent); color: var(--accent-fg);
876 border: 0; padding: 7px 16px; border-radius: var(--radius-sm); cursor: pointer;
877 font-size: 12.5px; font-weight: 500; box-shadow: 0 4px 10px rgba(201, 100, 66, .18);
878 transition: background .12s; }
879 .st-generate:hover:not(:disabled) { background: var(--accent-hover); }
880 .st-generate:disabled,
881 .st-clear:disabled { opacity: .4; cursor: not-allowed; box-shadow: none; }
882 .st-clear { background: var(--bg); border: 1px solid var(--border);
883 color: var(--text); padding: 6px 12px; border-radius: var(--radius-sm); cursor: pointer;
884 font-size: 12px; transition: background .12s; }
885 .st-clear:hover:not(:disabled) { background: var(--bg-subtle); }
886 .st-status { font-size: 11px; color: var(--text-muted); flex: 1; min-width: 0; }
887 .soundtrack-preview { display: flex; flex-direction: column; gap: 8px; }
888 .soundtrack-preview .st-track { display: flex; align-items: center; gap: 10px; }
889 .soundtrack-preview .st-track > span { font-size: 11px; color: var(--text-muted);
890 min-width: 64px; }
891 .soundtrack-preview audio { flex: 1; height: 32px; }
892
893 /* ===== Toast ===== */
894 .toast { position: fixed; bottom: 20px; left: 50%; transform: translateX(-50%);
895 background: var(--bg-elevated); border: 1px solid var(--border);
896 box-shadow: var(--shadow-lg); padding: 10px 18px; border-radius: var(--radius);
897 font-size: 13px; opacity: 0; transition: opacity .2s; z-index: 1000;
898 pointer-events: none; }
899 .toast.show { opacity: 1; }
900 .toast.success { border-color: var(--green); color: var(--green); }
901 .toast.error { border-color: var(--red); color: var(--red); }
902
903 /* ===== Modal: gallery ===== */
904 .modal-bg { position: fixed; inset: 0; background: rgba(0,0,0,.4); display: none;
905 align-items: center; justify-content: center; z-index: 100; }
906 .modal-bg.show { display: flex; }
907 .modal { background: var(--bg-elevated); box-shadow: var(--shadow-lg);
908 border-radius: var(--radius-lg); padding: 24px; width: 92vw; max-width: 1200px;
909 max-height: 86vh; display: flex; flex-direction: column; }
910 .modal-head { display: flex; align-items: center; gap: 12px; margin-bottom: 16px; }
911 .modal-head h2 { font-family: var(--font-serif); font-size: 22px; font-weight: 500;
912 flex: 1; }
913 .modal-close { background: var(--bg-panel); border: 1px solid var(--border);
914 color: var(--text); padding: 6px 14px; border-radius: var(--radius-sm); cursor: pointer; }
915 .modal-close:hover { border-color: var(--accent); }
916
917 .gallery { flex: 1; overflow-y: auto; display: grid;
918 grid-template-columns: repeat(auto-fill, minmax(260px, 1fr));
919 grid-auto-rows: max-content; align-content: start;
920 gap: 16px; padding: 4px; }
921 .gallery-card { background: var(--bg-panel); border: 1px solid var(--border);
922 border-radius: var(--radius); overflow: hidden; cursor: pointer;
923 transition: all .15s; display: flex; flex-direction: column; }
924 .gallery-card:hover { border-color: var(--accent); transform: translateY(-2px);
925 box-shadow: var(--shadow-md); }
926 .gallery-card.selected { border-color: var(--accent);
927 box-shadow: 0 0 0 2px var(--accent-soft); }
928 /* Use the same fixed-source-size + transform:scale trick as the main
929 preview iframe so 1920×1080 templates fit any card width without
930 being cropped by absolute-positioned elements at the right edge. */
931 /* Height comes from a padding-top percentage rather than aspect-ratio:
932 inside the column-flex card, aspect-ratio on a flex item collapses to
933 0 height (min-height:auto + no resolved cross size), leaving just the
934 black bar. padding-top:56.25% (= 9/16) reserves a real 16:9 box that
935 the absolutely-positioned iframe fills. */
936 .gallery-card .preview { padding-top: 56.25%; background: #000; position: relative;
937 overflow: hidden; flex: 0 0 auto; }
938 .gallery-card .preview iframe { position: absolute; top: 0; left: 0;
939 width: 1920px; height: 1080px; border: 0;
940 transform: scale(var(--gallery-scale, 0.135));
941 transform-origin: top left; pointer-events: none; }
942 /* Portrait templates (vignelli / 9:16) keep the SAME 16:9 card box as
943 everything else so the grid rows stay uniform — the tall 1080×1920
944 source is scaled to fit the box height and centred, letterboxed left
945 and right rather than stretching the whole card. */
946 .gallery-card .preview.portrait iframe { width: 1080px; height: 1920px;
947 left: 50%;
948 transform: translateX(-50%) scale(var(--gallery-scale, 0.08));
949 transform-origin: top center; }
950 /* Poster-mode cards: the shipped SVG fills the 16:9 box directly. */
951 .gallery-card .preview img.poster { position: absolute; inset: 0;
952 width: 100%; height: 100%; object-fit: cover; display: block; }
953
954 /* ===== Template fullscreen preview (the "confirm before applying" modal) ===== */
955 .tpl-preview-modal { z-index: 1100; }
956 .tpl-preview-modal .modal { width: min(1240px, 96vw); height: min(840px, 92vh);
957 padding: 0; display: flex; flex-direction: column; gap: 0; }
958 .tpl-preview-head { display: flex; align-items: center; gap: 14px;
959 padding: 14px 18px; border-bottom: 1px solid var(--border); }
960 .tpl-preview-head .name { font-size: 14.5px; font-weight: 500; flex: 0 0 auto; }
961 .tpl-preview-head .desc { font-size: 12px; color: var(--text-muted);
962 flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
963 .tpl-preview-head .modal-close { background: transparent; border: 0;
964 color: var(--text-muted); font-size: 16px; cursor: pointer; padding: 4px 8px;
965 border-radius: var(--radius-sm); }
966 .tpl-preview-head .modal-close:hover { color: var(--text); background: var(--bg-subtle); }
967 .tpl-preview-stage { flex: 1; background: #000; padding: 22px;
968 display: flex; align-items: flex-start; justify-content: center;
969 overflow: auto; }
970 .tpl-preview-frame { aspect-ratio: 16/9; background: #000; position: relative;
971 width: 100%; max-width: 100%; flex: 0 0 auto; border-radius: var(--radius);
972 overflow: hidden; }
973 .tpl-preview-frame.portrait { aspect-ratio: 9/16; max-width: none;
974 width: min(46%, 420px); }
975 .tpl-preview-frame iframe { position: absolute; top: 0; left: 0;
976 width: 1920px; height: 1080px; border: 0;
977 transform: scale(var(--tpl-preview-scale, 1)); transform-origin: top left; }
978 .tpl-preview-frame.portrait iframe { width: 1080px; height: 1920px; }
979 .tpl-preview-frame img.poster { position: absolute; inset: 0; width: 100%;
980 height: 100%; object-fit: contain; background: #000; }
981 .tpl-preview-source { padding: 11px 18px; border-top: 1px solid var(--border);
982 background: var(--bg-panel); font-size: 12px; line-height: 1.5;
983 color: var(--text-dim, #8a8a8a); display: flex; flex-direction: column; gap: 3px; }
984 .tpl-preview-source .row { display: flex; gap: 7px; align-items: baseline; }
985 .tpl-preview-source .lbl { flex: 0 0 auto; min-width: 62px; font-weight: 600;
986 text-transform: uppercase; letter-spacing: .4px; font-size: 10.5px;
987 color: var(--text-dim, #8a8a8a); }
988 .tpl-preview-source .val { color: var(--text); }
989 .tpl-preview-source a { color: var(--accent); text-decoration: none; }
990 .tpl-preview-source a:hover { text-decoration: underline; }
991 .tpl-preview-source .lic { display: inline-block; padding: 1px 6px;
992 border: 1px solid var(--border); border-radius: 4px; font-size: 10.5px;
993 color: var(--text); }
994 .tpl-preview-foot { padding: 14px 18px; border-top: 1px solid var(--border);
995 display: flex; gap: 10px; align-items: center; justify-content: flex-end;
996 background: var(--bg-panel); }
997 .tpl-preview-foot .grow { flex: 1; }
998 .tpl-preview-foot button { background: var(--bg-subtle);
999 border: 1px solid var(--border); color: var(--text); padding: 7px 14px;
1000 border-radius: var(--radius-sm); cursor: pointer; font-size: 12.5px;
1001 font-family: inherit; transition: border-color .12s; }
1002 .tpl-preview-foot button:hover { border-color: var(--accent); }
1003 .tpl-preview-foot button.primary { background: var(--accent);
1004 border-color: var(--accent); color: var(--accent-fg); font-weight: 500;
1005 box-shadow: 0 4px 10px rgba(201, 100, 66, .18); }
1006 .tpl-preview-foot button.primary:hover { background: var(--accent-hover); }
1007 .tpl-preview-foot .meta { font-size: 11.5px; color: var(--text-muted);
1008 font-family: var(--font-mono); }
1009 .gallery-card .meta { padding: 12px 14px; flex: 0 0 auto; }
1010 .gallery-card .meta .name { font-size: 14px; font-weight: 600; margin-bottom: 4px; }
1011 .gallery-card .meta .desc { font-size: 11.5px; color: var(--text-muted);
1012 line-height: 1.5; display: -webkit-box; -webkit-line-clamp: 2;
1013 -webkit-box-orient: vertical; overflow: hidden; }
1014 .gallery-card .meta .tags { margin-top: 8px; display: flex; gap: 4px;
1015 flex-wrap: wrap; }
1016 .gallery-card .meta .tag { font-size: 9.5px; padding: 1px 7px;
1017 border-radius: var(--radius-pill); background: var(--bg-subtle); color: var(--text-muted);
1018 font-family: var(--font-mono); }
1019
1020 /* ===== Modal: new project ===== */
1021 .small-modal .modal { min-width: 440px; max-width: 520px; max-height: none; }
1022 .small-modal h2 { font-family: var(--font-serif); font-size: 20px; margin-bottom: 18px;
1023 font-weight: 500; }
1024 .small-modal input { width: 100%; background: var(--bg); border: 1px solid var(--border);
1025 color: var(--text); padding: 10px 12px; border-radius: var(--radius-sm);
1026 margin-bottom: 12px; font-size: 14px; }
1027 .small-modal input:focus { outline: none; border-color: var(--accent);
1028 box-shadow: 0 0 0 3px var(--accent-soft); }
1029 .small-modal .actions { display: flex; gap: 8px; justify-content: flex-end; margin-top: 12px; }
1030 .small-modal button { background: var(--bg-panel); border: 1px solid var(--border);
1031 color: var(--text); padding: 8px 16px; border-radius: var(--radius-sm); cursor: pointer;
1032 font-size: 13px; }
1033 .small-modal button:hover { border-color: var(--accent); }
1034 .small-modal button.primary { background: var(--accent); color: var(--accent-fg);
1035 border-color: var(--accent); font-weight: 500; }
1036 .small-modal button.primary:hover { background: var(--accent-hover); }
1037 </style>
1038 </head>
1039 <body>
1040
1041 <div class="app">
1042 <header class="toolbar">
1043 <div class="brand">
1044 <div class="logo">H</div>
1045 <div>
1046 <h1>html-video</h1>
1047 <div class="v">studio · v0.7</div>
1048 </div>
1049 </div>
1050
1051 <input class="name-input" id="proj-name" data-i18n-attr="placeholder:app.no_project" disabled />
1052
1053 <div class="toolbar-section">
1054 <span class="sec-label" data-i18n="toolbar.template">Template</span>
1055 <button class="pick-btn empty" id="btn-pick-template" disabled>
1056 <span class="label">— choose —</span><span class="arrow">▾</span>
1057 </button>
1058 </div>
1059
1060 <div class="toolbar-section agent-switch" id="agent-switch">
1061 <span class="sec-label" data-i18n="toolbar.agent">Agent</span>
1062 <button class="pick-btn agent-pill" id="btn-agent">
1063 <span class="agent-dot" id="agent-dot"></span>
1064 <span class="agent-pill-logo" id="agent-pill-logo"></span>
1065 <span class="label" id="agent-pill-label">—</span><span class="arrow">▾</span>
1066 </button>
1067 <div class="agent-menu" id="agent-menu" hidden></div>
1068 </div>
1069
1070 <!-- Model picker — only shown for agents that expose a model catalog (AMR). -->
1071 <div class="toolbar-section model-switch" id="model-switch" hidden>
1072 <span class="sec-label" data-i18n="toolbar.model">Model</span>
1073 <select class="model-select" id="model-select"></select>
1074 </div>
1075
1076 <div class="grow"></div>
1077
1078 <div class="toolbar-section toolbar-links">
1079 <a class="tb-link" href="https://github.com/nexu-io/html-video" target="_blank" rel="noopener" title="html-video on GitHub" aria-label="html-video repo">
1080 <svg viewBox="0 0 24 24" width="14" height="14" fill="currentColor" aria-hidden="true"><path d="M12 .5a11.5 11.5 0 0 0-3.63 22.42c.57.1.78-.25.78-.55v-2.13c-3.18.69-3.85-1.36-3.85-1.36-.52-1.32-1.27-1.67-1.27-1.67-1.04-.71.08-.7.08-.7 1.15.08 1.76 1.18 1.76 1.18 1.02 1.76 2.69 1.25 3.34.96.1-.74.4-1.25.72-1.54-2.54-.29-5.21-1.27-5.21-5.66 0-1.25.45-2.27 1.18-3.07-.12-.29-.51-1.46.11-3.04 0 0 .96-.31 3.15 1.17a10.92 10.92 0 0 1 5.74 0c2.18-1.48 3.15-1.17 3.15-1.17.62 1.58.23 2.75.11 3.04.74.8 1.18 1.82 1.18 3.07 0 4.4-2.68 5.36-5.23 5.65.41.36.78 1.06.78 2.13v3.16c0 .31.21.66.79.55A11.5 11.5 0 0 0 12 .5z"/></svg>
1081 <span class="tb-link-label">html-video</span>
1082 </a>
1083 <a class="tb-link" href="https://github.com/nexu-io/open-design" target="_blank" rel="noopener" title="Open Design on GitHub" aria-label="Open Design repo">
1084 <svg viewBox="0 0 24 24" width="14" height="14" fill="currentColor" aria-hidden="true"><path d="M12 .5a11.5 11.5 0 0 0-3.63 22.42c.57.1.78-.25.78-.55v-2.13c-3.18.69-3.85-1.36-3.85-1.36-.52-1.32-1.27-1.67-1.27-1.67-1.04-.71.08-.7.08-.7 1.15.08 1.76 1.18 1.76 1.18 1.02 1.76 2.69 1.25 3.34.96.1-.74.4-1.25.72-1.54-2.54-.29-5.21-1.27-5.21-5.66 0-1.25.45-2.27 1.18-3.07-.12-.29-.51-1.46.11-3.04 0 0 .96-.31 3.15 1.17a10.92 10.92 0 0 1 5.74 0c2.18-1.48 3.15-1.17 3.15-1.17.62 1.58.23 2.75.11 3.04.74.8 1.18 1.82 1.18 3.07 0 4.4-2.68 5.36-5.23 5.65.41.36.78 1.06.78 2.13v3.16c0 .31.21.66.79.55A11.5 11.5 0 0 0 12 .5z"/></svg>
1085 <span class="tb-link-label">Open Design</span>
1086 </a>
1087 <a class="tb-link tb-link-icon-only" href="https://discord.com/invite/keeVPMrueT" target="_blank" rel="noopener" title="Discord" aria-label="Discord">
1088 <svg viewBox="0 0 24 24" width="14" height="14" fill="currentColor" aria-hidden="true"><path d="M20.317 4.37a19.79 19.79 0 0 0-4.885-1.515.074.074 0 0 0-.079.037c-.21.375-.444.864-.608 1.25a18.27 18.27 0 0 0-5.487 0 12.64 12.64 0 0 0-.617-1.25.077.077 0 0 0-.079-.037A19.74 19.74 0 0 0 3.683 4.37a.07.07 0 0 0-.032.027C.533 9.046-.32 13.58.099 18.057a.082.082 0 0 0 .031.057 19.9 19.9 0 0 0 5.993 3.03.078.078 0 0 0 .084-.028 14.09 14.09 0 0 0 1.226-1.994.076.076 0 0 0-.041-.106 13.107 13.107 0 0 1-1.872-.892.077.077 0 0 1-.008-.128c.126-.094.252-.192.372-.291a.074.074 0 0 1 .077-.01c3.928 1.793 8.18 1.793 12.062 0a.074.074 0 0 1 .078.009c.12.099.246.198.373.292a.077.077 0 0 1-.006.127 12.299 12.299 0 0 1-1.873.892.077.077 0 0 0-.04.107c.36.698.772 1.362 1.225 1.993a.076.076 0 0 0 .084.028 19.84 19.84 0 0 0 6.002-3.03.077.077 0 0 0 .032-.054c.5-5.177-.838-9.674-3.549-13.66a.061.061 0 0 0-.031-.03zM8.02 15.331c-1.183 0-2.157-1.085-2.157-2.42 0-1.333.956-2.42 2.157-2.42 1.21 0 2.176 1.097 2.157 2.42 0 1.335-.956 2.42-2.157 2.42zm7.974 0c-1.183 0-2.157-1.085-2.157-2.42 0-1.333.955-2.42 2.157-2.42 1.21 0 2.176 1.097 2.157 2.42 0 1.335-.946 2.42-2.157 2.42z"/></svg>
1089 </a>
1090 </div>
1091
1092 <button class="icon-btn-toolbar" id="btn-settings" title="Settings" aria-label="Settings">
1093 <svg viewBox="0 0 24 24" width="16" height="16" fill="none" stroke="currentColor" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><circle cx="12" cy="12" r="3"/><path d="M19.4 15a1.65 1.65 0 0 0 .33 1.82l.06.06a2 2 0 0 1 0 2.83 2 2 0 0 1-2.83 0l-.06-.06a1.65 1.65 0 0 0-1.82-.33 1.65 1.65 0 0 0-1 1.51V21a2 2 0 0 1-4 0v-.09A1.65 1.65 0 0 0 9 19.4a1.65 1.65 0 0 0-1.82.33l-.06.06a2 2 0 0 1-2.83 0 2 2 0 0 1 0-2.83l.06-.06a1.65 1.65 0 0 0 .33-1.82 1.65 1.65 0 0 0-1.51-1H3a2 2 0 0 1 0-4h.09A1.65 1.65 0 0 0 4.6 9a1.65 1.65 0 0 0-.33-1.82l-.06-.06a2 2 0 0 1 0-2.83 2 2 0 0 1 2.83 0l.06.06a1.65 1.65 0 0 0 1.82.33H9a1.65 1.65 0 0 0 1-1.51V3a2 2 0 0 1 4 0v.09a1.65 1.65 0 0 0 1 1.51 1.65 1.65 0 0 0 1.82-.33l.06-.06a2 2 0 0 1 2.83 0 2 2 0 0 1 0 2.83l-.06.06a1.65 1.65 0 0 0-.33 1.82V9a1.65 1.65 0 0 0 1.51 1H21a2 2 0 0 1 0 4h-.09a1.65 1.65 0 0 0-1.51 1z"/></svg>
1094 </button>
1095
1096 <button class="export-btn" id="btn-export" disabled data-i18n="toolbar.export_mp4">Export MP4</button>
1097 </header>
1098
1099 <div class="body" id="body">
1100 <aside class="sidebar">
1101 <div class="sidebar-head">
1102 <h2>Projects</h2>
1103 <button class="new-project" id="btn-new">+ New</button>
1104 <button class="sidebar-toggle" id="btn-sidebar-toggle" title="Collapse sidebar">‹</button>
1105 </div>
1106 <div class="project-list" id="project-list"></div>
1107 </aside>
1108 <div class="empty-state">
1109 <div>
1110 <div class="ico">🎬</div>
1111 <h2>Pick or create a project</h2>
1112 <p>Each project = one HTML video. Choose a template to see the visual baseline, chat with your agent to drive the content, edit per-frame text in the middle column, see the result on the right.</p>
1113 </div>
1114 </div>
1115 </div>
1116 </div>
1117
1118 <div class="toast" id="toast"></div>
1119
1120 <div class="modal-bg small-modal" id="new-modal">
1121 <div class="modal">
1122 <h2 data-i18n="modal.new.title">New project</h2>
1123 <input id="new-name" data-i18n-attr="placeholder:modal.new.name_placeholder" autofocus />
1124 <input id="new-intent" data-i18n-attr="placeholder:modal.new.intent_placeholder" />
1125 <div class="actions">
1126 <button id="new-cancel" data-i18n="modal.new.cancel">Cancel</button>
1127 <button class="primary" id="new-ok" data-i18n="modal.new.create">Create</button>
1128 </div>
1129 </div>
1130 </div>
1131
1132 <div class="modal-bg" id="gallery-modal">
1133 <div class="modal">
1134 <div class="modal-head">
1135 <h2 data-i18n="gallery.title">Pick a template</h2>
1136 <button class="modal-close" id="gallery-close" data-i18n="gallery.close">Close</button>
1137 </div>
1138 <div class="gallery" id="gallery"></div>
1139 </div>
1140 </div>
1141
1142 <div class="modal-bg tpl-preview-modal" id="tpl-preview-modal">
1143 <div class="modal">
1144 <div class="tpl-preview-head">
1145 <div class="name" id="tpl-preview-name">—</div>
1146 <div class="desc" id="tpl-preview-desc"></div>
1147 <button class="modal-close" id="tpl-preview-close" aria-label="Close">✕</button>
1148 </div>
1149 <div class="tpl-preview-stage">
1150 <div class="tpl-preview-frame" id="tpl-preview-frame">
1151 <iframe id="tpl-preview-iframe" sandbox="allow-scripts allow-same-origin" src="about:blank"></iframe>
1152 <img id="tpl-preview-poster" class="poster" alt="" hidden />
1153 </div>
1154 </div>
1155 <div class="tpl-preview-source" id="tpl-preview-source" hidden></div>
1156 <div class="tpl-preview-foot">
1157 <div class="meta" id="tpl-preview-meta"></div>
1158 <div class="grow"></div>
1159 <button id="tpl-preview-cancel" data-i18n="tpl_preview.cancel">Cancel</button>
1160 <button class="primary" id="tpl-preview-use" data-i18n="tpl_preview.use">Use this template</button>
1161 </div>
1162 </div>
1163 </div>
1164
1165 <div class="modal-bg settings-modal" id="settings-modal">
1166 <div class="modal settings">
1167 <div class="settings-head">
1168 <h2 data-i18n="settings.title">Settings</h2>
1169 <button class="modal-close" id="settings-close" aria-label="Close">✕</button>
1170 </div>
1171 <div class="settings-body">
1172 <nav class="settings-nav">
1173 <button class="settings-nav-item active" data-settings-tab="agent">
1174 <svg viewBox="0 0 24 24" width="14" height="14" fill="none" stroke="currentColor" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M4 21V12M4 6V3M12 21v-9M12 8V3M20 21v-5M20 12V3M2 12h4M10 8h4M18 16h4"/></svg>
1175 <span data-i18n="settings.tab.agent">Agent</span>
1176 </button>
1177 <button class="settings-nav-item" data-settings-tab="audio">
1178 <svg viewBox="0 0 24 24" width="14" height="14" fill="none" stroke="currentColor" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M3 10v4M7 7v10M11 4v16M15 8v8M19 11v2"/></svg>
1179 <span data-i18n="settings.tab.audio">Audio</span>
1180 </button>
1181 <button class="settings-nav-item" data-settings-tab="language">
1182 <svg viewBox="0 0 24 24" width="14" height="14" fill="none" stroke="currentColor" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M5 8h12M9 4v4M14 13l-4 7M14 13l4 7M11 17h6"/></svg>
1183 <span data-i18n="settings.tab.language">Language</span>
1184 </button>
1185 <button class="settings-nav-item" data-settings-tab="about">
1186 <svg viewBox="0 0 24 24" width="14" height="14" fill="none" stroke="currentColor" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><circle cx="12" cy="12" r="9"/><path d="M12 8v.01M11 12h1v4h1"/></svg>
1187 <span data-i18n="settings.tab.about">About</span>
1188 </button>
1189 </nav>
1190 <div class="settings-panel" id="settings-panel"></div>
1191 </div>
1192 </div>
1193 </div>
1194
1195 <script type="module" src="/app.js?v=0.7"></script>
1196 </body>
1197 </html>
1198
1198 lines HTML