@cfbender/cesium 0.3.5

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.
Files changed (44) hide show
  1. package/ARCHITECTURE.md +304 -0
  2. package/CHANGELOG.md +335 -0
  3. package/LICENSE +21 -0
  4. package/README.md +479 -0
  5. package/agents/cesium.md +39 -0
  6. package/assets/styleguide.html +857 -0
  7. package/package.json +61 -0
  8. package/src/cli/commands/ls.ts +186 -0
  9. package/src/cli/commands/open.ts +208 -0
  10. package/src/cli/commands/prune.ts +348 -0
  11. package/src/cli/commands/restart.ts +38 -0
  12. package/src/cli/commands/serve.ts +214 -0
  13. package/src/cli/commands/stop.ts +130 -0
  14. package/src/cli/commands/theme.ts +333 -0
  15. package/src/cli/index.ts +78 -0
  16. package/src/config.ts +94 -0
  17. package/src/index.ts +35 -0
  18. package/src/prompt/system-fragment.md +97 -0
  19. package/src/render/client-js.ts +316 -0
  20. package/src/render/controls.ts +302 -0
  21. package/src/render/critique.ts +360 -0
  22. package/src/render/extract.ts +83 -0
  23. package/src/render/scrub.ts +141 -0
  24. package/src/render/theme.ts +712 -0
  25. package/src/render/validate.ts +524 -0
  26. package/src/render/wrap.ts +165 -0
  27. package/src/server/api.ts +166 -0
  28. package/src/server/http.ts +195 -0
  29. package/src/server/lifecycle.ts +331 -0
  30. package/src/server/stop.ts +124 -0
  31. package/src/storage/index-cache.ts +71 -0
  32. package/src/storage/index-gen.ts +447 -0
  33. package/src/storage/lock.ts +108 -0
  34. package/src/storage/mutate.ts +396 -0
  35. package/src/storage/paths.ts +159 -0
  36. package/src/storage/project-summaries.ts +19 -0
  37. package/src/storage/theme-write.ts +19 -0
  38. package/src/storage/write.ts +75 -0
  39. package/src/tools/ask.ts +353 -0
  40. package/src/tools/critique.ts +66 -0
  41. package/src/tools/publish.ts +404 -0
  42. package/src/tools/stop.ts +53 -0
  43. package/src/tools/styleguide.ts +23 -0
  44. package/src/tools/wait.ts +192 -0
@@ -0,0 +1,712 @@
1
+ // CSS framework and color token definitions — inlined into every artifact.
2
+
3
+ export interface ThemePalette {
4
+ bg: string;
5
+ surface: string;
6
+ surface2: string;
7
+ oat: string;
8
+ rule: string;
9
+ ink: string;
10
+ inkSoft: string;
11
+ muted: string;
12
+ accent: string;
13
+ olive: string;
14
+ codeBg: string;
15
+ codeFg: string;
16
+ }
17
+
18
+ export interface ThemeFonts {
19
+ serif: string;
20
+ sans: string;
21
+ mono: string;
22
+ }
23
+
24
+ export interface ThemeTokens {
25
+ colors: ThemePalette;
26
+ fonts: ThemeFonts;
27
+ }
28
+
29
+ export type ThemePresetName =
30
+ | "warm"
31
+ | "cool"
32
+ | "mono"
33
+ | "paper"
34
+ | "claret" // alias for claret-dark
35
+ | "claret-dark"
36
+ | "claret-light";
37
+
38
+ const STANDARD_FONTS: ThemeFonts = {
39
+ serif: 'ui-serif, Georgia, "Times New Roman", serif',
40
+ sans: 'system-ui, -apple-system, "Segoe UI", Roboto, sans-serif',
41
+ mono: 'ui-monospace, "SF Mono", Menlo, Monaco, monospace',
42
+ };
43
+
44
+ export const THEME_PRESETS: Readonly<Record<ThemePresetName, ThemePalette>> = {
45
+ // claret-dark: deep wine background with bright rose/sage — claret.nvim dark palette.
46
+ "claret-dark": {
47
+ bg: "#180810",
48
+ surface: "#211618",
49
+ surface2: "#2B1F22",
50
+ oat: "#3A2E25",
51
+ rule: "#3A2E25",
52
+ ink: "#DDD3C7",
53
+ inkSoft: "#BDB3A7",
54
+ muted: "#9E9288",
55
+ accent: "#C75B7A",
56
+ olive: "#8FA86E",
57
+ codeBg: "#2B1F22",
58
+ codeFg: "#DDD3C7",
59
+ },
60
+ // claret-light: deep-rose-on-warm-cream — derived from claret.nvim light palette.
61
+ // (this is the old "claret" palette, now renamed)
62
+ "claret-light": {
63
+ bg: "#FDF8F3",
64
+ surface: "#FFFFFF",
65
+ surface2: "#F5EDE3",
66
+ oat: "#E8DDD0",
67
+ rule: "#D4C8B8",
68
+ ink: "#2A1F1A",
69
+ inkSoft: "#5A4D42",
70
+ muted: "#7D7068",
71
+ accent: "#8B2252",
72
+ olive: "#5A6B40",
73
+ codeBg: "#180810",
74
+ codeFg: "#DDD3C7",
75
+ },
76
+ // claret: alias for claret-dark (backward compat)
77
+ claret: {
78
+ bg: "#180810",
79
+ surface: "#211618",
80
+ surface2: "#2B1F22",
81
+ oat: "#3A2E25",
82
+ rule: "#3A2E25",
83
+ ink: "#DDD3C7",
84
+ inkSoft: "#BDB3A7",
85
+ muted: "#9E9288",
86
+ accent: "#C75B7A",
87
+ olive: "#8FA86E",
88
+ codeBg: "#2B1F22",
89
+ codeFg: "#DDD3C7",
90
+ },
91
+ // Warm: ivory/clay/oat — the html-effectiveness reference palette.
92
+ warm: {
93
+ bg: "#FAF9F5",
94
+ surface: "#FFFFFF",
95
+ surface2: "#F0EEE6",
96
+ oat: "#E3DACC",
97
+ rule: "#D1CFC5",
98
+ ink: "#141413",
99
+ inkSoft: "#3D3D3A",
100
+ muted: "#87867F",
101
+ accent: "#D97757",
102
+ olive: "#788C5D",
103
+ codeBg: "#141413",
104
+ codeFg: "#E8E6DE",
105
+ },
106
+ // Cool: desaturated blue-grey — technical, trustworthy.
107
+ cool: {
108
+ bg: "#F4F6F9",
109
+ surface: "#FFFFFF",
110
+ surface2: "#E8ECF2",
111
+ oat: "#D8DFE8",
112
+ rule: "#C2C9D4",
113
+ ink: "#141820",
114
+ inkSoft: "#343B47",
115
+ muted: "#7A8496",
116
+ accent: "#3A7BB8",
117
+ olive: "#4E8A6A",
118
+ codeBg: "#1B2333",
119
+ codeFg: "#D8E0ED",
120
+ },
121
+ // Mono: black/white/grey — editorial, high-contrast.
122
+ mono: {
123
+ bg: "#FBFAF8",
124
+ surface: "#FFFFFF",
125
+ surface2: "#F2F2F0",
126
+ oat: "#E4E4E1",
127
+ rule: "#CECECA",
128
+ ink: "#111111",
129
+ inkSoft: "#3A3A3A",
130
+ muted: "#888884",
131
+ accent: "#C0392B",
132
+ olive: "#5A7A5A",
133
+ codeBg: "#111111",
134
+ codeFg: "#EBEBEB",
135
+ },
136
+ // Paper: sepia/cream — soft, book-like, warm and aged.
137
+ paper: {
138
+ bg: "#F5EFE0",
139
+ surface: "#FBF7EE",
140
+ surface2: "#EDE4CF",
141
+ oat: "#DDD0B8",
142
+ rule: "#C9BFAA",
143
+ ink: "#2A2218",
144
+ inkSoft: "#4A3E32",
145
+ muted: "#8A7E6E",
146
+ accent: "#B05A2A",
147
+ olive: "#607848",
148
+ codeBg: "#2A2218",
149
+ codeFg: "#E8DEC8",
150
+ },
151
+ };
152
+
153
+ export function isThemePresetName(name: string): name is ThemePresetName {
154
+ return (
155
+ name === "claret" ||
156
+ name === "claret-dark" ||
157
+ name === "claret-light" ||
158
+ name === "warm" ||
159
+ name === "cool" ||
160
+ name === "mono" ||
161
+ name === "paper"
162
+ );
163
+ }
164
+
165
+ export function themeFromPreset(name?: string): ThemeTokens {
166
+ const presetName: ThemePresetName =
167
+ name !== undefined && isThemePresetName(name) ? name : "claret-dark";
168
+ return {
169
+ colors: THEME_PRESETS[presetName],
170
+ fonts: STANDARD_FONTS,
171
+ };
172
+ }
173
+
174
+ export function defaultTheme(): ThemeTokens {
175
+ return themeFromPreset("claret-dark");
176
+ }
177
+
178
+ export function mergeTheme(base: ThemeTokens, override?: Partial<ThemePalette>): ThemeTokens {
179
+ if (!override) return base;
180
+ return {
181
+ ...base,
182
+ colors: { ...base.colors, ...override },
183
+ };
184
+ }
185
+
186
+ export function themeToCssVars(theme: ThemeTokens): string {
187
+ const { colors, fonts } = theme;
188
+ return `:root {
189
+ --bg: ${colors.bg};
190
+ --surface: ${colors.surface};
191
+ --surface-2: ${colors.surface2};
192
+ --oat: ${colors.oat};
193
+ --rule: ${colors.rule};
194
+ --ink: ${colors.ink};
195
+ --ink-soft: ${colors.inkSoft};
196
+ --muted: ${colors.muted};
197
+ --accent: ${colors.accent};
198
+ --olive: ${colors.olive};
199
+ --code-bg: ${colors.codeBg};
200
+ --code-fg: ${colors.codeFg};
201
+ --serif: ${fonts.serif};
202
+ --sans: ${fonts.sans};
203
+ --mono: ${fonts.mono};
204
+ }`;
205
+ }
206
+
207
+ /** Returns ":root { --bg: ...; ... }" — token definitions only. */
208
+ export function themeTokensCss(theme: ThemeTokens): string {
209
+ return themeToCssVars(theme);
210
+ }
211
+
212
+ /** Returns the framework's typography, components, layout — uses var(--bg) etc.
213
+ * Does NOT include any :root token definitions. Pure rules. */
214
+ export function frameworkRulesCss(): string {
215
+ return `
216
+ /* reset */
217
+ *, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
218
+ html { font-size: 16px; -webkit-text-size-adjust: 100%; }
219
+ body {
220
+ background: var(--bg);
221
+ color: var(--ink);
222
+ font-family: var(--sans);
223
+ font-size: 1rem;
224
+ line-height: 1.6;
225
+ padding: clamp(20px, 4vw, 56px);
226
+ }
227
+ a { color: var(--accent); text-decoration: underline; }
228
+ a:hover { opacity: 0.8; }
229
+ img, svg { max-width: 100%; height: auto; }
230
+ p { margin-bottom: 1em; }
231
+ ul, ol { padding-left: 1.5em; margin-bottom: 1em; }
232
+
233
+ /* layout */
234
+ .page { max-width: 1120px; margin: 0 auto; }
235
+ section { margin-bottom: 64px; }
236
+
237
+ /* typography */
238
+ h1, h2, h3, h4, h5, h6 {
239
+ font-family: var(--serif);
240
+ color: var(--ink);
241
+ line-height: 1.2;
242
+ margin-bottom: 0.5em;
243
+ }
244
+
245
+ /* eyebrow */
246
+ .eyebrow {
247
+ font-family: var(--mono);
248
+ font-size: 0.7rem;
249
+ font-weight: 600;
250
+ letter-spacing: 0.1em;
251
+ text-transform: uppercase;
252
+ color: var(--muted);
253
+ margin-bottom: 0.5em;
254
+ }
255
+
256
+ /* headings */
257
+ .h-display {
258
+ font-family: var(--serif);
259
+ font-size: clamp(2rem, 5vw, 3.25rem);
260
+ font-weight: 700;
261
+ line-height: 1.1;
262
+ color: var(--ink);
263
+ margin-bottom: 0.75em;
264
+ }
265
+ .h-section {
266
+ font-family: var(--serif);
267
+ font-size: 1.5rem;
268
+ font-weight: 600;
269
+ color: var(--ink);
270
+ margin-bottom: 0.5em;
271
+ }
272
+ .section-num {
273
+ display: inline-flex;
274
+ align-items: center;
275
+ justify-content: center;
276
+ background: var(--oat);
277
+ color: var(--ink-soft);
278
+ font-family: var(--mono);
279
+ font-size: 0.75rem;
280
+ font-weight: 700;
281
+ border-radius: 6px;
282
+ padding: 2px 7px;
283
+ margin-right: 0.5em;
284
+ vertical-align: middle;
285
+ }
286
+
287
+ /* card */
288
+ .card {
289
+ background: var(--surface);
290
+ border: 1.5px solid var(--rule);
291
+ border-radius: 12px;
292
+ padding: 18px 22px;
293
+ margin-bottom: 1.5em;
294
+ }
295
+
296
+ /* tldr */
297
+ .tldr {
298
+ background: var(--surface);
299
+ border-left: 4px solid var(--accent);
300
+ border-radius: 0 12px 12px 0;
301
+ padding: 16px 20px;
302
+ margin-bottom: 1.5em;
303
+ font-size: 1.05rem;
304
+ color: var(--ink-soft);
305
+ }
306
+
307
+ /* callout */
308
+ .callout {
309
+ border-radius: 8px;
310
+ padding: 14px 18px;
311
+ margin-bottom: 1.25em;
312
+ border: 1.5px solid var(--rule);
313
+ background: var(--surface-2);
314
+ color: var(--ink-soft);
315
+ font-size: 0.95rem;
316
+ }
317
+ .callout.note { border-color: var(--olive); background: color-mix(in srgb, var(--olive) 10%, var(--surface)); }
318
+ .callout.warn { border-color: var(--accent); background: color-mix(in srgb, var(--accent) 10%, var(--surface)); }
319
+ .callout.risk { border-color: #b45309; background: color-mix(in srgb, #b45309 10%, var(--surface)); }
320
+
321
+ /* code */
322
+ .code {
323
+ background: var(--code-bg);
324
+ color: var(--code-fg);
325
+ font-family: var(--mono);
326
+ font-size: 0.875rem;
327
+ line-height: 1.6;
328
+ border-radius: 8px;
329
+ padding: 16px 20px;
330
+ overflow-x: auto;
331
+ margin-bottom: 1.25em;
332
+ white-space: pre;
333
+ }
334
+ .code .kw { color: var(--accent); }
335
+ .code .str { color: var(--olive); }
336
+ .code .cm { color: var(--muted); font-style: italic; }
337
+ .code .fn { color: #d4a85a; }
338
+
339
+ /* timeline */
340
+ .timeline { list-style: none; padding: 0; position: relative; }
341
+ .timeline::before {
342
+ content: "";
343
+ position: absolute;
344
+ left: 9px;
345
+ top: 6px;
346
+ bottom: 6px;
347
+ width: 2px;
348
+ background: var(--rule);
349
+ }
350
+ .timeline li {
351
+ position: relative;
352
+ padding-left: 32px;
353
+ margin-bottom: 1.25em;
354
+ }
355
+ .timeline li::before {
356
+ content: "";
357
+ position: absolute;
358
+ left: 2px;
359
+ top: 6px;
360
+ width: 14px;
361
+ height: 14px;
362
+ border-radius: 50%;
363
+ background: var(--oat);
364
+ border: 2px solid var(--accent);
365
+ }
366
+
367
+ /* diagram */
368
+ .diagram {
369
+ border: 1.5px solid var(--rule);
370
+ border-radius: 12px;
371
+ padding: 20px;
372
+ text-align: center;
373
+ margin-bottom: 1.5em;
374
+ background: var(--surface);
375
+ }
376
+ .diagram figcaption {
377
+ font-size: 0.85rem;
378
+ color: var(--muted);
379
+ margin-top: 10px;
380
+ font-family: var(--sans);
381
+ }
382
+
383
+ /* compare-table */
384
+ .compare-table {
385
+ width: 100%;
386
+ border-collapse: collapse;
387
+ margin-bottom: 1.5em;
388
+ font-size: 0.95rem;
389
+ }
390
+ .compare-table th, .compare-table td {
391
+ border: 1.5px solid var(--rule);
392
+ padding: 10px 14px;
393
+ text-align: left;
394
+ vertical-align: top;
395
+ }
396
+ .compare-table th {
397
+ background: var(--surface-2);
398
+ font-family: var(--sans);
399
+ font-weight: 600;
400
+ color: var(--ink);
401
+ }
402
+ .compare-table tr:nth-child(even) td { background: var(--surface-2); }
403
+
404
+ /* risk-table */
405
+ .risk-table {
406
+ width: 100%;
407
+ border-collapse: collapse;
408
+ margin-bottom: 1.5em;
409
+ font-size: 0.95rem;
410
+ }
411
+ .risk-table th, .risk-table td {
412
+ border: 1.5px solid var(--rule);
413
+ padding: 10px 14px;
414
+ text-align: left;
415
+ vertical-align: top;
416
+ }
417
+ .risk-table th {
418
+ background: var(--surface-2);
419
+ font-family: var(--sans);
420
+ font-weight: 600;
421
+ }
422
+ .risk-table td:first-child { font-weight: 600; color: var(--ink-soft); }
423
+
424
+ /* inline chips */
425
+ .kbd {
426
+ display: inline-block;
427
+ font-family: var(--mono);
428
+ font-size: 0.8em;
429
+ background: var(--surface-2);
430
+ border: 1.5px solid var(--rule);
431
+ border-radius: 4px;
432
+ padding: 1px 6px;
433
+ color: var(--ink-soft);
434
+ white-space: nowrap;
435
+ }
436
+ .pill {
437
+ display: inline-block;
438
+ font-family: var(--sans);
439
+ font-size: 0.8em;
440
+ font-weight: 500;
441
+ background: var(--oat);
442
+ border-radius: 20px;
443
+ padding: 2px 10px;
444
+ color: var(--ink-soft);
445
+ white-space: nowrap;
446
+ }
447
+ .tag {
448
+ display: inline-block;
449
+ font-family: var(--mono);
450
+ font-size: 0.75em;
451
+ font-weight: 600;
452
+ background: var(--surface-2);
453
+ border: 1px solid var(--rule);
454
+ border-radius: 6px;
455
+ padding: 2px 8px;
456
+ color: var(--muted);
457
+ text-transform: lowercase;
458
+ }
459
+
460
+ /* byline */
461
+ .byline {
462
+ border-top: 1.5px solid var(--rule);
463
+ margin-top: 64px;
464
+ padding-top: 18px;
465
+ font-family: var(--mono);
466
+ font-size: 0.75rem;
467
+ color: var(--muted);
468
+ display: flex;
469
+ flex-wrap: wrap;
470
+ gap: 1em;
471
+ align-items: center;
472
+ }
473
+ .byline a { color: var(--muted); }
474
+
475
+ /* ─── interactive controls (.cs-*) ─────────────────────────────────────────── */
476
+
477
+ /* questions container */
478
+ .cs-questions { display: flex; flex-direction: column; gap: 1.5rem; margin: 2rem 0; }
479
+
480
+ /* individual question sections */
481
+ .cs-control-pick_one,
482
+ .cs-control-pick_many,
483
+ .cs-control-confirm,
484
+ .cs-control-ask_text,
485
+ .cs-control-slider,
486
+ .cs-control-react {
487
+ background: var(--surface);
488
+ border: 1.5px solid var(--rule);
489
+ border-radius: 12px;
490
+ padding: 1.25rem 1.5rem;
491
+ display: flex;
492
+ flex-direction: column;
493
+ gap: 1rem;
494
+ }
495
+
496
+ /* answered section — subtle left accent, slightly muted */
497
+ .cs-answered {
498
+ background: var(--surface);
499
+ border: 1.5px solid var(--rule);
500
+ border-left: 3px solid var(--olive);
501
+ border-radius: 12px;
502
+ padding: 1.25rem 1.5rem;
503
+ display: flex;
504
+ flex-direction: column;
505
+ gap: 0.75rem;
506
+ opacity: 0.85;
507
+ }
508
+
509
+ /* pick buttons / labels */
510
+ .cs-pick {
511
+ display: block;
512
+ width: 100%;
513
+ padding: 1rem 1.25rem;
514
+ background: var(--surface);
515
+ border: 1.5px solid var(--rule);
516
+ border-radius: 8px;
517
+ color: var(--ink);
518
+ cursor: pointer;
519
+ text-align: left;
520
+ transition: border-color 120ms, transform 120ms;
521
+ font-family: inherit;
522
+ font-size: 1rem;
523
+ line-height: 1.4;
524
+ }
525
+ .cs-pick:hover, .cs-pick:focus-visible {
526
+ border-color: var(--accent);
527
+ transform: translateY(-1px);
528
+ outline: none;
529
+ }
530
+ .cs-pick-desc { color: var(--muted); font-size: 0.9em; margin-top: 0.25rem; }
531
+ .cs-recommended { border-color: color-mix(in srgb, var(--accent) 40%, var(--rule)); }
532
+
533
+ /* final/locked pick — no hover, accent border */
534
+ .cs-pick-final {
535
+ border-color: var(--accent);
536
+ background: color-mix(in srgb, var(--accent) 10%, var(--surface));
537
+ cursor: default;
538
+ transform: none !important;
539
+ }
540
+ .cs-pick-final:hover { transform: none; border-color: var(--accent); }
541
+
542
+ /* confirm buttons */
543
+ .cs-confirm-row { display: flex; gap: 0.75rem; flex-wrap: wrap; }
544
+ .cs-confirm {
545
+ min-width: 120px;
546
+ padding: 0.75rem 1.5rem;
547
+ border-radius: 8px;
548
+ border: 1.5px solid var(--rule);
549
+ font-family: inherit;
550
+ font-size: 1rem;
551
+ font-weight: 600;
552
+ cursor: pointer;
553
+ transition: opacity 120ms, transform 120ms;
554
+ }
555
+ .cs-confirm:hover:not(:disabled) { opacity: 0.85; transform: translateY(-1px); }
556
+ .cs-yes {
557
+ background: var(--accent);
558
+ color: var(--bg);
559
+ border-color: var(--accent);
560
+ }
561
+ .cs-no {
562
+ background: var(--surface);
563
+ color: var(--ink);
564
+ border-color: var(--rule);
565
+ }
566
+ .cs-confirm-final { cursor: default; transform: none !important; }
567
+ .cs-confirm-final:hover { opacity: 1; transform: none; }
568
+
569
+ /* text input / textarea */
570
+ .cs-text {
571
+ display: block;
572
+ width: 100%;
573
+ padding: 0.625rem 0.875rem;
574
+ background: var(--surface-2);
575
+ border: 1.5px solid var(--rule);
576
+ border-radius: 8px;
577
+ color: var(--ink);
578
+ font-family: var(--sans);
579
+ font-size: 1rem;
580
+ line-height: 1.5;
581
+ transition: border-color 120ms;
582
+ resize: vertical;
583
+ }
584
+ .cs-text:focus { border-color: var(--accent); outline: none; }
585
+ textarea.cs-text { font-family: var(--mono); }
586
+
587
+ /* answered text */
588
+ .cs-answered-text {
589
+ border-left: 3px solid var(--accent);
590
+ margin: 0;
591
+ padding: 0.5rem 1rem;
592
+ color: var(--ink-soft);
593
+ font-size: 0.95rem;
594
+ background: color-mix(in srgb, var(--accent) 6%, var(--surface));
595
+ border-radius: 0 8px 8px 0;
596
+ }
597
+
598
+ /* slider */
599
+ .cs-slider { width: 100%; accent-color: var(--accent); cursor: pointer; }
600
+ .cs-slider-out {
601
+ display: inline-block;
602
+ padding: 0.25rem 0.75rem;
603
+ background: var(--surface-2);
604
+ border-radius: 6px;
605
+ font-family: var(--mono);
606
+ font-weight: 600;
607
+ font-size: 0.9em;
608
+ }
609
+ .cs-slider-final { font-size: 1rem; color: var(--ink-soft); }
610
+ .cs-slider-final strong { color: var(--ink); font-family: var(--mono); }
611
+
612
+ /* react buttons */
613
+ .cs-react-row { display: flex; gap: 0.5rem; flex-wrap: wrap; }
614
+ .cs-react {
615
+ padding: 0.5rem 1rem;
616
+ border-radius: 8px;
617
+ border: 1.5px solid var(--rule);
618
+ background: var(--surface);
619
+ color: var(--ink);
620
+ font-family: inherit;
621
+ font-size: 0.95rem;
622
+ font-weight: 500;
623
+ cursor: pointer;
624
+ transition: border-color 120ms, opacity 120ms;
625
+ }
626
+ .cs-react:hover:not(:disabled) { border-color: var(--accent); opacity: 0.9; }
627
+ .cs-react-comment { /* same as cs-text textarea */ }
628
+
629
+ /* optional comment display */
630
+ .cs-comment {
631
+ color: var(--ink-soft);
632
+ font-size: 0.9rem;
633
+ font-style: italic;
634
+ margin-top: 0.25rem;
635
+ }
636
+
637
+ /* context prose */
638
+ .cs-context { color: var(--muted); font-size: 0.9rem; margin-bottom: 0; }
639
+
640
+ /* submit button */
641
+ .cs-submit {
642
+ align-self: flex-start;
643
+ padding: 0.6rem 1.5rem;
644
+ background: var(--accent);
645
+ color: var(--bg);
646
+ border: 1.5px solid var(--accent);
647
+ border-radius: 8px;
648
+ font-family: inherit;
649
+ font-size: 1rem;
650
+ font-weight: 600;
651
+ cursor: pointer;
652
+ transition: opacity 120ms;
653
+ }
654
+ .cs-submit:hover:not(:disabled) { opacity: 0.85; }
655
+ .cs-submit:disabled { opacity: 0.4; cursor: not-allowed; }
656
+
657
+ /* skip button — secondary, less prominent than submit */
658
+ .cs-skip {
659
+ align-self: flex-start;
660
+ padding: 0.6rem 1.5rem;
661
+ background: var(--surface-2);
662
+ color: var(--muted);
663
+ border: 1.5px solid var(--rule);
664
+ border-radius: 8px;
665
+ font-family: inherit;
666
+ font-size: 1rem;
667
+ font-weight: 500;
668
+ cursor: pointer;
669
+ transition: opacity 120ms, border-color 120ms;
670
+ }
671
+ .cs-skip:hover:not(:disabled) { opacity: 0.85; border-color: var(--muted); }
672
+ .cs-skip:disabled { opacity: 0.4; cursor: not-allowed; }
673
+
674
+ /* button row — submit + skip side by side */
675
+ .cs-button-row { display: flex; gap: 0.75rem; align-items: baseline; flex-wrap: wrap; }
676
+
677
+ /* answered-skipped placeholder */
678
+ .cs-answered-skipped { color: var(--muted); font-style: italic; font-size: 0.9rem; }
679
+
680
+ /* pending / saving state */
681
+ .cs-saving { opacity: 0.6; pointer-events: none; }
682
+
683
+ /* inline error */
684
+ .cs-error {
685
+ color: #c93b3b;
686
+ padding: 0.5rem 0.75rem;
687
+ background: color-mix(in srgb, #c93b3b 10%, transparent);
688
+ border: 1.5px solid currentColor;
689
+ border-radius: 6px;
690
+ font-size: 0.9rem;
691
+ }
692
+
693
+ /* session-ended banner */
694
+ .cs-banner-ended {
695
+ position: sticky;
696
+ top: 0;
697
+ padding: 0.75rem 1.25rem;
698
+ background: color-mix(in srgb, var(--accent) 20%, var(--bg));
699
+ border-bottom: 1.5px solid var(--accent);
700
+ text-align: center;
701
+ font-weight: 600;
702
+ z-index: 10;
703
+ }
704
+ `;
705
+ }
706
+
707
+ /** Backward-compat: returns themeTokensCss(theme) + frameworkRulesCss().
708
+ * Used by anyone wanting the full self-contained CSS in one string. */
709
+ export function frameworkCss(theme: ThemeTokens): string {
710
+ return `${themeTokensCss(theme)}
711
+ ${frameworkRulesCss()}`;
712
+ }