| 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 |