anvilsign in

collin/strudel-claude

main / src / style.css
1/* ---------- Site themes ----------
2 Each theme is one lighting rig: a palette, plus the --haze gradient stack
3 that lights the room. Backgrounds are never neutral grey — they carry the
4 rig's own hue. Switch with data-site-theme on <html> (see themes.js). */
5
6:root {
7 --glow: 0 0 24px rgba(var(--glow-rgb), 0.32);
8 font-family: ui-sans-serif, system-ui, -apple-system, 'Segoe UI', sans-serif;
9}
10
11/* Purple haze — a club rig on a violet wash with a magenta second gel. */
12:root,
13:root[data-site-theme='haze'] {
14 --bg: #0c0912;
15 --panel: rgba(21, 16, 34, 0.72);
16 --panel-2: rgba(30, 22, 48, 0.72);
17 --border: #2f2450;
18 --text: #ece7f6;
19 --muted: #8f86ab;
20 --accent: #b07cff;
21 --accent-2: #ff8ede;
22 --ok: #5fe0b0;
23 --err: #ff6b8f;
24 --on-accent: #140d20;
25 --glow-rgb: 176, 124, 255;
26 --haze:
27 radial-gradient(70vw 55vh at 20% 0%, rgba(150, 95, 255, 0.5), transparent 70%),
28 radial-gradient(50vw 45vh at 92% 12%, rgba(255, 105, 205, 0.26), transparent 68%),
29 radial-gradient(85vw 60vh at 55% 108%, rgba(110, 70, 220, 0.38), transparent 72%);
30}
31
32/* Ember — low warm wash, the room at 4am. */
33:root[data-site-theme='ember'] {
34 --bg: #100a09;
35 --panel: rgba(31, 18, 15, 0.72);
36 --panel-2: rgba(44, 25, 20, 0.72);
37 --border: #4a2a20;
38 --text: #f6e9e2;
39 --muted: #ab938a;
40 --accent: #ff9d5c;
41 --accent-2: #ff6b8f;
42 --ok: #d9d15f;
43 --err: #ff5f6b;
44 --on-accent: #1f0f08;
45 --glow-rgb: 255, 157, 92;
46 --haze:
47 radial-gradient(70vw 55vh at 18% 0%, rgba(255, 120, 50, 0.42), transparent 70%),
48 radial-gradient(50vw 45vh at 92% 12%, rgba(255, 55, 105, 0.26), transparent 66%),
49 radial-gradient(85vw 60vh at 58% 108%, rgba(190, 65, 40, 0.34), transparent 72%);
50}
51
52/* Laser — a single green beam cutting the smoke. */
53:root[data-site-theme='laser'] {
54 --bg: #06100c;
55 --panel: rgba(10, 27, 21, 0.72);
56 --panel-2: rgba(15, 38, 30, 0.72);
57 --border: #1d4a3a;
58 --text: #e2f6ec;
59 --muted: #7fa695;
60 --accent: #4fe6a0;
61 --accent-2: #6fd9ff;
62 --ok: #4fe6a0;
63 --err: #ff7a8f;
64 --on-accent: #04170f;
65 --glow-rgb: 79, 230, 160;
66 --haze:
67 radial-gradient(70vw 55vh at 22% 0%, rgba(45, 220, 150, 0.36), transparent 70%),
68 radial-gradient(50vw 45vh at 90% 12%, rgba(60, 190, 255, 0.22), transparent 66%),
69 radial-gradient(85vw 60vh at 55% 108%, rgba(25, 150, 115, 0.32), transparent 72%);
70}
71
72/* Blackout — house lights off, no wash at all. For when the glow is too much. */
73:root[data-site-theme='blackout'] {
74 --bg: #0b0b0d;
75 --panel: rgba(20, 20, 24, 0.82);
76 --panel-2: rgba(28, 28, 33, 0.82);
77 --border: #32323a;
78 --text: #e8e8ee;
79 --muted: #8b8b98;
80 --accent: #9aa4ff;
81 --accent-2: #ff9ec4;
82 --ok: #6bd68a;
83 --err: #ff6b6b;
84 --on-accent: #101018;
85 --glow-rgb: 154, 164, 255;
86 --haze: none;
87}
88
89* { box-sizing: border-box; }
90html, body { height: 100%; margin: 0; }
91body { background: var(--bg); color: var(--text); }
92
93/* ---------- The haze ----------
94 The signature: light in a club is in the air, not on the surfaces. One fixed
95 atmospheric layer sits behind every panel; the panels are translucent, so the
96 same wash reads through each one at a different strength. Nothing else in the
97 UI carries a drop shadow — this layer is where the glow lives. */
98body::before {
99 content: '';
100 position: fixed;
101 /* Exactly the viewport: the gradients' percentage positions are relative to
102 this box, so any inset would slide the beams off-screen. They fall off well
103 before the edges, so nothing needs to bleed past it. */
104 inset: 0;
105 z-index: -1;
106 pointer-events: none;
107 background: var(--haze);
108 transition: opacity 0.6s ease;
109 opacity: 0.8;
110}
111
112/* Lights follow the track: the haze only breathes while a pattern is running.
113 Slow enough to read as atmosphere rather than a strobe. */
114body.live::before {
115 opacity: 1;
116 animation: haze-breathe 6.4s ease-in-out infinite;
117}
118@keyframes haze-breathe {
119 0%, 100% { opacity: 0.82; transform: scale(1); }
120 50% { opacity: 1; transform: scale(1.045); }
121}
122@media (prefers-reduced-motion: reduce) {
123 body.live::before { animation: none; }
124}
125
126/* Visualizer layer. Strudel's draw context creates this canvas itself and
127 prepends it to <body>, fixed over the whole viewport with no z-index of its
128 own — so .punchcard(), .scope(), .pianoroll() and friends all draw here.
129 Without an explicit layer it lands in the same stacking context as an
130 unpositioned #app and paints inconsistently: over the panels, but under the
131 bubbles (which are position: relative), depending only on source order.
132 Pinning both settles it — the canvas sits above the haze and below the UI, so
133 a visualization fills the transparent editor column and reads softly through
134 the translucent chat and sidebar. Same arrangement strudel.cc uses.
135 Note: the .code-light themes give #editor an opaque background (see below),
136 which covers the canvas in the editor column. */
137#test-canvas {
138 z-index: 0;
139 /* Strudel draws these at full strength, which is right on a bare page but
140 here puts near-white punchcard cells directly under the code. Screen blend
141 drops the dark half of every visualizer out entirely and lets the lit half
142 glow through the background, so it reads as part of the room's lighting
143 rather than a sheet laid over the editor. The opacity is what keeps the
144 code legible on top of a busy frame. */
145 opacity: 0.55;
146 mix-blend-mode: screen;
147}
148/* Screen blows out to white against a light code theme — darken instead so the
149 visualizer still registers. */
150body.code-light #test-canvas {
151 mix-blend-mode: multiply;
152}
153
154#app {
155 position: relative;
156 z-index: 1;
157 display: grid;
158 /* minmax(0, 1fr) lets the editor column shrink below its content width
159 (CodeMirror scrolls internally) instead of pushing the chat off-screen. */
160 grid-template-columns: 232px minmax(0, 1fr) 420px;
161 height: 100vh;
162}
163/* Collapsed: the sidebar keeps its grid slot at zero width — `display: none`
164 would drop it from the grid and slide the editor into that empty column.
165 `visibility: hidden` also takes its buttons out of the tab order. */
166#app.sidebar-collapsed { grid-template-columns: 0 minmax(0, 1fr) 420px; }
167#app.sidebar-collapsed #sidebar { visibility: hidden; border-right: none; }
168
169section,
170aside {
171 display: flex;
172 flex-direction: column;
173 min-height: 0;
174}
175#ide { border-right: 1px solid var(--border); }
176#chat { background: var(--panel); backdrop-filter: blur(12px); }
177
178.pane-header {
179 display: flex;
180 align-items: center;
181 justify-content: space-between;
182 gap: 12px;
183 /* Both panes' headers share this height so their bottom borders line up. */
184 min-height: 57px;
185 padding: 12px 16px;
186 border-bottom: 1px solid var(--border);
187 background: var(--panel-2);
188 backdrop-filter: blur(12px);
189}
190.logo { font-weight: 700; letter-spacing: 0.2px; }
191.subtitle { color: var(--muted); font-size: 13px; }
192
193/* Editor — StrudelMirror mounts its CodeMirror inside #editor */
194#editor { flex: 1; min-height: 0; overflow: auto; position: relative; }
195/* The code pane shares the site's background instead of the code theme's, so
196 the haze runs unbroken across all three panes. StrudelMirror sets
197 `background-color: var(--background)` inline and defines --background on
198 <html> from JS, so only !important can clear it.
199 Exception: a light code theme needs its own light background to stay
200 readable — .code-light (set in themes.js) opts back out. */
201body:not(.code-light) #editor { background-color: transparent !important; }
202body:not(.code-light) #editor .cm-editor,
203body:not(.code-light) #editor .cm-editor .cm-scroller,
204body:not(.code-light) #editor .cm-gutters { background: transparent; }
205#editor .cm-editor { height: 100%; font-size: 15px; }
206#editor .cm-scroller { font-family: ui-monospace, 'SF Mono', Menlo, monospace; }
207
208.status {
209 display: flex;
210 justify-content: space-between;
211 align-items: center;
212 gap: 12px;
213 padding: 6px 16px;
214 /* Rack-unit lettering: small, tracked, uppercase — the vernacular of the
215 gear this thing stands in for. */
216 font-size: 11px;
217 letter-spacing: 0.09em;
218 text-transform: uppercase;
219 color: var(--muted);
220 border-top: 1px solid var(--border);
221 background: var(--panel-2);
222 backdrop-filter: blur(12px);
223}
224#ide-status { min-width: 0; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
225#ide-status.ok { color: var(--ok); }
226#ide-status.err { color: var(--err); }
227
228/* Buttons */
229.transport { display: flex; gap: 8px; }
230.btn {
231 background: var(--panel);
232 color: var(--text);
233 border: 1px solid var(--border);
234 border-radius: 8px;
235 padding: 7px 14px;
236 font-size: 13px;
237 font-weight: 600;
238 cursor: pointer;
239 transition: background 0.15s, border-color 0.15s;
240}
241.btn:hover { background: var(--panel-2); border-color: var(--accent); }
242.btn:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
243/* One transport button: mint "Play" when idle, pink "Stop" while running.
244 Running is the only state that lights up — it is the one the room reacts to. */
245.btn-transport { min-width: 92px; color: var(--ok); }
246.btn-transport:hover { border-color: var(--ok); }
247.btn-transport.playing {
248 color: var(--err);
249 border-color: var(--err);
250 box-shadow: 0 0 20px color-mix(in srgb, var(--err) 30%, transparent);
251}
252.btn-send { background: var(--accent); color: var(--on-accent); border: none; }
253.btn-send:hover { background: #c398ff; box-shadow: var(--glow); }
254.btn-ghost { padding: 5px 9px; font-size: 13px; color: var(--muted); }
255.btn-ghost:hover { color: var(--text); border-color: var(--accent); }
256
257/* ---------- Session sidebar ---------- */
258#sidebar {
259 background: var(--panel-2);
260 backdrop-filter: blur(12px);
261 border-right: 1px solid var(--border);
262 overflow: hidden;
263}
264.sidebar-header { background: none; }
265.sidebar-header .logo { font-size: 14px; white-space: nowrap; }
266.ide-title { display: flex; align-items: center; gap: 8px; min-width: 0; }
267
268/* The song's name, in the pane that holds the song. Reads as a plain title
269 until hovered, where a faint field outline reveals it's the rename control. */
270.song-title {
271 min-width: 0;
272 max-width: 46ch;
273 padding: 5px 9px;
274 background: none;
275 border: 1px solid transparent;
276 border-radius: 8px;
277 color: var(--text);
278 font: 700 15px inherit;
279 letter-spacing: 0.2px;
280 text-align: left;
281 white-space: nowrap;
282 overflow: hidden;
283 text-overflow: ellipsis;
284 cursor: text;
285}
286.song-title:hover { border-color: var(--border); background: var(--panel); }
287.song-title:focus-visible { outline: 2px solid var(--accent); outline-offset: 1px; }
288
289.song-title-edit {
290 min-width: 0;
291 width: 46ch;
292 max-width: 100%;
293 padding: 5px 9px;
294 background: var(--bg);
295 color: var(--text);
296 border: 1px solid var(--accent);
297 border-radius: 8px;
298 font: 700 15px inherit;
299 letter-spacing: 0.2px;
300}
301.song-title-edit:focus { outline: none; box-shadow: var(--glow); }
302
303.new-session {
304 margin: 10px 10px 6px;
305 padding: 9px 12px;
306 display: flex;
307 align-items: center;
308 gap: 8px;
309 background: var(--panel);
310 color: var(--text);
311 border: 1px solid var(--border);
312 border-radius: 10px;
313 font: 600 13px inherit;
314 cursor: pointer;
315 transition: background 0.15s, border-color 0.15s;
316}
317.new-session:hover { background: var(--bg); border-color: var(--accent); }
318
319.session-list {
320 flex: 1;
321 min-height: 0;
322 overflow-y: auto;
323 padding: 4px 6px 12px;
324 display: flex;
325 flex-direction: column;
326 gap: 1px;
327}
328
329.session-row {
330 position: relative;
331 display: flex;
332 align-items: center;
333 border-radius: 8px;
334}
335.session-row:hover { background: var(--panel); }
336.session-row.active { background: var(--panel); }
337/* The active track gets a lit channel-strip LED down its left edge. */
338.session-row.active::before {
339 content: '';
340 position: absolute;
341 left: 0;
342 top: 6px;
343 bottom: 6px;
344 width: 2px;
345 border-radius: 2px;
346 background: var(--accent);
347 box-shadow: 0 0 10px rgba(var(--glow-rgb), 0.7);
348}
349
350.session-open {
351 flex: 1;
352 min-width: 0;
353 padding: 9px 6px 9px 12px;
354 background: none;
355 border: none;
356 color: var(--muted);
357 font: 400 13px inherit;
358 text-align: left;
359 white-space: nowrap;
360 overflow: hidden;
361 text-overflow: ellipsis;
362 cursor: pointer;
363}
364.session-row.active .session-open { color: var(--text); font-weight: 600; }
365.session-open:focus-visible { outline: 1px solid var(--accent); outline-offset: -2px; }
366
367/* Per-row actions appear on hover / keyboard focus only */
368.session-actions { display: flex; gap: 0; padding-right: 4px; opacity: 0; }
369.session-row:hover .session-actions,
370.session-actions:focus-within { opacity: 1; }
371.session-actions .icon-btn { width: 24px; height: 24px; font-size: 12px; opacity: 0.7; }
372
373.session-rename {
374 flex: 1;
375 min-width: 0;
376 margin: 4px;
377 padding: 5px 8px;
378 background: var(--bg);
379 color: var(--text);
380 border: 1px solid var(--accent);
381 border-radius: 6px;
382 font: 400 13px inherit;
383}
384.session-rename:focus { outline: none; }
385
386/* Theme control, pinned to the bottom of the sidebar. The model and key
387 controls use the same chip + dropdown parts, but live in the chat header
388 (see .chat-header below) — the two settings the chat depends on sit above
389 the conversation they govern. */
390.sidebar-footer { position: relative; padding: 8px; border-top: 1px solid var(--border); }
391.sidebar-footer .key-btn { width: 100%; }
392.key-btn {
393 display: flex;
394 align-items: center;
395 gap: 8px;
396 padding: 8px 10px;
397 background: none;
398 border: 1px solid transparent;
399 border-radius: 8px;
400 color: var(--muted);
401 font: 400 12px inherit;
402 text-align: left;
403 cursor: pointer;
404}
405.key-btn:hover { background: var(--panel); color: var(--text); }
406.key-btn #key-state { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
407/* No key anywhere — nothing will work until one is set, so say so */
408.key-btn.key-missing { color: var(--accent-2); border-color: var(--accent-2); }
409
410.key-panel {
411 position: absolute;
412 left: 8px;
413 right: 8px;
414 bottom: calc(100% - 2px);
415 z-index: 20;
416 padding: 12px;
417 /* The panel tint over an opaque base: every other surface here is
418 translucent so the haze reads through, but a popover floats over live
419 content (a transcript, a track list) and needs to hide it. */
420 background: linear-gradient(var(--panel-2), var(--panel-2)), var(--bg);
421 border: 1px solid var(--border);
422 border-radius: 10px;
423 box-shadow: 0 12px 28px rgba(0, 0, 0, 0.5);
424}
425.key-label { display: block; margin-bottom: 6px; font-size: 12px; font-weight: 600; }
426.key-input {
427 width: 100%;
428 padding: 8px;
429 background: var(--bg);
430 color: var(--text);
431 border: 1px solid var(--border);
432 border-radius: 8px;
433 font: 400 12px ui-monospace, monospace;
434}
435.key-input:focus { outline: none; border-color: var(--accent); }
436.key-note { margin: 8px 0 10px; font-size: 11px; line-height: 1.4; color: var(--muted); }
437.key-label-spaced { margin-top: 12px; }
438
439.key-select {
440 width: 100%;
441 padding: 7px 8px;
442 background: var(--bg);
443 color: var(--text);
444 border: 1px solid var(--border);
445 border-radius: 8px;
446 font: 400 12px inherit;
447 cursor: pointer;
448}
449.key-select:focus-visible { outline: none; border-color: var(--accent); box-shadow: var(--glow); }
450/* Native option lists paint on the OS surface, not ours — keep them legible. */
451.key-select option { background: var(--bg); color: var(--text); }
452.key-actions { display: flex; gap: 6px; }
453.key-actions .btn { flex: 1; padding: 7px 10px; font-size: 12px; }
454
455/* ---------- Chat header ----------
456 Two chips sharing the row, each opening its panel downward over the
457 transcript. Anchored on the header itself so the panels line up with the
458 chips rather than with the chat column's edges. */
459.chat-header {
460 position: relative;
461 /* The header's backdrop-filter makes it a stacking context, so the panels'
462 own z-index can't lift them past the transcript — the header has to be
463 raised as a whole (bubbles are positioned, hence in the same layer). */
464 z-index: 5;
465 justify-content: flex-start;
466 gap: 8px;
467}
468.chat-header .key-btn {
469 flex: 1;
470 min-width: 0;
471 border-color: var(--border);
472 background: var(--panel);
473}
474.chat-header .key-btn:hover { background: var(--bg); color: var(--text); }
475/* Restated here: the border-color above would otherwise win on source order
476 and swallow the "no key set" warning state. */
477.chat-header .key-btn.key-missing { color: var(--accent-2); border-color: var(--accent-2); }
478.chat-header .key-btn span:last-child {
479 min-width: 0;
480 overflow: hidden;
481 text-overflow: ellipsis;
482 white-space: nowrap;
483}
484.chat-header .key-panel {
485 top: calc(100% - 1px);
486 bottom: auto;
487 left: 16px;
488 right: 16px;
489}
490
491/* Quiet icon buttons — they surface when their container is hovered */
492.icon-btn {
493 width: 30px;
494 height: 30px;
495 display: grid;
496 place-items: center;
497 background: none;
498 border: none;
499 border-radius: 8px;
500 color: var(--muted);
501 font-size: 15px;
502 line-height: 1;
503 cursor: pointer;
504 opacity: 0.5;
505 transition: opacity 0.15s, background 0.15s, color 0.15s;
506}
507.sidebar-header:hover .icon-btn,
508.icon-btn:focus-visible { opacity: 1; }
509.icon-btn:hover { background: var(--panel); color: var(--text); }
510.icon-btn[hidden] { display: none; } /* display:grid above would otherwise win */
511.icon-btn.danger:hover { color: var(--err); }
512
513/* Chat */
514#messages {
515 flex: 1;
516 min-height: 0;
517 overflow-y: auto;
518 padding: 16px;
519 display: flex;
520 flex-direction: column;
521 gap: 12px;
522}
523.bubble {
524 max-width: 90%;
525 padding: 10px 14px;
526 border-radius: 14px;
527 font-size: 14px;
528 line-height: 1.45;
529 white-space: pre-wrap;
530 word-wrap: break-word;
531}
532.bubble.user {
533 align-self: flex-end;
534 background: var(--accent);
535 color: var(--on-accent);
536 border-bottom-right-radius: 4px;
537 box-shadow: 0 0 22px rgba(var(--glow-rgb), 0.22);
538}
539.bubble.assistant {
540 align-self: flex-start;
541 background: var(--panel-2);
542 border: 1px solid var(--border);
543 border-bottom-left-radius: 4px;
544}
545.bubble.err { color: var(--err); border-color: var(--err); }
546
547/* Revert control — sits just outside the right edge of an assistant bubble */
548.bubble.assistant { position: relative; }
549.bubble .revert {
550 position: absolute;
551 right: -30px;
552 top: 50%;
553 transform: translateY(-50%);
554 width: 24px;
555 height: 24px;
556 padding: 0;
557 display: grid;
558 place-items: center;
559 background: transparent;
560 color: var(--muted);
561 border: 1px solid transparent;
562 border-radius: 50%;
563 font-size: 13px;
564 line-height: 1;
565 cursor: pointer;
566 opacity: 0;
567 transition: opacity 0.15s, color 0.15s, border-color 0.15s;
568}
569.bubble.assistant:hover .revert,
570.bubble .revert:focus-visible { opacity: 1; }
571.bubble .revert:hover { color: var(--accent-2); border-color: var(--accent-2); }
572
573/* Time travel — every message that carries a pattern is a point on the song's
574 timeline, and clicking it takes the editor there. Kept deliberately quiet:
575 the whole transcript is clickable, so a loud affordance on each bubble would
576 read as noise. The lift on hover is the hint; the ring is "you are here". */
577.bubble.travelable {
578 cursor: pointer;
579 transition: transform 0.15s, box-shadow 0.15s, outline-color 0.15s;
580 outline: 1.5px solid transparent;
581 outline-offset: 2px;
582}
583.bubble.travelable:hover { transform: translateY(-1px); }
584.bubble.travelable:focus-visible { outline-color: var(--accent-2); }
585.bubble.travelled {
586 outline-color: var(--accent-2);
587 box-shadow: 0 0 18px rgba(var(--glow-rgb), 0.35);
588}
589
590.undo-link {
591 background: none;
592 border: none;
593 padding: 0;
594 font: inherit;
595 color: var(--accent);
596 cursor: pointer;
597 text-decoration: underline;
598}
599
600/* Animated "Claude is thinking" indicator */
601.bubble.typing {
602 display: flex;
603 gap: 5px;
604 align-items: center;
605 padding: 14px 16px;
606}
607.bubble.typing .dot {
608 width: 7px;
609 height: 7px;
610 border-radius: 50%;
611 background: var(--muted);
612 animation: typing-bounce 1.2s infinite ease-in-out both;
613}
614.bubble.typing .dot:nth-child(2) { animation-delay: 0.15s; }
615.bubble.typing .dot:nth-child(3) { animation-delay: 0.3s; }
616@keyframes typing-bounce {
617 0%, 80%, 100% { transform: translateY(0); opacity: 0.4; }
618 40% { transform: translateY(-5px); opacity: 1; }
619}
620
621#chat-form {
622 padding: 12px;
623 border-top: 1px solid var(--border);
624 background: var(--panel-2);
625 backdrop-filter: blur(12px);
626}
627.composer { display: flex; gap: 8px; }
628#chat-input {
629 flex: 1;
630 resize: none;
631 background: var(--bg);
632 color: var(--text);
633 border: 1px solid var(--border);
634 border-radius: 8px;
635 padding: 10px;
636 font-size: 14px;
637 line-height: 1.45;
638 font-family: inherit;
639 overflow-y: auto;
640}
641#chat-input:focus {
642 outline: none;
643 border-color: var(--accent);
644 box-shadow: 0 0 0 1px rgba(var(--glow-rgb), 0.35), var(--glow);
645}
646#chat-input::placeholder { color: var(--muted); }
647
648/* ---------- System prompt editor ----------
649 A modal, not a popover: the prompt is a few hundred lines of Markdown and
650 needs most of the window to be readable. */
651.key-btn.prompt-custom { color: var(--accent); }
652
653.modal {
654 position: fixed;
655 inset: 0;
656 z-index: 100;
657 display: grid;
658 place-items: center;
659 padding: 24px;
660 background: rgba(0, 0, 0, 0.55);
661 backdrop-filter: blur(2px);
662}
663.modal[hidden] { display: none; }
664
665.modal-card {
666 display: flex;
667 flex-direction: column;
668 width: min(900px, 100%);
669 height: min(80vh, 100%);
670 padding: 16px;
671 background: linear-gradient(var(--panel-2), var(--panel-2)), var(--bg);
672 border: 1px solid var(--border);
673 border-radius: 12px;
674 box-shadow: 0 24px 60px rgba(0, 0, 0, 0.6);
675}
676.modal-header {
677 display: flex;
678 align-items: center;
679 justify-content: space-between;
680 margin-bottom: 10px;
681}
682.modal-header h2 { margin: 0; font-size: 14px; font-weight: 600; }
683/* The close button lives in a header that isn't hovered as a whole, so it can't
684 rely on the usual reveal-on-hover of .icon-btn. */
685.modal-header .icon-btn { opacity: 1; }
686
687.prompt-text {
688 flex: 1;
689 min-height: 0;
690 width: 100%;
691 padding: 12px;
692 resize: none;
693 background: var(--bg);
694 color: var(--text);
695 border: 1px solid var(--border);
696 border-radius: 8px;
697 font: 400 12px/1.6 ui-monospace, monospace;
698 tab-size: 2;
699}
700.prompt-text:focus { outline: none; border-color: var(--accent); box-shadow: var(--glow); }
701.modal-card .key-actions { justify-content: flex-end; }
702.modal-card .key-actions .btn { flex: 0 0 auto; }