@kolkrabbi/kol-theme 0.132.1 → 0.134.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.
@@ -53,7 +53,12 @@
53
53
  display: inline-flex;
54
54
  align-items: center;
55
55
  border-radius: var(--kol-radius-sm);
56
- border: 1px solid transparent;
56
+ /* paint from the TONE properties (kol-components-molecules.css, TONE):
57
+ * `--filled` / `--outline` are aliases of the primary / outline tones, the
58
+ * fallback is filled, and a toned wrapper reaches a shell with no modifier */
59
+ background-color: var(--kol-tone-bg, var(--kol-surface-secondary));
60
+ color: var(--kol-tone-fg, var(--kol-surface-on-primary));
61
+ border: 1px solid var(--kol-tone-border, transparent);
57
62
  transition: background-color 150ms ease,
58
63
  border-color 150ms ease,
59
64
  color 150ms ease;
@@ -62,10 +67,9 @@
62
67
 
63
68
  /* ─── Variants ─── */
64
69
 
65
- .kol-control--filled {
66
- background-color: var(--kol-surface-secondary);
67
- color: var(--kol-surface-on-primary);
68
- }
70
+ /* `.kol-control--filled` / `.kol-control--outline` — tone aliases, see TONE.
71
+ * `.kol-control--plain` is the bare shell (ViewToggle's inactive text chip):
72
+ * no fill, no border, the meta ink. */
69
73
 
70
74
  .kol-control--ghost {
71
75
  /* DEPRECATED (2026-07-08 chrome law) — ghost folded into outline; Input
@@ -80,13 +84,6 @@
80
84
  color: var(--kol-surface-on-primary);
81
85
  }
82
86
 
83
- .kol-control--outline {
84
- background-color: transparent;
85
- color: var(--kol-surface-on-primary);
86
- /* oq-08 — the one outline border (user ruling 2026-08-26); was oq-16 */
87
- border-color: var(--kol-oq-08);
88
- }
89
-
90
87
  /* Textarea modifier — flips the inline-flex shell to block so a multi-line
91
88
  * textarea takes full width. Vertical resize is real (2026-07-08): the
92
89
  * resize-grip icon is the JS drag handle (both axes); native resize is
@@ -239,7 +236,20 @@
239
236
  color var(--kol-transition-base),
240
237
  border-color var(--kol-transition-base);
241
238
  cursor: pointer;
242
- }
239
+ /* THE TONE AXIS (tone-is-the-ground-axis, 2026-09-03): the paint is read
240
+ * from `--kol-tone-*` — set on the element by its own variant / tone class,
241
+ * or inherited from a toned wrapper — and the fallbacks are `primary`, so a
242
+ * bare button with no tone anywhere renders exactly what it always did.
243
+ * The bundles live in kol-components-molecules.css (TONE). */
244
+ background-color: var(--kol-tone-bg, var(--kol-surface-secondary));
245
+ background-image: var(--kol-tone-image, none);
246
+ color: var(--kol-tone-fg, var(--kol-surface-on-primary));
247
+ border: 1px solid var(--kol-tone-border, transparent);
248
+ }
249
+ /* WEIGHT IS THE VARIANT'S, not the tone's: a tone is a ground, and a ground
250
+ * must not re-weigh a label — a sunken secondary keeps its 500 (matrix-verified
251
+ * 2026-09-03, the one regression the first cut had). */
252
+ .kol-btn-secondary, .kol-btn-accent, .kol-btn-danger, .kol-btn.kol-tone-secondary { font-weight: 500; }
243
253
 
244
254
  .kol-btn:focus-visible {
245
255
  outline: 2px solid var(--kol-focus-ring);
@@ -279,135 +289,26 @@
279
289
  .kol-btn-icon.kol-btn-md { width: 32px; height: 32px; padding: 0; }
280
290
  .kol-btn-icon.kol-btn-lg { width: 40px; height: 40px; padding: 0; }
281
291
 
282
- .kol-btn-primary {
283
- background: var(--kol-surface-secondary);
284
- color: var(--kol-surface-on-primary);
285
- border: 1px solid transparent;
286
- }
287
- @media (hover: hover) {
288
- .kol-btn-primary:not(.kol-btn-animate):not(.kol-dd-trigger):hover {
289
- background-color: var(--kol-oq-08);
290
- color: var(--kol-surface-on-primary);
291
- }
292
- }
293
-
294
- .kol-btn-secondary {
295
- background: var(--kol-surface-on-primary);
296
- color: var(--kol-surface-primary);
297
- border: 1px solid transparent;
298
- font-weight: 500;
299
- }
292
+ /* ─── Variants — the ground ones are TONES now (2026-09-03) ───
293
+ * `.kol-btn-primary` · `-secondary` · `-outline` · `-ghost` · `-grey` are
294
+ * aliases of `.kol-tone-*`, and `-nav` · `-danger` · `-accent` carry their own
295
+ * bundles — all in kol-components-molecules.css (TONE). What used to be eight
296
+ * hover rules is one, reading each tone's own rungs. */
300
297
  @media (hover: hover) {
301
- .kol-btn-secondary:not(.kol-btn-animate):not(.kol-dd-trigger):hover {
302
- background-color: var(--kol-oq-inverse-40);
303
- color: var(--kol-surface-primary);
304
- border-color: transparent;
298
+ .kol-btn:not(.kol-btn-animate):not(.kol-dd-trigger):hover {
299
+ background-color: var(--kol-tone-hover-bg, var(--kol-oq-08));
300
+ background-image: var(--kol-tone-hover-image, none);
301
+ color: var(--kol-tone-hover-fg, var(--kol-surface-on-primary));
302
+ border-color: var(--kol-tone-hover-border, transparent);
305
303
  }
306
304
  }
307
-
308
- .kol-btn-accent {
309
- background: var(--kol-accent-primary);
310
- color: var(--kol-accent-on-primary);
311
- border: 1px solid transparent;
312
- font-weight: 500;
313
- }
314
- @media (hover: hover) {
315
- .kol-btn-accent:not(.kol-btn-animate):not(.kol-dd-trigger):hover {
316
- background-color: var(--kol-accent-primary-strong);
317
- color: var(--kol-accent-on-primary);
318
- }
319
- }
320
-
321
- .kol-btn-outline {
322
- background: transparent;
323
- color: var(--kol-surface-on-primary);
324
- /* ONE outline border across the controls (user ruling 2026-08-26): OPAQUE
325
- * 8% — the system's hairline. oq-16 read heavy beside the segmented
326
- * toggle's fg-04, and fg is out: a translucent border doubles where two
327
- * controls touch. Button outline · .kol-control--outline · .kol-seg agree. */
328
- border: 1px solid var(--kol-oq-08);
329
- }
330
- @media (hover: hover) {
331
- .kol-btn-outline:not(.kol-btn-animate):not(.kol-dd-trigger):hover {
332
- background-color: var(--kol-oq-02);
333
- border-color: color-mix(in srgb, var(--kol-surface-on-primary) 25%, var(--kol-surface-primary));
334
- color: var(--kol-surface-on-primary);
335
- }
336
- }
337
-
338
- .kol-btn-ghost {
339
- background: transparent;
340
- color: var(--kol-oq-48);
341
- border: 1px solid transparent;
342
- }
343
- @media (hover: hover) {
344
- .kol-btn-ghost:not(.kol-btn-animate):not(.kol-dd-trigger):hover {
345
- background-color: var(--kol-oq-04);
346
- color: var(--kol-surface-on-primary);
347
- }
348
- }
349
-
350
- /* Navigation (2026-07-28, user-ordered) — chrome for links that move between
351
- * pages, not tool actions. The SQUARE comes from `.kol-btn-icon` above — one
352
- * height per size, 22/26/32/40, so mixed bars line up (this rule described a
353
- * padding-derived box, `sm 24 / md 30 / lg 36`, from before the square was
354
- * pinned; corrected 2026-09-03). Quiet states: rest dimmed, hover the
355
- * ghost wash, and ACTIVE keys off aria-current="page" (NavLink sets it
356
- * natively) — brighter glyph on a faint wash, never the pressed fill;
357
- * navigation is location, not a toggled tool. */
358
- .kol-btn-nav {
359
- background: transparent;
360
- color: var(--kol-oq-80);
361
- border: 1px solid transparent;
362
- }
363
- @media (hover: hover) {
364
- .kol-btn-nav:not(.kol-btn-animate):not(.kol-dd-trigger):hover {
365
- background-color: var(--kol-oq-04);
366
- color: var(--kol-surface-on-primary);
367
- }
368
- }
369
- /* active = brightness only (0.11.7, user-ordered): quiet chrome states ride
370
- * the opacity ladder — a background block is pressed-tool language, not
371
- * location. Hover keeps its transient wash (feedback, not state). */
305
+ /* nav's ACTIVE keys off aria-current="page" (NavLink sets it natively) —
306
+ * brightness only (0.11.7, user-ordered): quiet chrome states ride the
307
+ * opacity ladder; a background block is pressed-tool language, not location.
308
+ * Hover keeps its transient wash (feedback, not state). */
372
309
  .kol-btn-nav[aria-current="page"] {
373
310
  color: var(--kol-oq-88);
374
311
  }
375
- /* square box comes from .kol-btn-icon (geometry, all variants) — no
376
- * nav-specific padding rules needed */
377
-
378
- /* Grey (2026-07-15, user-ordered) — pill-subtle's visual weight as a real
379
- * Button variant on the opaque tier: rest oq-12, hover/active one stop past
380
- * each, same state math as the rest of the family. */
381
- .kol-btn-grey {
382
- /* Ladder (user ruling 2026-08-15): 12 rest → 04 hover (darker) → 32 press,
383
- * then eased back down. */
384
- background: var(--kol-oq-12);
385
- color: var(--kol-surface-on-primary);
386
- border: 1px solid transparent;
387
- }
388
- @media (hover: hover) {
389
- .kol-btn-grey:not(.kol-btn-animate):not(.kol-dd-trigger):hover {
390
- background-color: var(--kol-oq-16);
391
- color: var(--kol-surface-on-primary);
392
- }
393
- }
394
-
395
- /* Destructive actions (2026-07-15, chess consumer audit finding 1: red was
396
- * being hacked onto normal buttons via className). Accent's shape on the
397
- * --ui-error state token — filled, opaque interaction stops, white label
398
- * (error red carries it in both themes). */
399
- .kol-btn-danger {
400
- background: var(--ui-error);
401
- color: var(--kol-color-ab-white);
402
- border: 1px solid transparent;
403
- font-weight: 500;
404
- }
405
- @media (hover: hover) {
406
- .kol-btn-danger:not(.kol-btn-animate):not(.kol-dd-trigger):hover {
407
- background-color: color-mix(in srgb, var(--ui-error) 80%, var(--kol-surface-primary));
408
- color: var(--kol-color-ab-white);
409
- }
410
- }
411
312
 
412
313
  /* xs — the panel rung (ControlsXsRung, 2026-09-01): 22px shell (20 + ring), xs radius */
413
314
  .kol-btn-xs { padding: 4px 8px; border-radius: var(--kol-radius-xs); }
@@ -423,13 +324,10 @@
423
324
  * on :hover — and that restatement was specificity (0,4,0) against the sunken
424
325
  * tone's (0,2,0), so hovering a SUNKEN trigger repainted it in the untoned
425
326
  * fill: a light box beside two icon controls that stayed dark. */
426
- .kol-btn-primary:not(.kol-btn-animate):not(:disabled):not(.kol-dd-trigger):active { background-color: var(--kol-oq-16); }
427
- .kol-btn-secondary:not(.kol-btn-animate):not(:disabled):not(.kol-dd-trigger):active { background-color: var(--kol-oq-inverse-48); color: var(--kol-surface-primary); }
428
- .kol-btn-accent:not(.kol-btn-animate):not(:disabled):not(.kol-dd-trigger):active { background-color: color-mix(in srgb, var(--kol-accent-primary) 70%, var(--kol-surface-primary)); }
429
- .kol-btn-outline:not(.kol-btn-animate):not(:disabled):not(.kol-dd-trigger):active,
430
- .kol-btn-ghost:not(.kol-btn-animate):not(:disabled):not(.kol-dd-trigger):active { background-color: var(--kol-oq-08); }
431
- .kol-btn-danger:not(.kol-btn-animate):not(:disabled):not(.kol-dd-trigger):active { background-color: color-mix(in srgb, var(--ui-error) 70%, var(--kol-surface-primary)); }
432
- .kol-btn-grey:not(.kol-btn-animate):not(:disabled):not(.kol-dd-trigger):active { background-color: var(--kol-oq-24); }
327
+ .kol-btn:not(.kol-btn-animate):not(:disabled):not(.kol-dd-trigger):active {
328
+ background-color: var(--kol-tone-active-bg, var(--kol-oq-16));
329
+ background-image: var(--kol-tone-active-image, none);
330
+ }
433
331
 
434
332
  .kol-btn:disabled,
435
333
  .kol-btn[disabled] {
@@ -1156,8 +1054,9 @@ a:hover .icon-hover,
1156
1054
  * lit-when-active idiom. Always composes with a variant class — the
1157
1055
  * variant supplies the border that suppresses UA button chrome. */
1158
1056
  .kol-btn-pressed {
1159
- background-color: var(--kol-surface-on-primary);
1160
- color: var(--kol-surface-primary);
1057
+ background-color: var(--kol-tone-pressed-bg, var(--kol-surface-on-primary));
1058
+ background-image: var(--kol-tone-pressed-image, none);
1059
+ color: var(--kol-tone-pressed-fg, var(--kol-surface-primary));
1161
1060
  }
1162
1061
 
1163
1062
  /* ─────────────────────────────────────────────────────────────────────
@@ -1212,6 +1111,13 @@ a:hover .icon-hover,
1212
1111
  line-height: 0;
1213
1112
  border-radius: var(--kol-radius-sm);
1214
1113
  padding: 0;
1114
+ /* paint from the TONE properties (kol-components-molecules.css, TONE); the
1115
+ * variant classes are aliases of the tones, the fallback is `secondary` —
1116
+ * the frame's default since it was promoted — and a toned wrapper reaches a
1117
+ * frame with no variant of its own. No states, as ever. */
1118
+ background-color: var(--kol-tone-bg, var(--kol-surface-on-primary));
1119
+ color: var(--kol-tone-fg, var(--kol-surface-primary));
1120
+ border: 1px solid var(--kol-tone-border, transparent);
1215
1121
  }
1216
1122
 
1217
1123
  /* An ACTIONABLE frame — `onClick`/`href` (user ruling 2026-08-01: *"you dont
@@ -1257,46 +1163,12 @@ a.kol-icon-frame {
1257
1163
  .kol-icon-frame-md { width: 32px; height: 32px; }
1258
1164
  .kol-icon-frame-lg { width: 40px; height: 40px; }
1259
1165
 
1260
- .kol-icon-frame-primary {
1261
- background: var(--kol-surface-secondary);
1262
- color: var(--kol-surface-on-primary);
1263
- border: 1px solid transparent;
1264
- }
1265
- .kol-icon-frame-secondary {
1266
- background: var(--kol-surface-on-primary);
1267
- color: var(--kol-surface-primary);
1268
- border: 1px solid transparent;
1269
- }
1270
- .kol-icon-frame-accent {
1271
- background: var(--kol-accent-primary);
1272
- color: var(--kol-accent-on-primary);
1273
- border: 1px solid transparent;
1274
- }
1275
- .kol-icon-frame-outline {
1276
- background: transparent;
1277
- color: var(--kol-surface-on-primary);
1278
- border: 1px solid var(--kol-oq-08);
1279
- }
1280
- .kol-icon-frame-ghost {
1281
- background: transparent;
1282
- color: var(--kol-oq-48);
1283
- border: 1px solid transparent;
1284
- }
1285
- .kol-icon-frame-nav {
1286
- background: transparent;
1287
- /* full ink, not oq-64 (user re-rule 2026-08-09: header glyphs at 100%
1288
- * opacity — the dimmed row read as disabled beside the ThemeToggle). */
1289
- color: var(--kol-surface-on-primary);
1290
- border: 1px solid transparent;
1291
- }
1292
- /* plate — the ONE exception in 05-control-chrome.md § Icon box: an affordance
1293
- * sitting on a PHOTO. It needs an opaque plate and a backdrop blur to stay
1294
- * legible over arbitrary pixels, and no Button variant covers it. Values are
1295
- * MediaCard's, which is the instance the doc names. */
1166
+ /* `.kol-icon-frame-{primary,secondary,accent,outline,ghost,nav,grey,danger}` — tone aliases, see TONE */
1296
1167
  .kol-icon-frame-plate {
1297
1168
  background: var(--kol-fg-ab-12, rgba(0, 0, 0, 0.4));
1298
1169
  backdrop-filter: blur(4px);
1299
1170
  color: var(--kol-surface-on-primary);
1171
+ border: 0; /* not a tone — its own glass, no ring (as before the tone axis) */
1300
1172
  }
1301
1173
 
1302
1174
  .kol-icon-frame-grey {
@@ -1354,23 +1226,25 @@ a.kol-icon-frame {
1354
1226
  /* fill="none" — invisible container. FULL-STRENGTH INK (user ruling): the text
1355
1227
  * and icon drop NO opacity. `.kol-btn-nav` rested at oq-64 only so its hover
1356
1228
  * could brighten to full; with no states that dim was just a faded label. */
1229
+ /* geometry variants read the TONE (kol-components-molecules.css) and fall
1230
+ * back to what each painted before the tone axis (2026-09-03) */
1357
1231
  .kol-theme-toggle-none {
1358
- background: transparent;
1359
- color: var(--kol-surface-on-primary);
1360
- border: 1px solid transparent;
1232
+ background-color: var(--kol-tone-bg, transparent);
1233
+ color: var(--kol-tone-fg, var(--kol-surface-on-primary));
1234
+ border: 1px solid var(--kol-tone-border, transparent);
1361
1235
  }
1362
1236
 
1363
1237
  /* fill="subtle" — the filled rung */
1364
1238
  .kol-theme-toggle-subtle {
1365
- background: var(--kol-surface-secondary);
1366
- color: var(--kol-surface-on-primary);
1367
- border: 1px solid transparent;
1239
+ background-color: var(--kol-tone-bg, var(--kol-surface-secondary));
1240
+ color: var(--kol-tone-fg, var(--kol-surface-on-primary));
1241
+ border: 1px solid var(--kol-tone-border, transparent);
1368
1242
  }
1369
1243
 
1370
1244
  /* flush — no pad, no radius, no fill, ever */
1371
1245
  .kol-theme-toggle-flush {
1372
- background: transparent;
1373
- color: var(--kol-surface-on-primary);
1246
+ background-color: var(--kol-tone-bg, transparent);
1247
+ color: var(--kol-tone-fg, var(--kol-surface-on-primary));
1374
1248
  border: 0;
1375
1249
  border-radius: 0;
1376
1250
  padding: 0;
@@ -98,12 +98,8 @@
98
98
  transition: rotate var(--kol-transition-base);
99
99
  }
100
100
  .kol-dd-trigger[data-state="open"] .kol-dd-caret { rotate: 180deg; }
101
- /* grey — the opaque oq-12 fill, dropdown-only (never a kol-btn-* variant) */
102
- .kol-dd-trigger--grey {
103
- background: var(--kol-oq-12);
104
- color: var(--kol-surface-on-primary);
105
- border: 1px solid transparent;
106
- }
101
+ /* grey — `.kol-dd-trigger--grey` is an alias of the grey TONE now (see TONE,
102
+ * below); the trigger is `.kol-btn` and paints from the tone properties */
107
103
  /* open/panel fusion — the open trigger and its panel read as ONE piece:
108
104
  * trigger squares its bottom corners, panel squares its top, and the fill
109
105
  * continues (panel offset is -1 in Dropdown.jsx for the same reason). */
@@ -117,13 +113,13 @@
117
113
  overflow: hidden;
118
114
  display: flex;
119
115
  flex-direction: column;
120
- }
121
- .kol-dd-panel--primary { background: var(--kol-surface-secondary); }
122
- .kol-dd-panel--grey { background: var(--kol-oq-12); }
123
- .kol-dd-panel--outline {
124
- background: var(--kol-surface-primary);
125
- border: 1px solid var(--kol-oq-08); /* matches the trigger's rest border */
126
- border-top: none; /* the trigger's bottom edge is the divider (docs page) */
116
+ /* the FLOATING surface of the trigger's tone (TONE, below): primary / grey /
117
+ * sunken continue their fill, outline and ghost paint the page's ground —
118
+ * `--kol-tone-ground` so opening changes nothing under the panel. The
119
+ * `--primary` / `--grey` / `--outline` classes are aliases of the tones. */
120
+ background-color: var(--kol-tone-panel-bg, var(--kol-tone-bg, var(--kol-surface-secondary)));
121
+ border: var(--kol-tone-panel-border-w, 0) solid var(--kol-tone-panel-border, transparent); /* outline: matches the trigger's rest border */
122
+ border-top: 0; /* the trigger's bottom edge is the divider (docs page) */
127
123
  }
128
124
  .kol-dd-div { border-top: 1px solid var(--kol-fg-08); }
129
125
  /* A ROWS CEILING THE CONSUMER CAN REACH (same ticket): Popover's size
@@ -1487,66 +1483,289 @@
1487
1483
  * `surface-tertiary` is `#0e0e11` in dark (14.2, genuinely below the page) and
1488
1484
  * `#ffffff` in light, so it is theme-correct on both sides where a mix toward
1489
1485
  * a fixed near-white could only ever be right on one. */
1490
- .kol-view-toggle.kol-tone-sunken, .kol-view-toggle.kol-tone-inverse { background-color: var(--kol-surface-sunken); }
1491
- .kol-view-toggle.kol-tone-sunken > button[aria-pressed="true"], .kol-view-toggle.kol-tone-inverse > button[aria-pressed="true"] { background-color: var(--kol-fg-08); }
1492
- .kol-view-toggle.kol-tone-sunken > button:not([aria-pressed="true"]):hover, .kol-view-toggle.kol-tone-inverse > button:not([aria-pressed="true"]):hover { background-color: var(--kol-fg-04); }
1493
- .kol-control.kol-tone-sunken, .kol-control.kol-tone-inverse { background-color: var(--kol-surface-sunken); }
1494
- .kol-expand.kol-tone-sunken, .kol-expand.kol-tone-inverse { background-color: var(--kol-surface-sunken); }
1495
- /* the tone's fill, one selector — nothing left to cancel (2026-08-28) */
1496
- .kol-dd-trigger.kol-tone-sunken, .kol-dd-trigger.kol-tone-inverse { background-color: var(--kol-surface-sunken); }
1497
- /* THE PANEL IS OPAQUE (user 2026-08-28: "transparent doesn't work for
1498
- * dropdown") — it floats over page content, and every `fg-*` stop is an alpha
1499
- * wash that the content reads through. `oq-inverse-*`, the opaque tier, bakes
1500
- * onto `surface-inverse` — the near-WHITE in dark theme — so even `oq-inverse-96`
1501
- * lands ~23.5 against the #121215 page's 18: lighter than the page, the wrong
1502
- * side of it. `surface-tertiary` is the flat twin of what the trigger renders:
1503
- * #0e0e11 (14.0) in dark against the trigger's 14.2, #ffffff in light. */
1504
- .kol-dd-panel.kol-tone-sunken, .kol-dd-panel.kol-tone-inverse { background-color: var(--kol-surface-sunken); }
1505
- /* the rest of the set — Button (icon-only especially), IconFrame, ThemeToggle:
1506
- * the well, transparent edge, fg-96 ink; hover the CONTRAST wash, pressed the
1507
- * fg-08 chip — both LAYERED over the well (a gradient image over the fill),
1508
- * since one background cannot stack two translucent colours.
1486
+ /* ═══════════════════════════════════════════════════════════════════════
1487
+ * TONE — the ground axis (tone-is-the-ground-axis, kol-client-olina 2026-09-03).
1509
1488
  *
1510
- * REST / HOVER / SELECTED ARE ONE LADDER, THREE RUNGS (2026-08-30):
1489
+ * User, on fxr's /settings: "it would be best to not have to set every
1490
+ * component but rather get a set with it already set" — and, reading Button's
1491
+ * variants: "I thought you would set these colors as variants … that is 6
1492
+ * tones right?". Until 0.134.0 `toneClass` returned a class for `sunken` and
1493
+ * '' for everything else: `tone` had ONE value, and the real ground vocabulary
1494
+ * — five of Button's eight variants — lived on Button alone, so Dropdown,
1495
+ * `.kol-control` and `.kol-dd-panel` each re-invented it with paints that
1496
+ * drifted (`primary` painting `surface-secondary`; the panel's outline
1497
+ * hardcoding `surface-primary`, so a dropdown opened over any other ground
1498
+ * changed the colour under it).
1511
1499
  *
1512
- * rest the well 10.2 dark · 244.8 light
1513
- * hover + fg-04 19.8 · 235.7
1514
- * selected + fg-08 29.4 · 226.7
1500
+ * SIX TONES, ONE MECHANISM. A tone is a bundle of `--kol-tone-*` custom
1501
+ * properties — rest, hover, press, pressed, and what a FLOATING surface of
1502
+ * that tone paints — and every control reads its paint from those properties
1503
+ * with its own old literal as the fallback:
1515
1504
  *
1516
- * `fg-*` already flips toward the ink, so it darkens a light well and lightens
1517
- * a dark one with no help. Two things were wrong before: the wash was a
1518
- * hardcoded WHITE, invisible over the light well (white on a 245 ground), and
1519
- * when that was first fixed it went to an 8% wash — the same strength as
1520
- * `selected`, which put hover 0.4 from selected in dark and 1.5 in light. A
1521
- * hovered unselected control was indistinguishable from the selected one.
1522
- * 04 and 08 are ~10 apart on both themes; the ladder reads.
1505
+ * .kol-btn / .kol-dd-trigger fallback primary
1506
+ * .kol-control fallback filled (= primary)
1507
+ * .kol-icon-frame fallback secondary
1508
+ * .kol-dd-panel fallback primary
1509
+ * .kol-view-toggle well · .kol-expand--open · .kol-theme-toggle-* their own
1523
1510
  *
1524
- * THE TRIGGER IS EXCLUDED. `.kol-dd-trigger` carries `.kol-btn`, so every
1525
- * `.kol-btn.kol-tone-sunken` state rule matches it unless it says otherwise —
1526
- * and "REST AND OPEN ARE THE ONLY STATES" for a Dropdown trigger is a law
1527
- * written TWICE at the top of this file (2026-07-15, re-called 2026-08-09 and
1528
- * 2026-08-28). The atoms sheet excludes the trigger from all eight variant
1529
- * hovers; these tone rules did not, so the law held everywhere except the one
1530
- * tone the settings page uses. Repeatedly reported and repeatedly missed,
1531
- * because the exclusion was checked in `kol-components-atoms.css` and the
1532
- * offending rule lives here. */
1533
- .kol-btn.kol-tone-sunken,
1534
- .kol-icon-frame.kol-tone-sunken,
1535
- .kol-theme-toggle.kol-tone-sunken {
1536
- background-color: var(--kol-surface-sunken);
1537
- border-color: transparent;
1538
- color: var(--kol-fg-96);
1539
- }
1540
- @media (hover: hover) {
1541
- .kol-btn.kol-tone-sunken:not(.kol-btn-animate):not(:disabled):not(.kol-dd-trigger):hover {
1542
- background-color: var(--kol-surface-sunken);
1543
- background-image: linear-gradient(var(--kol-fg-04), var(--kol-fg-04));
1544
- color: var(--kol-fg-96);
1545
- }
1546
- }
1511
+ * Custom properties INHERIT, which is the whole point (ticket ask 3): one
1512
+ * `.kol-tone-grey` on a wrapper tones every control inside it, because a
1513
+ * control with no tone of its own reads the inherited values. A control that
1514
+ * IS handed a variant or tone sets the properties on itself, and an element's
1515
+ * own declaration beats inheritance so the per-component prop stays the
1516
+ * exception, exactly as asked. The ground variants (`primary` · `secondary` ·
1517
+ * `outline` · `ghost` · `grey` on Button, `filled` / `outline` on the control
1518
+ * shell, the panel and icon-frame variants) are ALIASES of the tones — the
1519
+ * same bundle under `:where()`, specificity 0, so an explicit `tone` on the
1520
+ * same element wins regardless of source order. `danger` and `accent` are
1521
+ * semantic, not grounds: they stay variants and carry their own bundle. `nav`
1522
+ * is ghost's chrome rung — the ink one step brighter, plus aria-current — and
1523
+ * stays a variant (the ticket read it as ghost; the CSS says oq-80 against
1524
+ * ghost's oq-48, so folding it would have dimmed every close button and rail
1525
+ * row in the estate).
1526
+ *
1527
+ * THE FLOATING SURFACE PAINTS THE GROUND (ask 2). `--kol-tone-panel-bg` is
1528
+ * what a dropdown panel of that tone paints: primary/grey/sunken continue
1529
+ * their fill; outline and ghost paint `--kol-tone-ground` — the page's own
1530
+ * background, `surface-primary` unless the page says otherwise — so opening
1531
+ * a dropdown changes nothing under it. A page on another ground sets
1532
+ * `--kol-tone-ground` once at its root. (The panel is PORTALLED, so Dropdown
1533
+ * copies the trigger's resolved properties onto it on open — see Dropdown.jsx.)
1534
+ *
1535
+ * A bundle sets EVERY property, including the ones it leaves at default, so a
1536
+ * control with its own tone inside a differently-toned wrapper inherits
1537
+ * nothing from it — not even a hover gradient. (Weight is the variant's own,
1538
+ * never the tone's: a ground does not re-weigh a label.)
1539
+ *
1540
+ * `default` is not a tone: it is the ABSENCE of one — inherit the wrapper's,
1541
+ * else the family's fallback. (An alias of `primary` set on the element would
1542
+ * block the inheritance that ask 3 exists for.) `inverse` stays sunken's alias.
1543
+ *
1544
+ * The rules live HERE, after every default they out-rank. Sunken's own
1545
+ * history — the well, its three-rung ladder, why it was raised until
1546
+ * 2026-08-30 — is the comment above; nothing about its values changed.
1547
+ * ═══════════════════════════════════════════════════════════════════════ */
1548
+ .kol-tone-primary,
1549
+ :where(.kol-btn-primary, .kol-control--filled, .kol-icon-frame-primary, .kol-dd-panel--primary) {
1550
+ --kol-tone-bg: var(--kol-surface-secondary);
1551
+ --kol-tone-image: none;
1552
+ --kol-tone-fg: var(--kol-surface-on-primary);
1553
+ --kol-tone-border: transparent;
1554
+ --kol-tone-hover-bg: var(--kol-oq-08);
1555
+ --kol-tone-hover-image: none;
1556
+ --kol-tone-hover-fg: var(--kol-surface-on-primary);
1557
+ --kol-tone-hover-border: transparent;
1558
+ --kol-tone-active-bg: var(--kol-oq-16);
1559
+ --kol-tone-active-image: none;
1560
+ --kol-tone-pressed-bg: var(--kol-surface-on-primary);
1561
+ --kol-tone-pressed-image: none;
1562
+ --kol-tone-pressed-fg: var(--kol-surface-primary);
1563
+ --kol-tone-panel-bg: var(--kol-surface-secondary);
1564
+ --kol-tone-panel-border-w: 0;
1565
+ --kol-tone-panel-border: transparent;
1566
+ }
1567
+ .kol-tone-secondary,
1568
+ :where(.kol-btn-secondary, .kol-icon-frame-secondary, .kol-dd-panel--secondary) {
1569
+ --kol-tone-bg: var(--kol-surface-on-primary);
1570
+ --kol-tone-image: none;
1571
+ --kol-tone-fg: var(--kol-surface-primary);
1572
+ --kol-tone-border: transparent;
1573
+ --kol-tone-hover-bg: var(--kol-oq-inverse-40);
1574
+ --kol-tone-hover-image: none;
1575
+ --kol-tone-hover-fg: var(--kol-surface-primary);
1576
+ --kol-tone-hover-border: transparent;
1577
+ --kol-tone-active-bg: var(--kol-oq-inverse-48);
1578
+ --kol-tone-active-image: none;
1579
+ --kol-tone-pressed-bg: var(--kol-surface-on-primary);
1580
+ --kol-tone-pressed-image: none;
1581
+ --kol-tone-pressed-fg: var(--kol-surface-primary);
1582
+ --kol-tone-panel-bg: var(--kol-surface-on-primary);
1583
+ --kol-tone-panel-border-w: 0;
1584
+ --kol-tone-panel-border: transparent;
1585
+ }
1586
+ .kol-tone-outline,
1587
+ :where(.kol-btn-outline, .kol-control--outline, .kol-icon-frame-outline, .kol-dd-panel--outline) {
1588
+ --kol-tone-bg: transparent;
1589
+ --kol-tone-image: none;
1590
+ --kol-tone-fg: var(--kol-surface-on-primary);
1591
+ --kol-tone-border: var(--kol-oq-08);
1592
+ --kol-tone-hover-bg: var(--kol-oq-02);
1593
+ --kol-tone-hover-image: none;
1594
+ --kol-tone-hover-fg: var(--kol-surface-on-primary);
1595
+ --kol-tone-hover-border: color-mix(in srgb, var(--kol-surface-on-primary) 25%, var(--kol-surface-primary));
1596
+ --kol-tone-active-bg: var(--kol-oq-08);
1597
+ --kol-tone-active-image: none;
1598
+ --kol-tone-pressed-bg: var(--kol-surface-on-primary);
1599
+ --kol-tone-pressed-image: none;
1600
+ --kol-tone-pressed-fg: var(--kol-surface-primary);
1601
+ --kol-tone-panel-bg: var(--kol-tone-ground, var(--kol-surface-primary));
1602
+ --kol-tone-panel-border-w: 1px;
1603
+ --kol-tone-panel-border: var(--kol-oq-08);
1604
+ }
1605
+ .kol-tone-ghost,
1606
+ :where(.kol-btn-ghost, .kol-icon-frame-ghost, .kol-dd-panel--ghost) {
1607
+ --kol-tone-bg: transparent;
1608
+ --kol-tone-image: none;
1609
+ --kol-tone-fg: var(--kol-oq-48);
1610
+ --kol-tone-border: transparent;
1611
+ --kol-tone-hover-bg: var(--kol-oq-04);
1612
+ --kol-tone-hover-image: none;
1613
+ --kol-tone-hover-fg: var(--kol-surface-on-primary);
1614
+ --kol-tone-hover-border: transparent;
1615
+ --kol-tone-active-bg: var(--kol-oq-08);
1616
+ --kol-tone-active-image: none;
1617
+ --kol-tone-pressed-bg: var(--kol-surface-on-primary);
1618
+ --kol-tone-pressed-image: none;
1619
+ --kol-tone-pressed-fg: var(--kol-surface-primary);
1620
+ --kol-tone-panel-bg: var(--kol-tone-ground, var(--kol-surface-primary));
1621
+ --kol-tone-panel-border-w: 0;
1622
+ --kol-tone-panel-border: transparent;
1623
+ }
1624
+ .kol-tone-grey,
1625
+ :where(.kol-btn-grey, .kol-dd-trigger--grey, .kol-dd-panel--grey, .kol-icon-frame-grey) {
1626
+ --kol-tone-bg: var(--kol-oq-12);
1627
+ --kol-tone-image: none;
1628
+ --kol-tone-fg: var(--kol-surface-on-primary);
1629
+ --kol-tone-border: transparent;
1630
+ --kol-tone-hover-bg: var(--kol-oq-16);
1631
+ --kol-tone-hover-image: none;
1632
+ --kol-tone-hover-fg: var(--kol-surface-on-primary);
1633
+ --kol-tone-hover-border: transparent;
1634
+ --kol-tone-active-bg: var(--kol-oq-24);
1635
+ --kol-tone-active-image: none;
1636
+ --kol-tone-pressed-bg: var(--kol-surface-on-primary);
1637
+ --kol-tone-pressed-image: none;
1638
+ --kol-tone-pressed-fg: var(--kol-surface-primary);
1639
+ --kol-tone-panel-bg: var(--kol-oq-12);
1640
+ --kol-tone-panel-border-w: 0;
1641
+ --kol-tone-panel-border: transparent;
1642
+ }
1643
+ .kol-tone-sunken,
1644
+ .kol-tone-inverse {
1645
+ --kol-tone-bg: var(--kol-surface-sunken);
1646
+ --kol-tone-image: none;
1647
+ --kol-tone-fg: var(--kol-fg-96);
1648
+ --kol-tone-border: transparent;
1649
+ --kol-tone-hover-bg: var(--kol-surface-sunken);
1650
+ --kol-tone-hover-image: linear-gradient(var(--kol-fg-04), var(--kol-fg-04));
1651
+ --kol-tone-hover-fg: var(--kol-fg-96);
1652
+ --kol-tone-hover-border: transparent;
1653
+ --kol-tone-active-bg: var(--kol-surface-sunken);
1654
+ --kol-tone-active-image: linear-gradient(var(--kol-fg-08), var(--kol-fg-08));
1655
+ --kol-tone-pressed-bg: var(--kol-surface-sunken);
1656
+ --kol-tone-pressed-image: linear-gradient(var(--kol-fg-08), var(--kol-fg-08));
1657
+ --kol-tone-pressed-fg: var(--kol-fg-96);
1658
+ --kol-tone-panel-bg: var(--kol-surface-sunken);
1659
+ --kol-tone-panel-border-w: 0;
1660
+ --kol-tone-panel-border: transparent;
1661
+ }
1662
+ :where(.kol-btn-nav) {
1663
+ --kol-tone-bg: transparent;
1664
+ --kol-tone-image: none;
1665
+ --kol-tone-fg: var(--kol-oq-80);
1666
+ --kol-tone-border: transparent;
1667
+ --kol-tone-hover-bg: var(--kol-oq-04);
1668
+ --kol-tone-hover-image: none;
1669
+ --kol-tone-hover-fg: var(--kol-surface-on-primary);
1670
+ --kol-tone-hover-border: transparent;
1671
+ --kol-tone-active-bg: var(--kol-oq-04);
1672
+ --kol-tone-active-image: none;
1673
+ --kol-tone-pressed-bg: var(--kol-surface-on-primary);
1674
+ --kol-tone-pressed-image: none;
1675
+ --kol-tone-pressed-fg: var(--kol-surface-primary);
1676
+ --kol-tone-panel-bg: var(--kol-tone-ground, var(--kol-surface-primary));
1677
+ --kol-tone-panel-border-w: 0;
1678
+ --kol-tone-panel-border: transparent;
1679
+ }
1680
+ :where(.kol-icon-frame-nav) {
1681
+ --kol-tone-bg: transparent;
1682
+ --kol-tone-image: none;
1683
+ --kol-tone-fg: var(--kol-surface-on-primary);
1684
+ --kol-tone-border: transparent;
1685
+ --kol-tone-hover-bg: transparent;
1686
+ --kol-tone-hover-image: none;
1687
+ --kol-tone-hover-fg: var(--kol-surface-on-primary);
1688
+ --kol-tone-hover-border: transparent;
1689
+ --kol-tone-active-bg: transparent;
1690
+ --kol-tone-active-image: none;
1691
+ --kol-tone-pressed-bg: var(--kol-surface-on-primary);
1692
+ --kol-tone-pressed-image: none;
1693
+ --kol-tone-pressed-fg: var(--kol-surface-primary);
1694
+ --kol-tone-panel-bg: var(--kol-tone-ground, var(--kol-surface-primary));
1695
+ --kol-tone-panel-border-w: 0;
1696
+ --kol-tone-panel-border: transparent;
1697
+ }
1698
+ :where(.kol-control--plain) {
1699
+ --kol-tone-bg: transparent;
1700
+ --kol-tone-image: none;
1701
+ --kol-tone-fg: var(--kol-fg-meta);
1702
+ --kol-tone-border: transparent;
1703
+ --kol-tone-hover-bg: transparent;
1704
+ --kol-tone-hover-image: none;
1705
+ --kol-tone-hover-fg: var(--kol-fg-meta);
1706
+ --kol-tone-hover-border: transparent;
1707
+ --kol-tone-active-bg: transparent;
1708
+ --kol-tone-active-image: none;
1709
+ --kol-tone-pressed-bg: var(--kol-surface-on-primary);
1710
+ --kol-tone-pressed-image: none;
1711
+ --kol-tone-pressed-fg: var(--kol-surface-primary);
1712
+ --kol-tone-panel-bg: var(--kol-tone-ground, var(--kol-surface-primary));
1713
+ --kol-tone-panel-border-w: 0;
1714
+ --kol-tone-panel-border: transparent;
1715
+ }
1716
+ :where(.kol-btn-danger, .kol-icon-frame-danger) {
1717
+ --kol-tone-bg: var(--ui-error);
1718
+ --kol-tone-image: none;
1719
+ --kol-tone-fg: var(--kol-color-ab-white);
1720
+ --kol-tone-border: transparent;
1721
+ --kol-tone-hover-bg: color-mix(in srgb, var(--ui-error) 80%, var(--kol-surface-primary));
1722
+ --kol-tone-hover-image: none;
1723
+ --kol-tone-hover-fg: var(--kol-color-ab-white);
1724
+ --kol-tone-hover-border: transparent;
1725
+ --kol-tone-active-bg: color-mix(in srgb, var(--ui-error) 70%, var(--kol-surface-primary));
1726
+ --kol-tone-active-image: none;
1727
+ --kol-tone-pressed-bg: var(--kol-surface-on-primary);
1728
+ --kol-tone-pressed-image: none;
1729
+ --kol-tone-pressed-fg: var(--kol-surface-primary);
1730
+ --kol-tone-panel-bg: var(--ui-error);
1731
+ --kol-tone-panel-border-w: 0;
1732
+ --kol-tone-panel-border: transparent;
1733
+ }
1734
+ :where(.kol-btn-accent, .kol-icon-frame-accent) {
1735
+ --kol-tone-bg: var(--kol-accent-primary);
1736
+ --kol-tone-image: none;
1737
+ --kol-tone-fg: var(--kol-accent-on-primary);
1738
+ --kol-tone-border: transparent;
1739
+ --kol-tone-hover-bg: var(--kol-accent-primary-strong);
1740
+ --kol-tone-hover-image: none;
1741
+ --kol-tone-hover-fg: var(--kol-accent-on-primary);
1742
+ --kol-tone-hover-border: transparent;
1743
+ --kol-tone-active-bg: color-mix(in srgb, var(--kol-accent-primary) 70%, var(--kol-surface-primary));
1744
+ --kol-tone-active-image: none;
1745
+ --kol-tone-pressed-bg: var(--kol-surface-on-primary);
1746
+ --kol-tone-pressed-image: none;
1747
+ --kol-tone-pressed-fg: var(--kol-surface-primary);
1748
+ --kol-tone-panel-bg: var(--kol-accent-primary);
1749
+ --kol-tone-panel-border-w: 0;
1750
+ --kol-tone-panel-border: transparent;
1751
+ }
1752
+
1753
+ /* ─── the families that keep their own plates ─── */
1754
+ .kol-view-toggle { background-color: var(--kol-tone-bg, var(--kol-surface-secondary)); }
1755
+ /* the sunken well's chip ladder (rest · +fg-04 hover · +fg-08 selected) — on the
1756
+ * element, or inherited from a sunken wrapper */
1757
+ :is(.kol-view-toggle.kol-tone-sunken, .kol-view-toggle.kol-tone-inverse, .kol-tone-sunken .kol-view-toggle) > button[aria-pressed="true"] { background-color: var(--kol-fg-08); }
1758
+ :is(.kol-view-toggle.kol-tone-sunken, .kol-view-toggle.kol-tone-inverse, .kol-tone-sunken .kol-view-toggle) > button:not([aria-pressed="true"]):hover { background-color: var(--kol-fg-04); }
1759
+ /* the open search shell — was `bg-fg-04` on the element, sunken by class */
1760
+ .kol-expand--open { background-color: var(--kol-tone-bg, var(--kol-fg-04)); }
1761
+ /* ⛔ THE TRIGGER IS EXCLUDED — rest and open are its only states (law, twice
1762
+ * at the top of this file). Pressed on a sunken button out-ranks its hover, as
1763
+ * it always did: this is the one rule the generic `.kol-btn-pressed` cannot
1764
+ * express, because hover carries three `:not()`s of specificity. */
1547
1765
  .kol-btn.kol-tone-sunken.kol-btn-pressed:not(.kol-dd-trigger),
1548
- .kol-btn.kol-tone-sunken:not(:disabled):not(.kol-dd-trigger):active {
1549
- background-color: var(--kol-surface-sunken);
1550
- background-image: linear-gradient(var(--kol-fg-08), var(--kol-fg-08));
1551
- color: var(--kol-fg-96);
1766
+ .kol-tone-sunken .kol-btn.kol-btn-pressed:not(.kol-dd-trigger) {
1767
+ background-color: var(--kol-tone-pressed-bg);
1768
+ background-image: var(--kol-tone-pressed-image);
1769
+ color: var(--kol-tone-pressed-fg);
1552
1770
  }
1771
+
@@ -173,3 +173,40 @@
173
173
  --kol-shadow-inner: inset 0 2px 4px rgba(255, 255, 255, 0.04);
174
174
  }
175
175
  }
176
+
177
+ /* =============================================================================
178
+ * TAILWIND CONTRACT — the widths and the z ladder as CLASSES
179
+ *
180
+ * THE LAW REQUIRED A CLASS THAT DID NOT EXIST (kol-client-olina 2026-09-03, off
181
+ * the brand app's full-consumption pass). Two of the six consumption checks say
182
+ * reach these tokens by class — check 2 flags `var(--kol-*)` in JSX, check 5
183
+ * flags a raw `z-[…]` — and the theme registered neither namespace, so the only
184
+ * way to use a content width or a z rung from JSX was
185
+ * `max-w-[var(--kol-content-measure)]` / `z-[var(--kol-z-modal)]`, which trip
186
+ * those very checks. Every consumer was hand-binding the same nine lines in its
187
+ * own `@theme` to get out of it; the brand app had done exactly that.
188
+ *
189
+ * Worse than verbose: SILENT. A `z-modal` written against an unregistered
190
+ * namespace emits nothing at all, which is how that app's skip link shipped
191
+ * with no fill and no stacking for a month (layout-skip-link, same pass).
192
+ *
193
+ * Tailwind v4 namespaces: `--container-*` generates `max-w-*` / `w-*` /
194
+ * `min-w-*`; `--z-index-*` generates `z-*`. The `--kol-*` tokens stay the source
195
+ * — these alias them, so there is one value and two ways to spell it, not two
196
+ * values. ============================================================== */
197
+ @theme {
198
+ --container-canvas: var(--kol-content-canvas);
199
+ --container-shell: var(--kol-content-shell);
200
+ --container-panel: var(--kol-content-panel);
201
+ --container-column: var(--kol-content-column);
202
+ --container-measure: var(--kol-content-measure);
203
+
204
+ --z-index-base: var(--kol-z-base);
205
+ --z-index-dropdown: var(--kol-z-dropdown);
206
+ --z-index-sticky: var(--kol-z-sticky);
207
+ --z-index-overlay: var(--kol-z-overlay);
208
+ --z-index-modal: var(--kol-z-modal);
209
+ --z-index-toast: var(--kol-z-toast);
210
+ --z-index-tooltip: var(--kol-z-tooltip);
211
+ --z-index-nav: var(--kol-z-nav);
212
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kolkrabbi/kol-theme",
3
- "version": "0.132.1",
3
+ "version": "0.134.0",
4
4
  "description": "KOL (Kolkrabbi) design-system tokens + base CSS — brand-neutral. The canonical token/cascade layer every other KOL package and consumer builds on.",
5
5
  "license": "MIT",
6
6
  "type": "module",