@kontourai/survey 8.0.0 → 8.1.1

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.
@@ -20,12 +20,23 @@
20
20
  /* text */
21
21
  --k-text: #eef3f8;
22
22
  --k-text-muted: #aebccb;
23
- --k-text-faint: #72869b;
23
+ --k-text-faint: #75889d;
24
24
 
25
- /* brand */
25
+ /* brand — the product identity slot (eyebrows, accents, the progress sweep).
26
+ A product theme or a white-label deployment may override it; nothing
27
+ interactive reads it, so a brand that fails contrast cannot break a
28
+ button or a focus ring. */
26
29
  --k-brand: #5ce0c6;
27
30
  --k-brand-contrast: #06080b;
28
31
 
32
+ /* interaction roles — primary actions and keyboard focus. They hold their
33
+ own values instead of var(--k-brand): an alias would make every brand
34
+ override repaint buttons whether or not the new color can carry text.
35
+ Each theme and mode sets its action and focus beside its brand. */
36
+ --k-action: #5ce0c6;
37
+ --k-action-contrast: #06080b;
38
+ --k-focus: #5ce0c6;
39
+
29
40
  /* semantic status scale */
30
41
  --k-positive: #34d399;
31
42
  --k-caution: #f3b14b;
@@ -36,6 +47,51 @@
36
47
  --k-caution-soft: color-mix(in oklab, var(--k-caution) 14%, transparent);
37
48
  --k-negative-soft: color-mix(in oklab, var(--k-negative) 14%, transparent);
38
49
  --k-active-soft: color-mix(in oklab, var(--k-active) 14%, transparent);
50
+ /* Text on a filled status tone (tone badges, status-toned buttons). Its own
51
+ role so a white-label --k-brand-contrast cannot recolor status text. */
52
+ --k-status-contrast: #06080b;
53
+
54
+ /* trust states — Surface's claim statuses (TRUST_STATUSES, in Surface's
55
+ order), shown as presentation states: separate from the five status
56
+ tones and from the product accents. Each state has its own ink (label,
57
+ glyph, border), fill (the chip's own background, so its text pair does
58
+ not depend on the container) and line style (the non-color cue borders
59
+ and chart strokes reuse). Values are literal, never aliases: an alias
60
+ declared here resolves here, so a light or themed scope would inherit
61
+ this scope's hue (ui#78). */
62
+ --k-trust-unknown: #a2acb7;
63
+ --k-trust-unknown-fill: #252d39;
64
+ --k-trust-unknown-line: dotted;
65
+ --k-trust-proposed: #87b1fd;
66
+ --k-trust-proposed-fill: #222d42;
67
+ --k-trust-proposed-line: dashed;
68
+ --k-trust-assumed: #c4a4fe;
69
+ --k-trust-assumed-fill: #2a2c43;
70
+ --k-trust-assumed-line: dashed;
71
+ --k-trust-verified: #50d492;
72
+ --k-trust-verified-fill: #1a3233;
73
+ --k-trust-verified-line: solid;
74
+ --k-trust-stale: #f6b84d;
75
+ --k-trust-stale-fill: #312e2a;
76
+ --k-trust-stale-line: dotted;
77
+ --k-trust-disputed: #ec81c0;
78
+ --k-trust-disputed-fill: #30273a;
79
+ --k-trust-disputed-line: double;
80
+ --k-trust-superseded: #6bc8d3;
81
+ --k-trust-superseded-fill: #1e313d;
82
+ --k-trust-superseded-line: dotted;
83
+ --k-trust-rejected: #f66d67;
84
+ --k-trust-rejected-fill: #31242d;
85
+ --k-trust-rejected-line: solid;
86
+ --k-trust-revoked: #fdc2a2;
87
+ --k-trust-revoked-fill: #323036;
88
+ --k-trust-revoked-line: dotted;
89
+
90
+ /* trust basis — the line after a trust-state chip. Caveat facets
91
+ (Model-derived, not evaluated, cited only, contradicts, failed) are
92
+ underlined with a dashed line in the text's own color: a line-style cue,
93
+ never a hue. Names no color, so it inherits the rated muted text. */
94
+ --k-basis-caveat-decoration: underline dashed 1px;
39
95
 
40
96
  /* spacing */
41
97
  --k-space-1: 4px;
@@ -86,8 +142,11 @@
86
142
  --k-tracking-wide: 0.08em;
87
143
 
88
144
  /* focus — one ring contract so every interactive primitive (controls,
89
- buttons, links) shows the same keyboard-focus affordance. */
90
- --k-focus-ring: var(--k-brand);
145
+ buttons, links) shows the same keyboard-focus affordance. The ring color
146
+ is the --k-focus role; --k-focus-ring is kept as a compatibility alias for
147
+ consumer styles. Primitives read --k-focus directly, so an override of
148
+ --k-focus on any element reaches them. */
149
+ --k-focus-ring: var(--k-focus);
91
150
  --k-focus-ring-width: 2px;
92
151
  --k-focus-ring-offset: 2px;
93
152
 
@@ -103,6 +162,63 @@
103
162
  --k-z-toast: 1400;
104
163
  }
105
164
 
165
+ /* The dark contract again, for a data-theme="dark" element below a light
166
+ element (ui#80). It sets exactly the tokens the light block sets, with the
167
+ :root values; check:contrast fails if either drifts. Mode-independent
168
+ tokens (spacing, type, radii, fonts) are not reset, so a theme's own values
169
+ for them survive a mode switch. With no light ancestor there is nothing to
170
+ reset, so it does not match and a dark element simply inherits. */
171
+ .survey-workbench-embed[data-theme="dark"]:where([data-theme="light"] *){
172
+ color-scheme: dark;
173
+ --k-bg: #0a0e13;
174
+ --k-panel: #111824;
175
+ --k-panel-raised: #16202d;
176
+ --k-line: rgba(150, 180, 210, 0.12);
177
+ --k-line-strong: rgba(150, 180, 210, 0.22);
178
+ --k-shadow: 0 26px 60px -42px rgba(0, 0, 0, 0.95);
179
+ --k-elevation-overlay:
180
+ 0 1px 2px rgba(0, 0, 0, 0.35),
181
+ 0 12px 32px rgba(0, 0, 0, 0.45),
182
+ 0 24px 56px rgba(0, 0, 0, 0.4);
183
+ --k-text: #eef3f8;
184
+ --k-text-muted: #aebccb;
185
+ --k-text-faint: #75889d;
186
+ --k-brand: #5ce0c6;
187
+ --k-brand-contrast: #06080b;
188
+ --k-action: #5ce0c6;
189
+ --k-action-contrast: #06080b;
190
+ --k-focus: #5ce0c6;
191
+ --k-positive: #34d399;
192
+ --k-caution: #f3b14b;
193
+ --k-negative: #ff6f6f;
194
+ --k-neutral: #6f8095;
195
+ --k-active: #7aa2ff;
196
+ --k-status-contrast: #06080b;
197
+ --k-trust-unknown: #a2acb7;
198
+ --k-trust-unknown-fill: #252d39;
199
+ --k-trust-proposed: #87b1fd;
200
+ --k-trust-proposed-fill: #222d42;
201
+ --k-trust-assumed: #c4a4fe;
202
+ --k-trust-assumed-fill: #2a2c43;
203
+ --k-trust-verified: #50d492;
204
+ --k-trust-verified-fill: #1a3233;
205
+ --k-trust-stale: #f6b84d;
206
+ --k-trust-stale-fill: #312e2a;
207
+ --k-trust-disputed: #ec81c0;
208
+ --k-trust-disputed-fill: #30273a;
209
+ --k-trust-superseded: #6bc8d3;
210
+ --k-trust-superseded-fill: #1e313d;
211
+ --k-trust-rejected: #f66d67;
212
+ --k-trust-rejected-fill: #31242d;
213
+ --k-trust-revoked: #fdc2a2;
214
+ --k-trust-revoked-fill: #323036;
215
+ --k-focus-ring: var(--k-focus);
216
+ --k-positive-soft: color-mix(in oklab, var(--k-positive) 14%, transparent);
217
+ --k-caution-soft: color-mix(in oklab, var(--k-caution) 14%, transparent);
218
+ --k-negative-soft: color-mix(in oklab, var(--k-negative) 14%, transparent);
219
+ --k-active-soft: color-mix(in oklab, var(--k-active) 14%, transparent);
220
+ }
221
+
106
222
  .survey-workbench-embed[data-theme="light"]{
107
223
  color-scheme: light;
108
224
  --k-bg: #f5f4ef;
@@ -117,25 +233,92 @@
117
233
  0 18px 48px rgba(0, 0, 0, 0.08);
118
234
  --k-text: #202124;
119
235
  --k-text-muted: #5b626b;
120
- --k-text-faint: #707782;
236
+ --k-text-faint: #6a707b;
121
237
  /* The dark-mode brand (#5ce0c6) is too light to carry white text or read as
122
238
  text on the light canvas (~1.6:1). Darken it for light mode: #0e7c64 holds
123
239
  the teal hue while clearing WCAG AA both as a button fill (white text,
124
240
  5.1:1) and as text on --k-bg (4.7:1). */
125
241
  --k-brand: #0e7c64;
126
242
  --k-brand-contrast: #ffffff;
243
+ --k-action: #0e7c64;
244
+ --k-action-contrast: #ffffff;
245
+ --k-focus: #0e7c64;
127
246
  --k-positive: #168257;
128
247
  --k-caution: #8a5a00;
129
248
  --k-negative: #c83b3b;
130
249
  --k-neutral: #5f6975;
131
250
  --k-active: #3f6fd6;
251
+ --k-status-contrast: #ffffff;
252
+
253
+ /* Light trust inks are dark enough that each label clears AA on its own
254
+ tinted fill; check:contrast rates every pair. */
255
+ --k-trust-unknown: #535c66;
256
+ --k-trust-unknown-fill: #eeeff0;
257
+ --k-trust-proposed: #2855ad;
258
+ --k-trust-proposed-fill: #eaeef7;
259
+ --k-trust-assumed: #6938a7;
260
+ --k-trust-assumed-fill: #f0ebf6;
261
+ --k-trust-verified: #006d42;
262
+ --k-trust-verified-fill: #e6f0ec;
263
+ --k-trust-stale: #855a00;
264
+ --k-trust-stale-fill: #f3efe6;
265
+ --k-trust-disputed: #94296f;
266
+ --k-trust-disputed-fill: #f4eaf1;
267
+ --k-trust-superseded: #016770;
268
+ --k-trust-superseded-fill: #e6f0f1;
269
+ --k-trust-rejected: #a12628;
270
+ --k-trust-rejected-fill: #f6e9ea;
271
+ --k-trust-revoked: #713408;
272
+ --k-trust-revoked-fill: #f1ebe6;
273
+
274
+ /* Tokens derived with var() resolve on the element that declares them and
275
+ are inherited as computed values, so each scope that changes an input
276
+ redeclares them. Without this, data-theme on a descendant of <html> would
277
+ keep the dark default's ring and soft fills. */
278
+ --k-focus-ring: var(--k-focus);
279
+ --k-positive-soft: color-mix(in oklab, var(--k-positive) 14%, transparent);
280
+ --k-caution-soft: color-mix(in oklab, var(--k-caution) 14%, transparent);
281
+ --k-negative-soft: color-mix(in oklab, var(--k-negative) 14%, transparent);
282
+ --k-active-soft: color-mix(in oklab, var(--k-active) 14%, transparent);
132
283
  }
133
284
 
134
285
 
286
+ /* Product themes. Each theme sets its brand (identity) and, beside it, the
287
+ action and focus roles. The roles hold their own values rather than aliasing
288
+ the brand, so a later brand override cannot break primary actions or focus.
289
+ Every scope that sets --k-action also sets --k-action-contrast: a theme
290
+ nested under another theme's scope would otherwise inherit that scope's text
291
+ color onto its own fill.
292
+
293
+ Every scope that sets --k-focus also redeclares --k-focus-ring, and every
294
+ scope that sets a status hue redeclares its soft fill: a var()-derived token
295
+ resolves where it is declared, so without this a scope below the root would
296
+ inherit the root's value (issue 78). check:tokens enforces both.
297
+
298
+ Each light block matches three placements of the theme class and
299
+ data-theme="light": on one element, the theme class below the light
300
+ attribute, and the light attribute below the theme class. The last is
301
+ wrapped in :where() so it weighs the same as the bare [data-theme="light"]
302
+ block in tokens.css (which it follows, so it wins there) and less than a
303
+ theme class sitting on the light element itself: a nested theme keeps its
304
+ own identity.
305
+
306
+ Scoping rule (ui#80, ui#84; DESIGN.md "Theme and mode scoping"): the
307
+ nearest data-theme picks the mode and the nearest theme class picks the
308
+ product. The :where(:not(...)) tails enforce "nearest" without changing a
309
+ selector's weight: the class-below-attribute form stops at a nearer
310
+ data-theme="dark", and the attribute-below-class form stops at a nearer
311
+ (or its own) other theme class. Weights are unchanged, so an override
312
+ declared later with these selectors still wins. */
313
+
135
314
  .survey-workbench-embed.theme-survey{
136
315
  --k-bg: #06080b;
137
316
  --k-brand: #5ce0c6;
138
- --k-text-faint: #6f8095;
317
+ --k-action: #5ce0c6;
318
+ --k-action-contrast: #06080b;
319
+ --k-focus: #5ce0c6;
320
+ --k-text-faint: #78889b;
321
+ --k-focus-ring: var(--k-focus);
139
322
  }
140
323
 
141
324
  .survey-workbench-embed.theme-console{
@@ -147,6 +330,9 @@
147
330
  --k-radius-overlay: 0;
148
331
  --k-brand: #c9ff4a;
149
332
  --k-brand-contrast: #11120f;
333
+ --k-action: #c9ff4a;
334
+ --k-action-contrast: #11120f;
335
+ --k-focus: #c9ff4a;
150
336
  --k-bg: #11120f;
151
337
  --k-panel: #191b16;
152
338
  --k-panel-raised: #20231e;
@@ -154,30 +340,81 @@
154
340
  --k-line-strong: #8ea36e;
155
341
  --k-text: #f2f0e8;
156
342
  --k-text-muted: #a6ab9c;
157
- --k-text-faint: #747a6c;
343
+ --k-text-faint: #858b7e;
158
344
  --k-positive: #a6d37b;
159
345
  --k-caution: #e8c15f;
160
346
  --k-negative: #ee776f;
161
347
  --k-active: #84d8c8;
348
+ --k-status-contrast: #11120f;
349
+ --k-focus-ring: var(--k-focus);
350
+ --k-positive-soft: color-mix(in oklab, var(--k-positive) 14%, transparent);
351
+ --k-caution-soft: color-mix(in oklab, var(--k-caution) 14%, transparent);
352
+ --k-negative-soft: color-mix(in oklab, var(--k-negative) 14%, transparent);
353
+ --k-active-soft: color-mix(in oklab, var(--k-active) 14%, transparent);
162
354
  }
163
355
 
356
+ /* Flow dark: the brand is the accent lightened (OKLCH hue within 0.3°, same chroma)
357
+ just enough to read as text at 4.5:1 on bg, panel and raised panel; the
358
+ action and focus keep the accent, which clears their 3:1 and dark-text
359
+ pairs. */
164
360
  .survey-workbench-embed.theme-flow{
165
- --k-brand: #2f88a6;
361
+ --k-brand: #3890ae;
362
+ --k-action: #2f88a6;
363
+ --k-action-contrast: #06080b;
364
+ --k-focus: #2f88a6;
365
+ --k-focus-ring: var(--k-focus);
166
366
  }
167
367
 
168
368
  .survey-workbench-embed.theme-surface{
169
369
  --k-brand: #14a37a;
170
- }
171
-
370
+ --k-action: #14a37a;
371
+ --k-action-contrast: #06080b;
372
+ --k-focus: #14a37a;
373
+ --k-focus-ring: var(--k-focus);
374
+ }
375
+
376
+ /* Station: the draft accent, adjusted per mode only where WCAG AA needs it.
377
+ Dark: the accent carries white text as a fill but is too dark to read as
378
+ text on the dark panel, so the focus ring is the accent lightened to clear
379
+ contrast, and the brand is lightened a little further (OKLCH hue within 0.1°) to
380
+ read as text at 4.5:1 on bg, panel and raised panel; the action keeps the
381
+ draft accent. */
382
+ .survey-workbench-embed.theme-station{
383
+ --k-brand: #966aff;
384
+ --k-action: #7c3aed;
385
+ --k-action-contrast: #ffffff;
386
+ --k-focus: #9364ff;
387
+ --k-focus-ring: var(--k-focus);
388
+ }
389
+
390
+ /* Survey light: the brand is the accent darkened (same OKLCH hue and chroma) just enough
391
+ to read as text at 4.5:1 on the light canvas; the action and focus keep the
392
+ accent. */
172
393
  .survey-workbench-embed[data-theme="light"].theme-survey,
173
- .survey-workbench-embed[data-theme="light"] .theme-survey{
174
- --k-brand: #16806f;
175
- }
176
-
394
+ .survey-workbench-embed[data-theme="light"] .theme-survey:where(:not([data-theme="dark"], [data-theme="light"] [data-theme="dark"] *)),
395
+ .survey-workbench-embed :where(.theme-survey) [data-theme="light"]:where(:not(.theme-console, .theme-flow, .theme-surface, .theme-station, .theme-survey :is(.theme-console, .theme-flow, .theme-surface, .theme-station) *)){
396
+ --k-bg: #f5f4ef;
397
+ --k-text-faint: #6a707b;
398
+ --k-brand: #107e6d;
399
+ --k-action: #16806f;
400
+ --k-action-contrast: #ffffff;
401
+ --k-focus: #16806f;
402
+ --k-focus-ring: var(--k-focus);
403
+ }
404
+
405
+ /* Console light: the brand is the lime darkened (OKLCH hue within 0.1°) just enough
406
+ to read as text at 4.5:1 on bg, panel and raised panel, and it keeps white
407
+ brand-contrast text (5.1:1; dark text would be 3.7:1). The action and focus
408
+ keep the brighter lime with dark text. */
177
409
  .survey-workbench-embed[data-theme="light"].theme-console,
178
- .survey-workbench-embed[data-theme="light"] .theme-console{
179
- --k-brand: #6c9400;
410
+ .survey-workbench-embed[data-theme="light"] .theme-console:where(:not([data-theme="dark"], [data-theme="light"] [data-theme="dark"] *)),
411
+ .survey-workbench-embed :where(.theme-console) [data-theme="light"]:where(:not(.theme-survey, .theme-flow, .theme-surface, .theme-station, .theme-console :is(.theme-survey, .theme-flow, .theme-surface, .theme-station) *)){
412
+ --k-brand: #577800;
180
413
  --k-brand-contrast: #ffffff;
414
+ --k-action: #6c9400;
415
+ --k-action-contrast: #11120f;
416
+ --k-focus: #6c9400;
417
+ --k-focus-ring: var(--k-focus);
181
418
  --k-bg: #f3f5eb;
182
419
  --k-panel: #fbfcf7;
183
420
  --k-panel-raised: #eef2e6;
@@ -185,21 +422,114 @@
185
422
  --k-line-strong: #8fa36f;
186
423
  --k-text: #1e2319;
187
424
  --k-text-muted: #596250;
188
- --k-text-faint: #77816d;
425
+ --k-text-faint: #68705f;
189
426
  --k-positive: #2f7d32;
190
427
  --k-caution: #8a6500;
191
428
  --k-negative: #b93a36;
192
429
  --k-active: #247f75;
430
+ --k-status-contrast: #ffffff;
431
+ --k-positive-soft: color-mix(in oklab, var(--k-positive) 14%, transparent);
432
+ --k-caution-soft: color-mix(in oklab, var(--k-caution) 14%, transparent);
433
+ --k-negative-soft: color-mix(in oklab, var(--k-negative) 14%, transparent);
434
+ --k-active-soft: color-mix(in oklab, var(--k-active) 14%, transparent);
193
435
  }
194
436
 
195
437
  .survey-workbench-embed[data-theme="light"].theme-flow,
196
- .survey-workbench-embed[data-theme="light"] .theme-flow{
438
+ .survey-workbench-embed[data-theme="light"] .theme-flow:where(:not([data-theme="dark"], [data-theme="light"] [data-theme="dark"] *)),
439
+ .survey-workbench-embed :where(.theme-flow) [data-theme="light"]:where(:not(.theme-survey, .theme-console, .theme-surface, .theme-station, .theme-flow :is(.theme-survey, .theme-console, .theme-surface, .theme-station) *)){
197
440
  --k-brand: #1f6f88;
441
+ --k-action: #1f6f88;
442
+ --k-action-contrast: #ffffff;
443
+ --k-focus: #1f6f88;
444
+ --k-focus-ring: var(--k-focus);
198
445
  }
199
446
 
200
447
  .survey-workbench-embed[data-theme="light"].theme-surface,
201
- .survey-workbench-embed[data-theme="light"] .theme-surface{
448
+ .survey-workbench-embed[data-theme="light"] .theme-surface:where(:not([data-theme="dark"], [data-theme="light"] [data-theme="dark"] *)),
449
+ .survey-workbench-embed :where(.theme-surface) [data-theme="light"]:where(:not(.theme-survey, .theme-console, .theme-flow, .theme-station, .theme-surface :is(.theme-survey, .theme-console, .theme-flow, .theme-station) *)){
202
450
  --k-brand: #0f6b52;
451
+ --k-action: #0f6b52;
452
+ --k-action-contrast: #ffffff;
453
+ --k-focus: #0f6b52;
454
+ --k-focus-ring: var(--k-focus);
455
+ }
456
+
457
+ .survey-workbench-embed[data-theme="light"].theme-station,
458
+ .survey-workbench-embed[data-theme="light"] .theme-station:where(:not([data-theme="dark"], [data-theme="light"] [data-theme="dark"] *)),
459
+ .survey-workbench-embed :where(.theme-station) [data-theme="light"]:where(:not(.theme-survey, .theme-console, .theme-flow, .theme-surface, .theme-station :is(.theme-survey, .theme-console, .theme-flow, .theme-surface) *)){
460
+ --k-brand: #7c3aed;
461
+ --k-action: #7c3aed;
462
+ --k-action-contrast: #ffffff;
463
+ --k-focus: #7c3aed;
464
+ --k-focus-ring: var(--k-focus);
465
+ }
466
+
467
+
468
+ /* Dark islands (ui#80): a data-theme="dark" element below a light element
469
+ takes its nearest theme's dark values. tokens.css resets it to the default
470
+ dark contract first; each block below repeats its theme's mode-dependent
471
+ base values (check:contrast fails if one drifts) and, like the light
472
+ blocks, yields to a nearer theme class. Without a light ancestor nothing
473
+ needs resetting, so these blocks do not match and the element inherits. */
474
+
475
+ .survey-workbench-embed[data-theme="dark"]:where(.theme-survey *):where([data-theme="light"] *):where(:not(.theme-console, .theme-flow, .theme-surface, .theme-station, .theme-survey :is(.theme-console, .theme-flow, .theme-surface, .theme-station) *)){
476
+ --k-bg: #06080b;
477
+ --k-brand: #5ce0c6;
478
+ --k-action: #5ce0c6;
479
+ --k-action-contrast: #06080b;
480
+ --k-focus: #5ce0c6;
481
+ --k-text-faint: #78889b;
482
+ --k-focus-ring: var(--k-focus);
483
+ }
484
+
485
+ .survey-workbench-embed[data-theme="dark"]:where(.theme-console *):where([data-theme="light"] *):where(:not(.theme-survey, .theme-flow, .theme-surface, .theme-station, .theme-console :is(.theme-survey, .theme-flow, .theme-surface, .theme-station) *)){
486
+ --k-brand: #c9ff4a;
487
+ --k-brand-contrast: #11120f;
488
+ --k-action: #c9ff4a;
489
+ --k-action-contrast: #11120f;
490
+ --k-focus: #c9ff4a;
491
+ --k-bg: #11120f;
492
+ --k-panel: #191b16;
493
+ --k-panel-raised: #20231e;
494
+ --k-line: #3a4035;
495
+ --k-line-strong: #8ea36e;
496
+ --k-text: #f2f0e8;
497
+ --k-text-muted: #a6ab9c;
498
+ --k-text-faint: #858b7e;
499
+ --k-positive: #a6d37b;
500
+ --k-caution: #e8c15f;
501
+ --k-negative: #ee776f;
502
+ --k-active: #84d8c8;
503
+ --k-status-contrast: #11120f;
504
+ --k-focus-ring: var(--k-focus);
505
+ --k-positive-soft: color-mix(in oklab, var(--k-positive) 14%, transparent);
506
+ --k-caution-soft: color-mix(in oklab, var(--k-caution) 14%, transparent);
507
+ --k-negative-soft: color-mix(in oklab, var(--k-negative) 14%, transparent);
508
+ --k-active-soft: color-mix(in oklab, var(--k-active) 14%, transparent);
509
+ }
510
+
511
+ .survey-workbench-embed[data-theme="dark"]:where(.theme-flow *):where([data-theme="light"] *):where(:not(.theme-survey, .theme-console, .theme-surface, .theme-station, .theme-flow :is(.theme-survey, .theme-console, .theme-surface, .theme-station) *)){
512
+ --k-brand: #3890ae;
513
+ --k-action: #2f88a6;
514
+ --k-action-contrast: #06080b;
515
+ --k-focus: #2f88a6;
516
+ --k-focus-ring: var(--k-focus);
517
+ }
518
+
519
+ .survey-workbench-embed[data-theme="dark"]:where(.theme-surface *):where([data-theme="light"] *):where(:not(.theme-survey, .theme-console, .theme-flow, .theme-station, .theme-surface :is(.theme-survey, .theme-console, .theme-flow, .theme-station) *)){
520
+ --k-brand: #14a37a;
521
+ --k-action: #14a37a;
522
+ --k-action-contrast: #06080b;
523
+ --k-focus: #14a37a;
524
+ --k-focus-ring: var(--k-focus);
525
+ }
526
+
527
+ .survey-workbench-embed[data-theme="dark"]:where(.theme-station *):where([data-theme="light"] *):where(:not(.theme-survey, .theme-console, .theme-flow, .theme-surface, .theme-station :is(.theme-survey, .theme-console, .theme-flow, .theme-surface) *)){
528
+ --k-brand: #966aff;
529
+ --k-action: #7c3aed;
530
+ --k-action-contrast: #ffffff;
531
+ --k-focus: #9364ff;
532
+ --k-focus-ring: var(--k-focus);
203
533
  }
204
534
 
205
535
 
@@ -296,31 +626,13 @@
296
626
  .survey-workbench-embed .queue-result-count{ margin-left: auto; color: var(--k-muted); }
297
627
  .survey-workbench-embed .queue-empty{ padding: 2rem; color: var(--k-muted); text-align: center; }
298
628
 
299
- /* Re-assert the base structural tokens for light mode. themes.css's `.theme-*`
300
- preset classes hardcode a dark --k-bg/--k-panel/etc (so the preset "just
301
- works" without requiring [data-theme] to be set), and a bare class selector
302
- ties with the `[data-theme="light"]` selector in tokens.css on specificity —
303
- so source order decides, and themes.css loads after tokens.css. Re-declaring
304
- the light values here (loaded after themes.css) wins that tie-break. Presets
305
- still control --k-brand (and theme-console's fuller palette) via their own,
306
- more specific `[data-theme="light"].theme-*` rules in themes.css. */
629
+ /* Light-mode token values come from @kontourai/ui (tokens.css plus each
630
+ theme's light block, which since 1.17 re-sets every mode-dependent value
631
+ its dark preset sets). Only color-scheme is re-asserted here: the :root rule
632
+ above sets it to dark and would otherwise win the source-order tie with
633
+ tokens.css's [data-theme="light"], leaving native controls dark. */
307
634
  .survey-workbench-embed[data-theme="light"]{
308
635
  color-scheme: light;
309
- --k-bg: #f5f4ef;
310
- --k-panel: #ffffff;
311
- --k-panel-raised: #fbfaf7;
312
- --k-line: rgba(36, 40, 46, 0.12);
313
- --k-line-strong: rgba(36, 40, 46, 0.20);
314
- --k-shadow: 0 18px 50px -30px rgba(0, 0, 0, 0.25);
315
- --k-text: #202124;
316
- --k-text-muted: #5b626b;
317
- --k-text-faint: #707782;
318
- --k-brand-contrast: #ffffff;
319
- --k-positive: #168257;
320
- --k-caution: #8a5a00;
321
- --k-negative: #c83b3b;
322
- --k-neutral: #5f6975;
323
- --k-active: #3f6fd6;
324
636
  }
325
637
 
326
638
  .survey-workbench-embed *{
@@ -91,31 +91,13 @@
91
91
  .queue-result-count { margin-left: auto; color: var(--k-muted); }
92
92
  .queue-empty { padding: 2rem; color: var(--k-muted); text-align: center; }
93
93
 
94
- /* Re-assert the base structural tokens for light mode. themes.css's `.theme-*`
95
- preset classes hardcode a dark --k-bg/--k-panel/etc (so the preset "just
96
- works" without requiring [data-theme] to be set), and a bare class selector
97
- ties with the `[data-theme="light"]` selector in tokens.css on specificity —
98
- so source order decides, and themes.css loads after tokens.css. Re-declaring
99
- the light values here (loaded after themes.css) wins that tie-break. Presets
100
- still control --k-brand (and theme-console's fuller palette) via their own,
101
- more specific `[data-theme="light"].theme-*` rules in themes.css. */
94
+ /* Light-mode token values come from @kontourai/ui (tokens.css plus each
95
+ theme's light block, which since 1.17 re-sets every mode-dependent value
96
+ its dark preset sets). Only color-scheme is re-asserted here: the :root rule
97
+ above sets it to dark and would otherwise win the source-order tie with
98
+ tokens.css's [data-theme="light"], leaving native controls dark. */
102
99
  [data-theme="light"] {
103
100
  color-scheme: light;
104
- --k-bg: #f5f4ef;
105
- --k-panel: #ffffff;
106
- --k-panel-raised: #fbfaf7;
107
- --k-line: rgba(36, 40, 46, 0.12);
108
- --k-line-strong: rgba(36, 40, 46, 0.20);
109
- --k-shadow: 0 18px 50px -30px rgba(0, 0, 0, 0.25);
110
- --k-text: #202124;
111
- --k-text-muted: #5b626b;
112
- --k-text-faint: #707782;
113
- --k-brand-contrast: #ffffff;
114
- --k-positive: #168257;
115
- --k-caution: #8a5a00;
116
- --k-negative: #c83b3b;
117
- --k-neutral: #5f6975;
118
- --k-active: #3f6fd6;
119
101
  }
120
102
 
121
103
  * {