litura-app 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,849 @@
1
+ @font-face {
2
+ font-family: 'iA Writer Duo';
3
+ src: url('/fonts/iAWriterDuoS-Regular.woff2') format('woff2');
4
+ font-weight: 400;
5
+ font-style: normal;
6
+ }
7
+ @font-face {
8
+ font-family: 'iA Writer Duo';
9
+ src: url('/fonts/iAWriterDuoS-Italic.woff2') format('woff2');
10
+ font-weight: 400;
11
+ font-style: italic;
12
+ }
13
+ @font-face {
14
+ font-family: 'iA Writer Duo';
15
+ src: url('/fonts/iAWriterDuoS-Bold.woff2') format('woff2');
16
+ font-weight: 700;
17
+ font-style: normal;
18
+ }
19
+ @font-face {
20
+ font-family: 'iA Writer Duo';
21
+ src: url('/fonts/iAWriterDuoS-BoldItalic.woff2') format('woff2');
22
+ font-weight: 700;
23
+ font-style: italic;
24
+ }
25
+
26
+ *, *::before, *::after {
27
+ box-sizing: border-box;
28
+ margin: 0;
29
+ padding: 0;
30
+ }
31
+
32
+ /*
33
+ * Design tokens lifted from chatgpt.com (OpenAI design system).
34
+ * Prose keeps iA Writer Duo (--font); every other surface uses --ui-font.
35
+ */
36
+ /* The page is warm paper; chat blocks are white cards on top of it. That
37
+ contrast is what separates the conversation from the draft, so the cards
38
+ need no shadow to lift off. */
39
+ :root {
40
+ --bg: #f7f5f0; /* surface-canvas — warm paper */
41
+ --surface: #f7f5f0; /* surface-primary */
42
+ --surface-2: #f0ede5; /* hover */
43
+ --surface-3: #e6e2d8; /* pressed / ghost hover */
44
+ --border: #0000001a;
45
+ --border-subtle: #0000000d;
46
+ --border-strong: #00000026;
47
+ --text: #0d0d0d;
48
+ --text-2: #5d5d5d;
49
+ --muted: #8f8f8f;
50
+ --accent: #3a83f7;
51
+ --danger: #911e1b;
52
+ --fill: #eae6dd; /* filled (secondary) button */
53
+ --fill-hover: #ddd8cd;
54
+ --primary-bg: #0d0d0d;
55
+ --primary-fg: #ffffff;
56
+ --primary-hover: #212121;
57
+ --overlay: #ffffff;
58
+ --scrim: #00000073;
59
+
60
+ --shadow-sm: 0 1px 2px 0 #00000014;
61
+ --shadow-md: 0 4px 16px 0 #0000001f;
62
+ --shadow-lg: 0 12px 40px 0 #0000002e;
63
+
64
+ --r-xs: 4px;
65
+ --r-sm: 6px;
66
+ --r-field: 10px;
67
+ --r-overlay: 20px;
68
+ --r-pill: 999px;
69
+
70
+ --ease: cubic-bezier(.16, 1, .3, 1);
71
+
72
+ --ui-font: 'OpenAI Sans', system-ui, -apple-system, 'Segoe UI', sans-serif;
73
+ --font: 'iA Writer Duo', 'Menlo', 'Monaco', 'Courier New', monospace;
74
+ }
75
+
76
+ @media (prefers-color-scheme: dark) {
77
+ :root {
78
+ --bg: #000000;
79
+ --surface: #212121;
80
+ --surface-2: #303030;
81
+ --surface-3: #414141;
82
+ --border: #ffffff26;
83
+ --border-subtle: #ffffff0d;
84
+ --border-strong: #ffffff33;
85
+ --text: #ffffff;
86
+ --text-2: #cdcdcd;
87
+ --muted: #afafaf;
88
+ --accent: #63a8f8;
89
+ --danger: #ff9b97;
90
+ --fill: #414141;
91
+ --fill-hover: #4f4f4f;
92
+ --primary-bg: #ffffff;
93
+ --primary-fg: #0d0d0d;
94
+ --primary-hover: #ececec;
95
+ --overlay: #303030;
96
+
97
+ /* A shadow tuned for paper is invisible on a dark canvas — the floating
98
+ panel then reads as text sitting on top of the draft. */
99
+ --shadow-sm: 0 1px 2px 0 #00000059;
100
+ --shadow-md: 0 6px 20px 0 #00000080;
101
+ --shadow-lg: 0 16px 48px 0 #000000a6;
102
+ }
103
+ }
104
+
105
+ html, body {
106
+ height: 100%;
107
+ overflow: hidden;
108
+ background: var(--bg);
109
+ color: var(--text);
110
+ font-family: var(--ui-font);
111
+ font-size: 14px;
112
+ -webkit-font-smoothing: antialiased;
113
+ }
114
+
115
+ /* ── Scrollbars ───────────────────────────────────────── */
116
+
117
+ * {
118
+ scrollbar-width: thin;
119
+ scrollbar-color: transparent transparent;
120
+ transition: scrollbar-color 0.3s;
121
+ }
122
+
123
+ *:hover {
124
+ scrollbar-color: var(--border-strong) transparent;
125
+ }
126
+
127
+ *::-webkit-scrollbar {
128
+ width: 6px;
129
+ height: 6px;
130
+ }
131
+
132
+ *::-webkit-scrollbar-track {
133
+ background: transparent;
134
+ }
135
+
136
+ *::-webkit-scrollbar-thumb {
137
+ background: transparent;
138
+ border-radius: var(--r-pill);
139
+ transition: background 0.3s;
140
+ }
141
+
142
+ *:hover::-webkit-scrollbar-thumb {
143
+ background: var(--border-strong);
144
+ }
145
+
146
+ *::-webkit-scrollbar-thumb:hover {
147
+ background: var(--muted);
148
+ }
149
+
150
+ /* ── Layout ───────────────────────────────────────────── */
151
+
152
+ .app {
153
+ display: flex;
154
+ height: 100vh;
155
+ overflow: hidden;
156
+ }
157
+
158
+ .pane {
159
+ display: flex;
160
+ flex-direction: column;
161
+ flex: 1;
162
+ min-width: 180px;
163
+ overflow: hidden;
164
+ background: var(--surface);
165
+ }
166
+
167
+ .pane-header {
168
+ position: relative;
169
+ height: 48px;
170
+ display: flex;
171
+ align-items: center;
172
+ gap: 8px;
173
+ padding: 0 12px 0 20px;
174
+ font-size: 13px;
175
+ font-weight: 500;
176
+ letter-spacing: 0;
177
+ text-transform: none;
178
+ color: var(--text-2);
179
+ border-bottom: 1px solid var(--border-subtle);
180
+ background: var(--surface);
181
+ user-select: none;
182
+ flex-shrink: 0;
183
+ }
184
+
185
+ .wordmark {
186
+ color: var(--text);
187
+ font-family: var(--font);
188
+ font-size: 14px;
189
+ font-weight: 700;
190
+ letter-spacing: -.02em;
191
+ }
192
+
193
+ /* Working pane: CodeMirror 6 mounts here */
194
+ #editor-wrapper {
195
+ flex: 1;
196
+ overflow: hidden;
197
+ background: var(--surface);
198
+ }
199
+
200
+ /* CM6 root fills the wrapper */
201
+ #editor-wrapper .cm-editor {
202
+ height: 100%;
203
+ }
204
+
205
+ /* ── Header controls (ChatGPT ghost/pill buttons) ─────── */
206
+
207
+ .icon-btn,
208
+ .settings-open {
209
+ display: inline-flex;
210
+ align-items: center;
211
+ justify-content: center;
212
+ width: 32px;
213
+ height: 32px;
214
+ border: 0;
215
+ border-radius: var(--r-pill);
216
+ background: transparent;
217
+ color: var(--text-2);
218
+ font: inherit;
219
+ font-size: 15px;
220
+ line-height: 1;
221
+ cursor: pointer;
222
+ transition: background var(--motion, .12s) ease, color .12s ease;
223
+ }
224
+
225
+ .icon-btn:hover,
226
+ .settings-open:hover {
227
+ background: var(--surface-3);
228
+ color: var(--text);
229
+ }
230
+
231
+ .agent-status {
232
+ max-width: 42%;
233
+ overflow: hidden;
234
+ text-overflow: ellipsis;
235
+ white-space: nowrap;
236
+ color: var(--muted);
237
+ font-size: 13px;
238
+ font-weight: 400;
239
+ }
240
+
241
+ /* Local style score — computed in the browser, so it updates as you type */
242
+ .style-score {
243
+ margin-left: auto;
244
+ min-width: 24px;
245
+ text-align: right;
246
+ color: var(--muted);
247
+ font-size: 13px;
248
+ font-variant-numeric: tabular-nums;
249
+ }
250
+
251
+ .style-score:empty { margin-left: auto; }
252
+ .style-score.warn { color: var(--accent); }
253
+
254
+ /* Secondary pill button — ChatGPT's action/secondary style */
255
+ .review-button {
256
+ display: inline-flex;
257
+ align-items: center;
258
+ height: 32px;
259
+ padding: 0 12px;
260
+ border: 1px solid var(--border);
261
+ border-radius: var(--r-pill);
262
+ background: var(--surface);
263
+ color: var(--text);
264
+ font: inherit;
265
+ font-size: 13px;
266
+ font-weight: 500;
267
+ letter-spacing: 0;
268
+ text-transform: none;
269
+ cursor: pointer;
270
+ transition: background .12s ease, border-color .12s ease;
271
+ }
272
+
273
+ .review-button:hover { background: var(--surface-2); }
274
+ .review-button:active { background: var(--surface-3); }
275
+ .review-button:disabled { cursor: default; opacity: .5; }
276
+ .review-button.has-findings { color: var(--accent); border-color: var(--accent); }
277
+
278
+ /* A background check is still the writer's money being spent — show it */
279
+ .review-button.is-busy { animation: review-busy 1.4s ease-in-out infinite; }
280
+
281
+ @keyframes review-busy {
282
+ 0%, 100% { border-color: var(--border); }
283
+ 50% { border-color: var(--accent); }
284
+ }
285
+
286
+ .style-score { cursor: pointer; }
287
+ .style-score:empty { cursor: default; }
288
+
289
+ /* ── Chat ─────────────────────────────────────────────── */
290
+ /*
291
+ * Pinned bottom-centre over the canvas, Granola-style: understated at rest,
292
+ * grows upward when it has something to say. Never modal — the draft below
293
+ * stays readable and editable, so there is no scrim.
294
+ */
295
+
296
+ .chat {
297
+ position: fixed;
298
+ left: 50%;
299
+ bottom: 20px;
300
+ transform: translateX(-50%);
301
+ z-index: 100;
302
+ width: min(680px, calc(100vw - 32px));
303
+ display: flex;
304
+ flex-direction: column;
305
+ gap: 10px;
306
+ pointer-events: none;
307
+ }
308
+
309
+ .chat > * { pointer-events: auto; }
310
+
311
+ /* The draft has to stop where the panel starts. Without this backdrop the
312
+ cards sit directly on running text and their edges disappear into it. */
313
+ .chat::before {
314
+ content: '';
315
+ position: absolute;
316
+ inset: -20px -28px -32px;
317
+ z-index: -1;
318
+ pointer-events: none;
319
+ background: linear-gradient(to bottom, transparent, var(--surface) 72px);
320
+ }
321
+
322
+ .chat-stream {
323
+ display: flex;
324
+ flex-direction: column;
325
+ gap: 8px;
326
+ max-height: 46vh;
327
+ overflow-y: auto;
328
+ padding: 4px;
329
+ /* Content clipped at the top should read as scrollable, not as broken. */
330
+ mask-image: linear-gradient(to bottom, transparent, #000 24px);
331
+ }
332
+
333
+ .chat-stream:empty { display: none; }
334
+
335
+ /* Way out of the stream. Only shown once there is something to leave. */
336
+ .chat-clear {
337
+ align-self: flex-end;
338
+ height: 28px;
339
+ padding: 0 12px;
340
+ border: 1px solid var(--border-subtle);
341
+ border-radius: var(--r-pill);
342
+ background: var(--overlay);
343
+ color: var(--muted);
344
+ font: inherit;
345
+ font-size: 12px;
346
+ cursor: pointer;
347
+ transition: color .12s ease;
348
+ }
349
+
350
+ .chat-clear:hover { color: var(--text); }
351
+ .chat-clear[hidden] { display: none; }
352
+
353
+ .chat-message,
354
+ .chat-card,
355
+ .chat-note,
356
+ .chat-error {
357
+ border-radius: var(--r-field);
358
+ border: 1px solid var(--border-subtle);
359
+ background: var(--overlay);
360
+ font-size: 14px;
361
+ line-height: 1.55;
362
+ white-space: pre-wrap;
363
+ animation: chat-in .2s var(--ease);
364
+ }
365
+
366
+ @keyframes chat-in {
367
+ from { opacity: 0; translate: 0 6px; }
368
+ to { opacity: 1; translate: 0 0; }
369
+ }
370
+
371
+ .chat-message {
372
+ padding: 10px 14px;
373
+ border: 1px solid var(--border-subtle);
374
+ color: var(--text);
375
+ }
376
+
377
+ /* Your own words are already known — let them recede next to the answer. */
378
+ .chat-message.is-user {
379
+ align-self: flex-end;
380
+ max-width: 80%;
381
+ border: 0;
382
+ background: var(--fill);
383
+ color: var(--text-2);
384
+ font-size: 13px;
385
+ }
386
+
387
+ .chat-error { padding: 10px 14px; color: var(--danger); }
388
+
389
+ /* Rendered reply: block spacing comes from the tags, not from pre-wrap */
390
+ .chat-message.is-markdown { white-space: normal; }
391
+ .chat-message.is-markdown > * + * { margin-top: 8px; }
392
+ .chat-message.is-markdown ul,
393
+ .chat-message.is-markdown ol { padding-left: 20px; }
394
+ .chat-message.is-markdown li + li { margin-top: 3px; }
395
+ .chat-message.is-markdown .md-heading { font-weight: 600; }
396
+ .chat-message.is-markdown a { color: var(--accent); }
397
+
398
+ .chat-message.is-markdown code {
399
+ padding: 1px 5px;
400
+ border-radius: var(--r-xs);
401
+ background: var(--surface-3);
402
+ font-family: var(--font);
403
+ font-size: .9em;
404
+ }
405
+
406
+ .chat-message.is-markdown pre {
407
+ overflow-x: auto;
408
+ padding: 10px 12px;
409
+ border-radius: var(--r-sm);
410
+ background: var(--surface-3);
411
+ }
412
+
413
+ .chat-message.is-markdown pre code { padding: 0; background: none; }
414
+
415
+ /* Streaming cursor, dropped as soon as the first token lands */
416
+ .chat-caret {
417
+ display: inline-block;
418
+ width: 7px;
419
+ height: 15px;
420
+ vertical-align: -2px;
421
+ background: var(--muted);
422
+ animation: chat-blink 1s steps(2) infinite;
423
+ }
424
+
425
+ @keyframes chat-blink { 50% { opacity: 0; } }
426
+
427
+ .chat-card {
428
+ position: relative;
429
+ display: flex;
430
+ flex-direction: column;
431
+ gap: 4px;
432
+ padding: 12px 34px 12px 14px;
433
+ color: var(--text-2);
434
+ font-size: 13px;
435
+ }
436
+
437
+ /* Dismiss: rejecting a finding must be as cheap as accepting one */
438
+ .chat-card-dismiss {
439
+ position: absolute;
440
+ top: 8px;
441
+ right: 8px;
442
+ border: 0;
443
+ background: none;
444
+ padding: 2px 6px;
445
+ color: var(--muted);
446
+ font-size: 16px;
447
+ line-height: 1;
448
+ cursor: pointer;
449
+ opacity: .6;
450
+ }
451
+
452
+ .chat-card-dismiss:hover { opacity: 1; color: var(--text); }
453
+
454
+ /* Offered, not spent: the rewrite request starts here, not on the underline */
455
+ .chat-offer {
456
+ align-self: flex-start;
457
+ margin-top: 6px;
458
+ height: 26px;
459
+ padding: 0 12px;
460
+ border: 1px solid var(--border);
461
+ border-radius: var(--r-pill);
462
+ background: var(--surface);
463
+ color: var(--text);
464
+ font: inherit;
465
+ font-size: 12px;
466
+ font-weight: 500;
467
+ cursor: pointer;
468
+ transition: background .12s ease;
469
+ }
470
+
471
+ .chat-offer:hover { background: var(--surface-2); }
472
+
473
+ .chat-card strong { color: var(--text); font-size: 15px; font-weight: 600; }
474
+ .chat-card small { color: var(--muted); font: inherit; }
475
+
476
+ .chat-variants {
477
+ display: flex;
478
+ flex-direction: column;
479
+ gap: 6px;
480
+ }
481
+
482
+ /* ── Composer ─────────────────────────────────────────── */
483
+
484
+ .chat-composer {
485
+ display: flex;
486
+ flex-direction: column;
487
+ gap: 8px;
488
+ padding: 8px 8px 8px 18px;
489
+ border: 1px solid var(--border);
490
+ border-radius: 24px;
491
+ background: var(--overlay);
492
+ box-shadow: var(--shadow-lg);
493
+ transition: border-color .12s ease, box-shadow .12s ease;
494
+ }
495
+
496
+ .chat-composer:focus-within {
497
+ border-color: var(--border-strong);
498
+ box-shadow: var(--shadow-lg), 0 0 0 3px color-mix(in srgb, var(--accent) 14%, transparent);
499
+ }
500
+
501
+ /* One line at rest: field left, send right, both on the same baseline. */
502
+ .chat-row {
503
+ display: flex;
504
+ align-items: flex-end;
505
+ gap: 8px;
506
+ }
507
+
508
+ #chat-input {
509
+ flex: 1;
510
+ padding: 7px 0;
511
+ border: 0;
512
+ outline: none;
513
+ resize: none;
514
+ background: none;
515
+ color: var(--text);
516
+ font-family: var(--ui-font);
517
+ font-size: 16px;
518
+ line-height: 1.45;
519
+ max-height: 160px;
520
+ }
521
+
522
+ #chat-input::placeholder { color: var(--muted); }
523
+
524
+ .chat-send {
525
+ display: inline-flex;
526
+ align-items: center;
527
+ justify-content: center;
528
+ flex-shrink: 0;
529
+ width: 34px;
530
+ height: 34px;
531
+ border: 0;
532
+ border-radius: var(--r-pill);
533
+ background: var(--primary-bg);
534
+ color: var(--primary-fg);
535
+ cursor: pointer;
536
+ transition: opacity .12s ease, background .12s ease;
537
+ }
538
+
539
+ .chat-send:hover:not(:disabled) { background: var(--primary-hover); }
540
+
541
+ /* Dim, not hidden: the writer should see where the send is before they type. */
542
+ .chat-send:disabled {
543
+ background: var(--fill);
544
+ color: var(--muted);
545
+ cursor: default;
546
+ }
547
+
548
+ /* Names what is attached; the passage itself stays highlighted in the draft. */
549
+ .chat-chip {
550
+ display: flex;
551
+ align-items: center;
552
+ gap: 6px;
553
+ align-self: flex-start;
554
+ max-width: 100%;
555
+ padding: 3px 4px 3px 10px;
556
+ border-radius: var(--r-pill);
557
+ background: color-mix(in srgb, var(--accent) 12%, transparent);
558
+ color: var(--text-2);
559
+ }
560
+
561
+ .chat-chip.hidden { display: none; }
562
+
563
+ .chat-chip-text {
564
+ overflow: hidden;
565
+ text-overflow: ellipsis;
566
+ white-space: nowrap;
567
+ font-size: 12px;
568
+ font-weight: 500;
569
+ }
570
+
571
+ .chat-chip button {
572
+ position: relative;
573
+ flex-shrink: 0;
574
+ width: 22px;
575
+ height: 22px;
576
+ border: 0;
577
+ border-radius: var(--r-pill);
578
+ background: none;
579
+ color: var(--muted);
580
+ font: inherit;
581
+ font-size: 15px;
582
+ line-height: 1;
583
+ cursor: pointer;
584
+ }
585
+
586
+ .chat-chip button:hover { background: var(--fill-hover); color: var(--text); }
587
+
588
+ /* Fitts: the visible dot is small, the thing you can hit is not. */
589
+ .chat-chip button::after { content: ''; position: absolute; inset: -8px; }
590
+
591
+ @media (prefers-reduced-motion: reduce) {
592
+ .chat-message,
593
+ .chat-card,
594
+ .chat-error,
595
+ .variant-card { animation: none; }
596
+ .review-button.is-busy { animation: none; border-color: var(--accent); }
597
+ .chat-caret { animation: none; opacity: .6; }
598
+ }
599
+
600
+ /* Keep the last lines of the draft clear of the panel. The panel grows with
601
+ its content, so the reserved space is measured, not guessed. */
602
+ #editor-wrapper .cm-content { padding-bottom: var(--chat-height, 140px); }
603
+
604
+ /* ── Loading skeletons ────────────────────────────────── */
605
+ /*
606
+ * Used where the shape of what is coming is known — the three variant cards.
607
+ * A spinner there would collapse the box and then shove it open again.
608
+ */
609
+
610
+ /* ── Variant cards ────────────────────────────────────── */
611
+
612
+ .variant-card {
613
+ padding: 12px 14px;
614
+ border-radius: var(--r-field);
615
+ border: 1px solid var(--border-subtle);
616
+ background: var(--overlay);
617
+ cursor: pointer;
618
+ color: var(--text);
619
+ font-size: 14px;
620
+ line-height: 1.55;
621
+ transition: background .12s ease;
622
+ animation: chat-in .2s var(--ease);
623
+ }
624
+
625
+ .variant-card:hover { background: var(--surface-2); }
626
+
627
+ .variant-label {
628
+ display: flex;
629
+ align-items: baseline;
630
+ gap: 8px;
631
+ margin-bottom: 4px;
632
+ color: var(--muted);
633
+ font-size: 12px;
634
+ font-weight: 600;
635
+ }
636
+
637
+ /* Draft score if you pick this variant. Informational — never fed to the model. */
638
+ .variant-delta {
639
+ font-weight: 400;
640
+ font-variant-numeric: tabular-nums;
641
+ }
642
+
643
+ .variant-delta.is-better { color: #53b559; }
644
+ .variant-delta.is-worse { color: var(--danger); }
645
+
646
+ .variant-card.is-loading { cursor: default; }
647
+ .variant-card.is-loading:hover { background: var(--overlay); }
648
+
649
+ /* Once one option is applied the others point at a range that is gone. Stop
650
+ * advertising a click that would silently do nothing. */
651
+ .chat-variants.is-spent .variant-card { cursor: default; opacity: .45; }
652
+ .chat-variants.is-spent .variant-card:hover { background: var(--overlay); }
653
+
654
+ .chat-variants.is-spent .variant-card.is-applied { opacity: 1; }
655
+
656
+ /* Three options the writer dislikes is a dead end without this */
657
+ .chat-again {
658
+ align-self: flex-end;
659
+ height: 26px;
660
+ padding: 0 10px;
661
+ border: 1px solid var(--border);
662
+ border-radius: var(--r-pill);
663
+ background: var(--overlay);
664
+ box-shadow: var(--shadow-sm);
665
+ color: var(--muted);
666
+ font: inherit;
667
+ font-size: 12px;
668
+ cursor: pointer;
669
+ transition: color .12s ease;
670
+ }
671
+
672
+ .chat-again:hover { color: var(--text); }
673
+ .chat-variants.is-spent .chat-again { display: none; }
674
+ .variant-card.is-applied .variant-label::after {
675
+ content: 'applied';
676
+ color: var(--accent);
677
+ font-weight: 400;
678
+ }
679
+
680
+ .skeleton {
681
+ border-radius: var(--r-xs);
682
+ background: linear-gradient(90deg, var(--surface-3) 25%, var(--surface-2) 37%, var(--surface-3) 63%);
683
+ background-size: 400% 100%;
684
+ animation: skeleton-sheen 1.4s ease-in-out infinite;
685
+ }
686
+
687
+ .skeleton-label {
688
+ width: 64px;
689
+ height: 10px;
690
+ margin-bottom: 8px;
691
+ }
692
+
693
+ .skeleton-line {
694
+ height: 12px;
695
+ margin-top: 7px;
696
+ }
697
+
698
+ .skeleton-line.is-short { width: 62%; }
699
+
700
+ @keyframes skeleton-sheen {
701
+ from { background-position: 100% 50%; }
702
+ to { background-position: 0 50%; }
703
+ }
704
+
705
+ @media (prefers-reduced-motion: reduce) {
706
+ .skeleton { animation: none; }
707
+ }
708
+
709
+ .error-msg {
710
+ padding: 12px 14px;
711
+ font-size: 14px;
712
+ color: var(--danger);
713
+ }
714
+
715
+ /* ── Pi settings dialog ──────────────────────────────── */
716
+
717
+ .settings-dialog {
718
+ /* the global `* { margin: 0 }` reset kills the UA's `margin: auto` centring */
719
+ margin: auto;
720
+ width: min(680px, calc(100vw - 32px));
721
+ border: 1px solid var(--border-subtle);
722
+ border-radius: var(--r-overlay);
723
+ padding: 0;
724
+ color: var(--text);
725
+ background: var(--overlay);
726
+ font-family: var(--ui-font);
727
+ box-shadow: var(--shadow-lg);
728
+ }
729
+
730
+ /* Native <dialog> centres itself — animate opacity only, no transform. */
731
+ .settings-dialog[open] { animation: dialog-in .2s var(--ease); }
732
+
733
+ @keyframes dialog-in {
734
+ from { opacity: 0; translate: 0 8px; }
735
+ to { opacity: 1; translate: 0 0; }
736
+ }
737
+
738
+ .settings-dialog::backdrop { background: var(--scrim); }
739
+ .settings-dialog form { display: flex; flex-direction: column; }
740
+ .settings-dialog header, .settings-dialog footer {
741
+ display: flex;
742
+ align-items: center;
743
+ justify-content: space-between;
744
+ padding: 20px 24px;
745
+ }
746
+ .settings-dialog header { border-bottom: 1px solid var(--border-subtle); }
747
+ .settings-dialog footer { border-top: 1px solid var(--border-subtle); justify-content: flex-end; gap: 8px; }
748
+ .settings-dialog header span, .settings-dialog h3 {
749
+ color: var(--muted);
750
+ font-size: 13px;
751
+ font-weight: 400;
752
+ letter-spacing: 0;
753
+ text-transform: none;
754
+ }
755
+ .settings-dialog h2 { font-size: 22px; font-weight: 600; margin-top: 2px; }
756
+ .settings-dialog h3 { margin-bottom: 12px; font-weight: 500; }
757
+ .settings-dialog section { padding: 20px 24px; border-bottom: 1px solid var(--border-subtle); }
758
+ .settings-dialog section:last-of-type { border-bottom: 0; }
759
+
760
+ /* Fields: rectangular, flat, unfilled — the quiet half of the pair */
761
+ .settings-dialog input, .settings-dialog select {
762
+ min-height: 44px;
763
+ border: 1px solid var(--border);
764
+ border-radius: var(--r-field);
765
+ padding: 0 12px;
766
+ color: var(--text);
767
+ background: var(--surface);
768
+ font: inherit;
769
+ font-size: 14px;
770
+ outline: none;
771
+ }
772
+ .settings-dialog input:focus, .settings-dialog select:focus { border-color: var(--accent); }
773
+
774
+ /* The UA chevron sits flush against the border — draw our own, inset properly */
775
+ .settings-dialog select {
776
+ appearance: none;
777
+ padding-right: 36px;
778
+ background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='none' stroke='%238f8f8f' stroke-width='1.5' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M4 6.5 8 10.5 12 6.5'/%3E%3C/svg%3E");
779
+ background-repeat: no-repeat;
780
+ background-position: right 12px center;
781
+ background-size: 16px 16px;
782
+ }
783
+
784
+ @media (prefers-color-scheme: dark) {
785
+ .settings-dialog select {
786
+ background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='none' stroke='%23afafaf' stroke-width='1.5' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M4 6.5 8 10.5 12 6.5'/%3E%3C/svg%3E");
787
+ }
788
+ }
789
+
790
+ /* Buttons: pill AND filled — never mistakable for a field */
791
+ .settings-dialog button {
792
+ min-height: 44px;
793
+ padding: 0 20px;
794
+ border: 1px solid transparent;
795
+ border-radius: var(--r-pill);
796
+ background: var(--fill);
797
+ color: var(--text);
798
+ font: inherit;
799
+ font-size: 14px;
800
+ font-weight: 500;
801
+ cursor: pointer;
802
+ transition: background .12s ease;
803
+ }
804
+ .settings-dialog button:hover { background: var(--fill-hover); }
805
+ .settings-dialog button.primary {
806
+ color: var(--primary-fg);
807
+ background: var(--primary-bg);
808
+ border-color: var(--primary-bg);
809
+ }
810
+ .settings-dialog button.primary:hover { background: var(--primary-hover); border-color: var(--primary-hover); }
811
+ .settings-dialog button:disabled { opacity: .5; cursor: default; }
812
+ .settings-dialog .dialog-close {
813
+ width: 32px;
814
+ min-height: 32px;
815
+ padding: 0;
816
+ border: 0;
817
+ background: none;
818
+ font-size: 20px;
819
+ color: var(--text-2);
820
+ }
821
+ .settings-dialog .dialog-close:hover { background: var(--surface-3); }
822
+
823
+ .settings-grid { display: grid; grid-template-columns: 1fr 2fr 1fr; gap: 10px; }
824
+ .settings-grid label { display: flex; flex-direction: column; gap: 6px; color: var(--muted); font-size: 13px; }
825
+ .key-entry { display: grid; grid-template-columns: 1fr 2fr auto; gap: 10px; }
826
+ .settings-check { display: flex; align-items: flex-start; gap: 10px; cursor: pointer; }
827
+ .settings-check input { min-height: 0; width: 16px; height: 16px; margin-top: 2px; accent-color: var(--accent); }
828
+ .settings-check span { color: var(--text); font-size: 14px; }
829
+ .settings-check small { display: block; color: var(--muted); font-size: 13px; }
830
+
831
+ .settings-hint, .settings-error { min-height: 18px; margin-top: 10px; color: var(--muted); font-size: 13px; }
832
+ .settings-error { color: var(--danger); }
833
+ .configured-keys { display: flex; flex-wrap: wrap; gap: 8px; margin-top: 12px; }
834
+ .key-chip {
835
+ display: flex;
836
+ align-items: center;
837
+ gap: 6px;
838
+ padding: 6px 12px;
839
+ border: 1px solid var(--border);
840
+ border-radius: var(--r-pill);
841
+ font-size: 13px;
842
+ }
843
+ .key-chip i { width: 6px; height: 6px; border-radius: 50%; background: #53b559; }
844
+ .key-chip button { min-height: auto; border: 0; padding: 0 2px; background: none; color: var(--muted); }
845
+ .key-chip button:hover { background: none; color: var(--text); }
846
+
847
+ @media (max-width: 640px) {
848
+ .settings-grid, .key-entry { grid-template-columns: 1fr; }
849
+ }