@kolkrabbi/kol-theme 0.19.0 → 0.30.2

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.
@@ -888,6 +888,26 @@ a:hover .icon-hover,
888
888
  padding: 0;
889
889
  }
890
890
 
891
+ /* An ACTIONABLE frame — `onClick`/`href` (user ruling 2026-08-01: *"you dont
892
+ * use a button… you use the ICON COMPONENT… it has no interactive states"*).
893
+ *
894
+ * The "no states" property used to be an accident of the element: a <span>
895
+ * cannot be hovered-as-a-control, so the frame's own docstring warned that
896
+ * reusing kol-btn-* on one suppressed states BY TAG rather than by contract.
897
+ * Handing the frame a click makes it a <button>/<a>, which brings the user
898
+ * agent's own chrome back with it — so the reset is declared here and the
899
+ * absence of state rules stays a PROPERTY OF THE CLASS, which is what the
900
+ * original ruling actually asked for. There is deliberately no :hover, no
901
+ * :active and no :focus fill below: a frame that lights up is a Button. */
902
+ button.kol-icon-frame,
903
+ a.kol-icon-frame {
904
+ appearance: none;
905
+ -webkit-appearance: none;
906
+ font: inherit;
907
+ cursor: pointer;
908
+ text-decoration: none;
909
+ }
910
+
891
911
  /* Radius (2026-07-30 ruling): either the system's 4px or a full round, nothing
892
912
  * between. `sm` is the default and carries no class — it's on .kol-icon-frame
893
913
  * above. `full` is a deliberate exception to the hard 4px repo invariant,
@@ -97,6 +97,14 @@
97
97
  border: 1px solid var(--kol-border-default);
98
98
  }
99
99
 
100
+ /* Pill takes the TRACKING but NOT the transform (user rulings 2026-08-01).
101
+ * Pill has no single base class, so its size classes carry it. The uppercase
102
+ * exception is Tag's alone: a Pill's label IS authored at the call site, so the
103
+ * no-`text-transform` law applies to it unchanged. */
104
+ .pill-sm,
105
+ .pill-md,
106
+ .pill-lg { letter-spacing: 0.04em; }
107
+
100
108
  /* PADDING ONLY — type is kol-helper-*, applied in Pill.jsx. Same ruling as
101
109
  * .kol-tag--* above (2026-08-01). */
102
110
  .pill-sm { padding: 2px 10px; }
@@ -126,6 +134,22 @@
126
134
  border-radius: var(--kol-radius-full);
127
135
  border: 1px solid transparent;
128
136
  transition: background-color 0.15s ease, color 0.15s ease, border-color 0.15s ease;
137
+ /* THE CHIP'S OWN TYPE (user rulings 2026-08-01, in two passes).
138
+ *
139
+ * First pass: the chip was moved onto the `kol-helper-*` ramp for its
140
+ * `line-height: 1` and inherited `letter-spacing: 0.10em` with it — the
141
+ * ramp's eyebrow tracking, and far too wide on a tag string. Set to `normal`.
142
+ *
143
+ * Second pass: `normal` is zero, which is too tight for caps. The chip is
144
+ * uppercase at **0.04em** — between the two, and the value the user named.
145
+ *
146
+ * UPPERCASE IS AN EXPLICIT EXCEPTION to the no-`text-transform` law, and the
147
+ * only one in the component tier. That law says casing is authored at the
148
+ * CALL SITE — which works because a call site exists. A tag has none: the
149
+ * string is `#domain/design-system`, generated from frontmatter data, so
150
+ * there is nowhere to author it. The transform lives here or nowhere. */
151
+ text-transform: uppercase;
152
+ letter-spacing: 0.04em;
129
153
  }
130
154
 
131
155
  /* PADDING ONLY. Type is the kol-helper-* ramp, applied at the call site
@@ -413,6 +437,12 @@
413
437
  margin-block: 1.5rem;
414
438
  }
415
439
 
440
+ /* THE BLOCK. Everything here is the SURFACE — fill, frame, font, wrapping.
441
+ * The two dimensional values (pad and type size) moved OUT to the size classes
442
+ * below (user ruling 2026-08-01: *"it has sizes, ok but its not set, so its
443
+ * just whatever its defaulting to? is that good? instead of saying sm or md?"*).
444
+ * They were unnamed constants sitting in this rule, so nothing could ask for a
445
+ * smaller block and nothing recorded that a choice had been made. */
416
446
  .kol-codeblock {
417
447
  /* verbatim from the reference build's rule (user DevTools, 2026-07-28) */
418
448
  background: var(--kol-surface-tertiary);
@@ -420,14 +450,28 @@
420
450
  font-family: var(--kol-font-family-mono);
421
451
  white-space: pre-wrap;
422
452
  overflow-wrap: break-word;
423
- border-radius: 4px;
424
- padding: 1rem;
425
- font-size: 14px;
453
+ border-radius: var(--kol-radius-sm);
426
454
  line-height: 1.6;
427
455
  position: relative;
428
456
  overflow-x: auto;
429
457
  }
430
458
 
459
+ /* THE SIZE AXIS — declared, not defaulted. `md` carries exactly the values the
460
+ * base rule used to hide, so nothing changes appearance until a call site asks
461
+ * for `sm`. Padding is the box, type is the ramp; both step together, because a
462
+ * tight box around 14px type is a different defect from small type in a roomy
463
+ * box. Size is INDEPENDENT of `bare`: bare removes the FRAME, size sets the
464
+ * BOX. A bare block still has a size. */
465
+ .kol-codeblock--md {
466
+ padding: var(--kol-spacing-4);
467
+ font-size: var(--kol-text-body-02);
468
+ }
469
+
470
+ .kol-codeblock--sm {
471
+ padding: var(--kol-spacing-3);
472
+ font-size: var(--kol-text-body-03);
473
+ }
474
+
431
475
  /* the ONE chip — filename if present, else the language. In-flow above the
432
476
  * code. VERBATIM from the live reference build (index-Bro9Jnn6.css). */
433
477
  /* bare — the host owns the frame (PreviewCard Code tab); the border/radius go,
@@ -48,17 +48,9 @@
48
48
  * audit finding 4); oq-* mirrors the same visual weight without alpha */
49
49
  border: 1px solid var(--kol-oq-08);
50
50
  border-radius: 4px;
51
- /* Scroll affordance: with the scrollbar hidden, a clipped table reads as
52
- * cut-off data on narrow viewports. Edge shadows appear only on the
53
- * scrollable side (surface-colored covers ride the content and mask them
54
- * at the extremes). */
55
- /* scroll edges: surface-colored FADE only — no shadow band (user ruling
56
- * 2026-07-28: the content dissolves into the background, no 3d edge) */
57
- background:
58
- linear-gradient(to right, var(--kol-surface-primary) 40%, transparent) left / 32px 100%,
59
- linear-gradient(to left, var(--kol-surface-primary) 40%, transparent) right / 32px 100%;
60
- background-repeat: no-repeat;
61
- background-attachment: local, local;
51
+ /* no scroll-edge paint the 2026-07-28 ruling bans shadow bands, and the
52
+ * surface-primary "cover" gradients that outlived it rendered as exactly
53
+ * that band on any non-primary surface (brand app dark, 2026-08-09) */
62
54
  }
63
55
 
64
56
  .kol-table-wrapper::-webkit-scrollbar { display: none; }
@@ -178,7 +170,11 @@
178
170
  /* No outer margin: spacing is the CONTAINER's job. The margin here
179
171
  * double-spaced against every flex row's own gap, which is the ragged
180
172
  * rhythm the metadata rows showed. */
181
- border-radius: 2px;
173
+ /* Radius is the shared chip radius, not a literal (2026-08-01). This one
174
+ * value was the last thing making a table token and a prose inline code chip
175
+ * read as two different objects — see the inline-code block in
176
+ * kol-type-roles.css, which owns the shared fill/radius/colour answer. */
177
+ border-radius: var(--kol-radius-sm);
182
178
  font-family: var(--kol-font-family-mono);
183
179
  font-size: 0.75rem;
184
180
  font-weight: 400;
@@ -215,11 +215,11 @@
215
215
  * SECTION the right rail's rows (same PAGE row idiom)
216
216
  *
217
217
  * CHAPTER and PAGE were indistinguishable: both `kol-mono-14`, and the only
218
- * difference was `.text-body` — which is a COLOUR utility, not type. A parent
218
+ * difference was `.text-default` — which is a COLOUR utility, not type. A parent
219
219
  * and its children reading identically is not a hierarchy.
220
220
  *
221
- * The weight is the difference, and it stays INSIDE the one ramp: 500 here
222
- * against kol-mono-14's own 400 on the page row. Not a second type class —
221
+ * The weight is the difference, and it stays INSIDE the one ramp: 700 here
222
+ * against the page row's 100. Not a second type class —
223
223
  * R1 exists to stop a second RAMP in the rails, and a weight within one ramp
224
224
  * is not one.
225
225
  *
@@ -228,7 +228,14 @@
228
228
  * borrow this class — that is how the right rail drifted; use
229
229
  * .shell-sidebar-toggle. */
230
230
  .shell-nav-group-header {
231
+ /* MEDIUM + SHOUT (user ruling 2026-08-01, the settled pair). 700 was tried
232
+ * for one turn and reverted: with the page rows at Thin 100 the weight gap
233
+ * already carries, so the CHAPTER buys its prominence from INK instead —
234
+ * `shout` (88) against the rows' `fg-64`. Weight and colour each doing half
235
+ * the work beats either doing all of it, and 500 keeps the chapter inside
236
+ * the ramp's normal range. */
231
237
  font-weight: 500;
238
+ color: var(--kol-fg-shout);
232
239
  padding: var(--kol-pad-rail-row-y) 0;
233
240
  cursor: pointer;
234
241
  display: flex;
@@ -241,6 +248,52 @@
241
248
  color: color-mix(in srgb, var(--kol-surface-on-primary) 90%, transparent);
242
249
  }
243
250
 
251
+ /* THE RAIL STACK — the SECTION container, owned here for exactly the reason
252
+ * `.shell-nav-items` below owns the ROW gap (user ruling 2026-08-01, said in
253
+ * anger and said before): *"I HAVE TOLD YOU LEFT AND RIGHT = THE SAME LAYOUT
254
+ * MARGINPADDING"*.
255
+ *
256
+ * The row gap was fixed on 2026-08-01 and the stack above it was not, so one
257
+ * layout shipped as FOUR idioms:
258
+ *
259
+ * left rail `flex flex-col gap-6` ShellChrome.jsx
260
+ * right rail `space-y-6` DocumentationReader.jsx
261
+ * sidebars `space-y-4` ShellSidebar / WorkshopSidebar
262
+ * rows `space-y-0` (the one already fixed)
263
+ *
264
+ * `gap` and `space-y-*` are not the same mechanism either — `space-y` is a
265
+ * margin on every child but the first, so it fights any child that sets its own
266
+ * margin, and the eyebrow box DOES (see the box law above). Flex gap does not.
267
+ *
268
+ * Two rungs, two gaps, one definition each. A rail stack is never a utility. */
269
+ .shell-rail-stack {
270
+ display: flex;
271
+ flex-direction: column;
272
+ gap: var(--kol-spacing-6);
273
+ }
274
+
275
+ /* Between GROUPS inside one category — tighter than between categories. The
276
+ * left rail said 16 and the right said 24 for the same relationship. */
277
+ .shell-rail-stack-inner {
278
+ display: flex;
279
+ flex-direction: column;
280
+ gap: var(--kol-spacing-4);
281
+ }
282
+
283
+ /* THE TAG SHELF — chips indented to the ROW text edge (user ruling 2026-08-01:
284
+ * "indent the tags like it was a text"). They were flush to the rail's left
285
+ * edge while every row beside them started one indent in, so the one block that
286
+ * is not a row was the only thing setting the rail's left margin. The inset is
287
+ * `.shell-nav-item`'s own left padding, read from the same declaration rather
288
+ * than typed again. */
289
+ .shell-rail-tags {
290
+ display: flex;
291
+ flex-wrap: wrap;
292
+ gap: var(--kol-spacing-2);
293
+ padding-left: var(--kol-pad-rail-row-x);
294
+ padding-top: var(--kol-spacing-2);
295
+ }
296
+
244
297
  /* THE ROW CONTAINER — owns the gap so the two rails cannot disagree. They
245
298
  * did: `space-y-0` on one side, `space-y-4` on the other, for the same list. */
246
299
  .shell-nav-items {
@@ -258,15 +311,34 @@
258
311
  *
259
312
  * `.shell-nav-item` was the shared name and nothing else: every call site
260
313
  * hand-wrote its own utility stack around it, so ONE row idiom shipped as NINE
261
- * strings across five files — `block kol-mono-14 text-body transition-colors
314
+ * strings across five files — `block kol-mono-14 text-default transition-colors
262
315
  * focus-visible:ring-focus hover:text-emphasis` in one rail, bare
263
316
  * `kol-mono-14` in the other, `flex items-center gap-2 …` for actions. R1/R2
264
317
  * locked the TYPE class and nothing locked the stack: the eyebrow-box failure,
265
318
  * one rung down.
266
319
  *
267
320
  * Layout, colour, hover, transition and focus live HERE now. Markup comes from
268
- * `RailRow`; `validate:rails` R4 fails a hand-written `shell-nav-item`. */
321
+ * `RailRow`; `validate:rails` R4 fails a hand-written `shell-nav-item`.
322
+ *
323
+ * WEIGHT IS THIN (user ruling 2026-08-01, asked five times).
324
+ *
325
+ * The arithmetic, measured on the H stem at this size:
326
+ *
327
+ * 400 vs 500 1.26 → 1.39 the original pair. Invisible.
328
+ * 300 vs 500 1.11 → 1.39 +25%. Also invisible — applied, correct in the
329
+ * cascade, and unseeable.
330
+ * 200 vs 500 0.92 → 1.39 +51%. Still not enough for him.
331
+ * 100 vs 700 0.70 → 1.75 +150%. THE PAIR. Both ends of the usable axis.
332
+ *
333
+ * I argued against 100 on the grounds that it falls under one device pixel and
334
+ * antialiases to a wash. He ruled it anyway, and against 700 rather than 500
335
+ * the contrast carries the hierarchy the stroke alone could not.
336
+ *
337
+ * Renderable only because the family went VARIABLE the same day
338
+ * (kol-typography-mono.css, `wght 100 800`). Under the old static set the
339
+ * lightest cut was 300 and this line would have rounded back to 400. */
269
340
  .shell-nav-item {
341
+ font-weight: 100;
270
342
  display: flex;
271
343
  align-items: center;
272
344
  gap: 8px;
@@ -280,6 +352,11 @@
280
352
  color: var(--kol-fg-64);
281
353
  cursor: pointer;
282
354
  transition: color 0.15s ease, background-color 0.15s ease;
355
+ /* THE TEXT INDENT, named (2026-08-01). It was the last value of the padding
356
+ * shorthand above and nothing else could read it, so the tag shelf beside
357
+ * these rows had to guess and guessed flush-left. Declared after the
358
+ * shorthand deliberately — it overrides that slot and nothing else. */
359
+ padding-left: var(--kol-pad-rail-row-x);
283
360
  }
284
361
 
285
362
  .shell-nav-item:hover {
@@ -291,9 +368,36 @@
291
368
  outline-offset: -1px;
292
369
  }
293
370
 
294
- /* The active rowone definition, not `text-emphasis` typed at each site. */
371
+ /* THE ACTIVE ROW`emphasis`, the role, not a raw token (user ruling
372
+ * 2026-08-01). One definition for both rails: the left tree's current PAGE and
373
+ * the right rail's current SECTION are the same state, and where-you-are is the
374
+ * one thing in a rail that earns max ink. It read `var(--kol-surface-on-primary)`
375
+ * — the same value, reached without going through the ladder, which is how a
376
+ * role set ends up with call sites that do not know it exists. */
295
377
  .shell-nav-item.is-active {
296
- color: var(--kol-surface-on-primary);
378
+ color: var(--kol-fg-emphasis);
379
+ }
380
+
381
+ /* THIS PAGE'S OWN ROW — brighter than the rest of the list (user ruling
382
+ * 2026-08-01, twice). The first attempt put `text-emphasis` on the row from the
383
+ * JSX and nothing happened: `.text-emphasis` and `.shell-nav-item` are both
384
+ * single-class selectors in the same cascade layer, and this file loads AFTER
385
+ * kol-opacity.css, so the rail's resting `--kol-fg-64` won every time. A colour
386
+ * utility cannot brighten a row whose own rule sets colour — the modifier has
387
+ * to live beside that rule. Same file, same specificity, declared later. */
388
+ .shell-nav-item--own {
389
+ color: var(--kol-fg-emphasis);
390
+ font-weight: 500;
391
+ }
392
+
393
+ /* …AND THE REST STEP DOWN (user ruling 2026-08-01: *"no needs more"*). Raising
394
+ * the page's own tags to full ink was not enough on its own, because the rows
395
+ * beneath them sat at the rail's resting `fg-64` — close enough that the two
396
+ * ranks read as one list with a slightly brighter top. Contrast is a GAP, not a
397
+ * value: the other rank drops to `meta`, so the distance is 100 against 48
398
+ * rather than 100 against 64, and the weight moves too. */
399
+ .shell-nav-item--muted {
400
+ color: var(--kol-fg-meta);
297
401
  }
298
402
 
299
403
  /* The trailing slot: counts, doc numbers, anything pushed right. */
@@ -401,7 +505,7 @@
401
505
  gap: 8px;
402
506
  }
403
507
 
404
- /* Doc meta row — layout only; type/color in JSX (kol-helper-10 text-body). */
508
+ /* Doc meta row — layout only; type/color in JSX (kol-helper-10 text-default). */
405
509
  .docs-meta {
406
510
  display: flex;
407
511
  flex-wrap: wrap;
@@ -449,7 +553,35 @@
449
553
  min-width: 120px;
450
554
  }
451
555
 
452
- .docs-frontmatter-row:has(.tag) {
556
+ /* THE VALUE COLUMN — it had no rule at all, so it was an auto-width flex item
557
+ * next to a fixed-width key: a long `description` or a `sources` path list had
558
+ * nothing telling it to shrink, and ran straight off the right edge of the
559
+ * panel (user screenshot 2026-08-01). `min-width: 0` is the half of the flex
560
+ * contract that was missing; `overflow-wrap: anywhere` handles the paths,
561
+ * which have no spaces to break at. */
562
+ .docs-frontmatter-value {
563
+ flex: 1;
564
+ min-width: 0;
565
+ overflow-wrap: anywhere;
566
+ }
567
+
568
+ /* A MULTI-VALUE FIELD IS A LIST WITH A CEILING (2026-08-01). Stacked one per
569
+ * line, and capped at roughly eight rows before it scrolls in place — a doc
570
+ * with fifteen `sources` would otherwise push its own article fifteen lines
571
+ * down and turn the metadata panel into the page. */
572
+ .docs-frontmatter-list {
573
+ display: flex;
574
+ flex-direction: column;
575
+ gap: var(--kol-spacing-1);
576
+ max-height: 11rem;
577
+ overflow-y: auto;
578
+ overscroll-behavior: contain;
579
+ }
580
+
581
+ /* `.tag` was the Tag component's base class until it was rebuilt on Pill's
582
+ * model (2026-08-01) and the scheme became `kol-tag`. The selector has matched
583
+ * nothing since. */
584
+ .docs-frontmatter-row:has(.kol-tag) {
453
585
  align-items: center;
454
586
  }
455
587
 
package/kol-opacity.css CHANGED
@@ -5,6 +5,8 @@
5
5
  * scattered across kol-color-simple.css and framework/app.css.
6
6
  *
7
7
  * Scale: 01, 02, 04, 08, 12, 16, 24, 32, 40, 48, 64, 80, 88, 96 (14 stops)
8
+ * + 72 in the STANDARD tier only (2026-08-01) — 15 stops there, 14
9
+ * everywhere else. The exception is stated at the token itself.
8
10
  *
9
11
  * Tier pairs:
10
12
  * - Standard (foreground on primary surface — var(--kol-surface-on-primary))
@@ -18,9 +20,9 @@
18
20
  * Every color-mix expression uses `var(<token>, currentColor)` — fallback is
19
21
  * load-bearing for SVG theming where the element's `color:` is the active paint.
20
22
  *
21
- * Semantic text role classes (.text-emphasis/lede/body/meta/subtle) live here
22
- * too they map directly to the fg-* primitives below, no intermediate alias
23
- * tokens. Single source of truth per opacity stop.
23
+ * Semantic text roles live here too — EIGHT of them since 2026-08-01
24
+ * (subtle · meta · body · lede · strong · shout · scream · emphasis), each an
25
+ * alias over one fg-* stop. `default` is a deprecated alias of `body`.
24
26
  */
25
27
 
26
28
  :root {
@@ -39,6 +41,15 @@
39
41
  --kol-fg-40: color-mix(in srgb, var(--kol-surface-on-primary, currentColor) 40%, transparent);
40
42
  --kol-fg-48: color-mix(in srgb, var(--kol-surface-on-primary, currentColor) 48%, transparent);
41
43
  --kol-fg-64: color-mix(in srgb, var(--kol-surface-on-primary, currentColor) 64%, transparent);
44
+ /* 72 — THE ONE OFF-RAMP STOP (user ruling 2026-08-01). The scale is a
45
+ * 14-stop signature and this makes it 15, deliberately and in ONE tier only:
46
+ * the eight TEXT roles below needed a step between body (64) and strong (80)
47
+ * for `lede`, and text chains exclusively through --kol-fg-*. The absolute
48
+ * and inverse tiers below, and the whole --kol-oq-* family, do NOT get a 72:
49
+ * they exist for fills and for theme-independent chrome, and neither has a
50
+ * role asking for this step. If a FILL ever needs 72, that is the signal to
51
+ * add it across all five families rather than widen this exception. */
52
+ --kol-fg-72: color-mix(in srgb, var(--kol-surface-on-primary, currentColor) 72%, transparent);
42
53
  --kol-fg-80: color-mix(in srgb, var(--kol-surface-on-primary, currentColor) 80%, transparent);
43
54
  --kol-fg-88: color-mix(in srgb, var(--kol-surface-on-primary, currentColor) 88%, transparent);
44
55
  --kol-fg-96: color-mix(in srgb, var(--kol-surface-on-primary, currentColor) 96%, transparent);
@@ -113,6 +124,7 @@
113
124
  .bg-fg-40 { background-color: color-mix(in srgb, var(--kol-surface-on-primary, currentColor) 40%, transparent); }
114
125
  .bg-fg-48 { background-color: color-mix(in srgb, var(--kol-surface-on-primary, currentColor) 48%, transparent); }
115
126
  .bg-fg-64 { background-color: color-mix(in srgb, var(--kol-surface-on-primary, currentColor) 64%, transparent); }
127
+ .bg-fg-72 { background-color: color-mix(in srgb, var(--kol-surface-on-primary, currentColor) 72%, transparent); }
116
128
  .bg-fg-80 { background-color: color-mix(in srgb, var(--kol-surface-on-primary, currentColor) 80%, transparent); }
117
129
  .bg-fg-88 { background-color: color-mix(in srgb, var(--kol-surface-on-primary, currentColor) 88%, transparent); }
118
130
  .bg-fg-96 { background-color: color-mix(in srgb, var(--kol-surface-on-primary, currentColor) 96%, transparent); }
@@ -170,6 +182,7 @@
170
182
  .hover\:bg-fg-40:hover { background-color: color-mix(in srgb, var(--kol-surface-on-primary, currentColor) 40%, transparent); }
171
183
  .hover\:bg-fg-48:hover { background-color: color-mix(in srgb, var(--kol-surface-on-primary, currentColor) 48%, transparent); }
172
184
  .hover\:bg-fg-64:hover { background-color: color-mix(in srgb, var(--kol-surface-on-primary, currentColor) 64%, transparent); }
185
+ .hover\:bg-fg-72:hover { background-color: color-mix(in srgb, var(--kol-surface-on-primary, currentColor) 72%, transparent); }
173
186
  .hover\:bg-fg-80:hover { background-color: color-mix(in srgb, var(--kol-surface-on-primary, currentColor) 80%, transparent); }
174
187
  .hover\:bg-fg-88:hover { background-color: color-mix(in srgb, var(--kol-surface-on-primary, currentColor) 88%, transparent); }
175
188
  .hover\:bg-fg-96:hover { background-color: color-mix(in srgb, var(--kol-surface-on-primary, currentColor) 96%, transparent); }
@@ -204,6 +217,7 @@
204
217
  .text-fg-40 { color: color-mix(in srgb, var(--kol-surface-on-primary, currentColor) 40%, transparent); }
205
218
  .text-fg-48 { color: color-mix(in srgb, var(--kol-surface-on-primary, currentColor) 48%, transparent); }
206
219
  .text-fg-64 { color: color-mix(in srgb, var(--kol-surface-on-primary, currentColor) 64%, transparent); }
220
+ .text-fg-72 { color: color-mix(in srgb, var(--kol-surface-on-primary, currentColor) 72%, transparent); }
207
221
  .text-fg-80 { color: color-mix(in srgb, var(--kol-surface-on-primary, currentColor) 80%, transparent); }
208
222
  .text-fg-88 { color: color-mix(in srgb, var(--kol-surface-on-primary, currentColor) 88%, transparent); }
209
223
  .text-fg-96 { color: color-mix(in srgb, var(--kol-surface-on-primary, currentColor) 96%, transparent); }
@@ -238,6 +252,7 @@
238
252
  .hover\:text-fg-40:hover { color: color-mix(in srgb, var(--kol-surface-on-primary, currentColor) 40%, transparent); }
239
253
  .hover\:text-fg-48:hover { color: color-mix(in srgb, var(--kol-surface-on-primary, currentColor) 48%, transparent); }
240
254
  .hover\:text-fg-64:hover { color: color-mix(in srgb, var(--kol-surface-on-primary, currentColor) 64%, transparent); }
255
+ .hover\:text-fg-72:hover { color: color-mix(in srgb, var(--kol-surface-on-primary, currentColor) 72%, transparent); }
241
256
  .hover\:text-fg-80:hover { color: color-mix(in srgb, var(--kol-surface-on-primary, currentColor) 80%, transparent); }
242
257
  .hover\:text-fg-88:hover { color: color-mix(in srgb, var(--kol-surface-on-primary, currentColor) 88%, transparent); }
243
258
  .hover\:text-fg-96:hover { color: color-mix(in srgb, var(--kol-surface-on-primary, currentColor) 96%, transparent); }
@@ -272,6 +287,7 @@
272
287
  .border-fg-40 { border-color: color-mix(in srgb, var(--kol-surface-on-primary, currentColor) 40%, transparent); }
273
288
  .border-fg-48 { border-color: color-mix(in srgb, var(--kol-surface-on-primary, currentColor) 48%, transparent); }
274
289
  .border-fg-64 { border-color: color-mix(in srgb, var(--kol-surface-on-primary, currentColor) 64%, transparent); }
290
+ .border-fg-72 { border-color: color-mix(in srgb, var(--kol-surface-on-primary, currentColor) 72%, transparent); }
275
291
  .border-fg-80 { border-color: color-mix(in srgb, var(--kol-surface-on-primary, currentColor) 80%, transparent); }
276
292
  .border-fg-88 { border-color: color-mix(in srgb, var(--kol-surface-on-primary, currentColor) 88%, transparent); }
277
293
  .border-fg-96 { border-color: color-mix(in srgb, var(--kol-surface-on-primary, currentColor) 96%, transparent); }
@@ -306,6 +322,7 @@
306
322
  .hover\:border-fg-40:hover { border-color: color-mix(in srgb, var(--kol-surface-on-primary, currentColor) 40%, transparent); }
307
323
  .hover\:border-fg-48:hover { border-color: color-mix(in srgb, var(--kol-surface-on-primary, currentColor) 48%, transparent); }
308
324
  .hover\:border-fg-64:hover { border-color: color-mix(in srgb, var(--kol-surface-on-primary, currentColor) 64%, transparent); }
325
+ .hover\:border-fg-72:hover { border-color: color-mix(in srgb, var(--kol-surface-on-primary, currentColor) 72%, transparent); }
309
326
  .hover\:border-fg-80:hover { border-color: color-mix(in srgb, var(--kol-surface-on-primary, currentColor) 80%, transparent); }
310
327
  .hover\:border-fg-88:hover { border-color: color-mix(in srgb, var(--kol-surface-on-primary, currentColor) 88%, transparent); }
311
328
  .hover\:border-fg-96:hover { border-color: color-mix(in srgb, var(--kol-surface-on-primary, currentColor) 96%, transparent); }
@@ -326,30 +343,51 @@
326
343
  .hover\:border-fg-inverse-96:hover { border-color: color-mix(in srgb, var(--kol-surface-on-inverse, currentColor) 96%, transparent); }
327
344
 
328
345
  /* ==============================================================================
329
- * SEMANTIC OPACITY DESCRIPTORS
346
+ * SEMANTIC OPACITY DESCRIPTORS — EIGHT ROLES (user ruling 2026-08-01)
330
347
  *
331
- * Six-stop hierarchy: subtle / mute / meta / body / strong / emphasis. Each
332
- * stop is a token (--kol-fg-*) and a class family (text-*, bg-*, border-*,
333
- * ring-*) so the same descriptor names work across every property.
348
+ * Each role is a token (--kol-fg-*) and a text class, so the same name works
349
+ * wherever ink is set.
334
350
  *
335
- * Stops:
336
351
  * subtle 24% — dividers, disabled hints, faintest visible
337
- * mute 32% — dim labels, quiet UI hints
338
- * meta 48% — labels, eyebrows, captions (current text-meta workhorse)
339
- * body 64% — running copy, links (current text-body default)
340
- * strong 80% — emphasized body, <strong> (renamed from text-lede)
341
- * emphasis 100% — headings, max ink (current text-emphasis)
352
+ * meta 48% — labels, captions
353
+ * body 64% — running copy, links (the resting ink)
354
+ * lede 72% — the step that did not exist; see --kol-fg-72 above
355
+ * strong 80% — emphasized body, <strong>, eyebrows
356
+ * shout 88% — chrome that has to cut through
357
+ * scream 96% — the loudest thing that is not full ink
358
+ * emphasis 100% — headings, max ink
342
359
  *
343
- * Tokens chain through the existing fg-opacity ramp single source of truth
344
- * (the 14-stop --kol-fg-{01..96} family) is preserved; descriptors are aliases.
360
+ * WHAT CHANGED, AND WHAT DELIBERATELY DID NOT. `default` became `body`
361
+ * the name 25 files spelled as the resting ink while four others already wrote
362
+ * `text-body` into a class that did not exist. `emphasis` STAYS at 100%: 83
363
+ * files reach for it as max ink, and moving it would have dimmed every heading
364
+ * in the estate silently, with nothing failing. Three roles are new over stops
365
+ * that already existed; only `lede` needed a new stop.
366
+ *
367
+ * The naming shifts register at 88 — subtle…strong are document roles, shout
368
+ * and scream are volume. That is deliberate: the top two are for chrome, not
369
+ * for text.
370
+ *
371
+ * `mute` (32%) was retired 2026-04-30 with zero consumers and does not return.
372
+ *
373
+ * Tokens chain through the fg ramp — single source of truth is preserved;
374
+ * descriptors are aliases, never their own colour values.
345
375
  * ============================================================================== */
346
376
 
347
377
  :root {
348
378
  --kol-fg-subtle: var(--kol-fg-24);
349
379
  --kol-fg-meta: var(--kol-fg-48);
350
380
  --kol-fg-body: var(--kol-fg-64);
381
+ --kol-fg-lede: var(--kol-fg-72);
351
382
  --kol-fg-strong: var(--kol-fg-80);
383
+ --kol-fg-shout: var(--kol-fg-88);
384
+ --kol-fg-scream: var(--kol-fg-96);
352
385
  --kol-fg-emphasis: var(--kol-surface-on-primary);
386
+
387
+ /* DEPRECATED ALIAS — `default` is the old name for `body`, kept pointing at
388
+ * the same stop so a consumer mid-sweep cannot render colourless. Remove once
389
+ * no source references it. */
390
+ --kol-fg-default: var(--kol-fg-body);
353
391
  }
354
392
 
355
393
  /* ─── Text descriptors (5 stops — `mute` retired 2026-04-30, 0 consumers).
@@ -359,11 +397,21 @@
359
397
  .text-subtle { color: var(--kol-fg-subtle); }
360
398
  .text-meta { color: var(--kol-fg-meta); }
361
399
  .text-body { color: var(--kol-fg-body); }
400
+ .text-lede { color: var(--kol-fg-lede); }
362
401
  .text-strong { color: var(--kol-fg-strong); }
402
+ .text-shout { color: var(--kol-fg-shout); }
403
+ .text-scream { color: var(--kol-fg-scream); }
363
404
  .text-emphasis { color: var(--kol-fg-emphasis); }
364
405
 
406
+ /* Deprecated alias — see the token block. */
407
+ .text-default { color: var(--kol-fg-body); }
408
+
365
409
  .hover\:text-subtle:hover { color: var(--kol-fg-subtle); }
366
410
  .hover\:text-meta:hover { color: var(--kol-fg-meta); }
367
411
  .hover\:text-body:hover { color: var(--kol-fg-body); }
412
+ .hover\:text-lede:hover { color: var(--kol-fg-lede); }
368
413
  .hover\:text-strong:hover { color: var(--kol-fg-strong); }
414
+ .hover\:text-shout:hover { color: var(--kol-fg-shout); }
415
+ .hover\:text-scream:hover { color: var(--kol-fg-scream); }
369
416
  .hover\:text-emphasis:hover { color: var(--kol-fg-emphasis); }
417
+ .hover\:text-default:hover { color: var(--kol-fg-body); }
package/kol-theme.css CHANGED
@@ -81,6 +81,11 @@
81
81
  * and needs no alias of its own. */
82
82
  --kol-pad-rail-row-y: 0.375rem;
83
83
 
84
+ /* The row's TEXT INDENT — `.shell-nav-item`'s left padding, named 2026-08-01
85
+ * so anything else that has to line up with a row (the tag shelf) reads the
86
+ * indent instead of retyping it. */
87
+ --kol-pad-rail-row-x: 1.25rem;
88
+
84
89
  /* Content widths (2026-07-28; panel added 2026-07-30) — ONE frame, three
85
90
  * inner caps (chess law). Every page: mx-auto max-w-shell + the one padding
86
91
  * rhythm, content LEFT-ANCHORED inside. Width is a content decision, never a
@@ -24,7 +24,15 @@
24
24
  * ============================================================================= */
25
25
 
26
26
  /* eyebrow — the overline label above a title ("KOL · ICONS"). Single-line
27
- * chrome → helper ramp (line-height 1); never carries paragraphs. */
27
+ * chrome → helper ramp (line-height 1); never carries paragraphs.
28
+ *
29
+ * Ink: --kol-fg-strong (80%), raised from --kol-fg-meta (48%) on 2026-08-01 by
30
+ * user ruling. An eyebrow is a SECTION HEADER, not a caption: it names the
31
+ * material below it and is what the eye lands on when scanning a rail. At the
32
+ * meta stop it sat at the same weight as the captions and disabled hints it is
33
+ * supposed to outrank, so the rail read as one flat field. Uppercase mono at
34
+ * the smallest step with wide tracking is already a quiet treatment — the ink
35
+ * does not also need to whisper. */
28
36
  .kol-doc-eyebrow {
29
37
  font-family: var(--kol-font-family-mono);
30
38
  font-size: 10px;
@@ -32,7 +40,7 @@
32
40
  font-weight: 500;
33
41
  letter-spacing: 0.1em;
34
42
  text-transform: uppercase;
35
- color: var(--kol-fg-meta);
43
+ color: var(--kol-fg-strong);
36
44
  }
37
45
 
38
46
  /* heading — the page H1 of doc chrome (matches the DocHeader contract). */
@@ -60,7 +68,7 @@
60
68
  line-height: 160%;
61
69
  font-weight: 400;
62
70
  letter-spacing: 0.04em;
63
- color: var(--kol-fg-body);
71
+ color: var(--kol-fg-default);
64
72
  max-width: var(--kol-content-measure);
65
73
  }
66
74
 
@@ -72,7 +80,7 @@
72
80
  line-height: 160%;
73
81
  font-weight: 400;
74
82
  letter-spacing: 0.04em;
75
- color: var(--kol-fg-body);
83
+ color: var(--kol-fg-default);
76
84
  max-width: var(--kol-content-measure);
77
85
  }
78
86
 
@@ -93,13 +101,31 @@
93
101
  .kol-doc-code { margin: 0; }
94
102
  .kol-prose pre { margin: 24px 0; } /* prose keeps its editorial block rhythm */
95
103
 
96
- /* inline code */
104
+ /* INLINE CODE — the chip look, shared with `.kol-table-token` (user question
105
+ * 2026-08-01: *"list all the inline code classes, why do we have more then
106
+ * one?"*). There are three, and only two of them are one concept:
107
+ *
108
+ * .kol-doc-code-inline prose inline code. Size is RELATIVE (0.875em) so it
109
+ * tracks whatever text it sits in. Wraps.
110
+ * .kol-table-token the same chip as fixed-size chrome inside a Table.
111
+ * Never wraps. Defined in kol-components-organisms.css
112
+ * because it belongs to that component.
113
+ * .kol-doc-table-token NOT a chip — a `td` SLOT (no fill, no padding, no
114
+ * radius) that types a whole cell as one token. It
115
+ * only shares the word "token".
116
+ *
117
+ * The two chips had drifted on every value that makes them look alike: fg-04
118
+ * against fg-08, radius-sm against a bare literal, no colour against fg-80.
119
+ * Same concept, two spellings — the `.text-fg-*` vs `--kol-fg-*` lesson again.
120
+ * FILL, RADIUS and COLOUR are now one answer; size and padding stay per role,
121
+ * because relative-in-prose and fixed-in-chrome is a real difference. */
97
122
  .kol-prose code,
98
123
  .kol-doc-code-inline {
99
124
  font-family: var(--kol-font-family-mono);
100
125
  font-size: 0.875em;
101
126
  font-weight: 400;
102
- background: var(--kol-fg-04);
127
+ color: var(--kol-fg-80);
128
+ background: var(--kol-fg-08);
103
129
  padding: 0.1em 0.35em;
104
130
  border-radius: var(--kol-radius-sm);
105
131
  }
@@ -147,7 +173,7 @@
147
173
  padding: 12px 16px;
148
174
  border-bottom: 1px solid var(--kol-fg-08);
149
175
  vertical-align: top;
150
- color: var(--kol-fg-body);
176
+ color: var(--kol-fg-default);
151
177
  }
152
178
  .kol-prose tbody tr:last-child td,
153
179
  .kol-doc-table tbody tr:last-child td { border-bottom: 0; }
@@ -7,60 +7,44 @@
7
7
  * the scale with its own mono font — fonts are per-app (brand = JetBrains Mono,
8
8
  * web = RightGroteskMono). The umbrella kol-theme.css imports both files.
9
9
  *
10
- * Italic note: only 500i is bundled (Medium Italic). 400i (Regular Italic)
11
- * deferred until a consumer requires it.
10
+ * VARIABLE, since 2026-08-01. Two files carry every weight in the family.
12
11
  *
13
12
  * Architecture: docs/kol-migration/locked/typography-system.md
14
13
  */
15
14
 
16
15
  /* =============================================================================
17
- * FONT-FACE — JetBrains Mono (6 faces: 400, 400i, 500, 500i, 600, 600i)
16
+ * FONT-FACE — JetBrains Mono, VARIABLE (2 faces: roman + italic, wght 100–800)
17
+ *
18
+ * WHY (user ruling 2026-08-01). This was SEVEN static cuts and it still could
19
+ * not answer a simple design question. The rail needed a Page weight lighter
20
+ * than its Chapter; the folder's lightest cut was 300, and 300 against 500 is a
21
+ * 0.28px difference in stroke at 14px — applied, correct in the cascade, and
22
+ * invisible. Reaching a weight that reads meant shipping another static, and
23
+ * the next question would have meant another one.
24
+ *
25
+ * The axis removes the question. `wght 100 800` means any weight is a NUMBER,
26
+ * not a new file — and the payload went DOWN: two variable faces at ~230 kb
27
+ * against 1024 kb of statics, with four weights the statics never had.
28
+ *
29
+ * The tradeoff, stated: a variable file is one download, so the page fetches
30
+ * the whole axis or none of it. At this size that is still less than the two
31
+ * static cuts it replaces.
18
32
  * ============================================================================= */
19
33
 
20
34
  @font-face {
21
35
  font-family: 'JetBrains Mono';
22
- src: url('/fonts/jetbrains-mono/JetBrainsMono-Regular.woff2') format('woff2');
23
- font-weight: 400;
24
- font-style: normal;
25
- font-display: swap;
26
- }
27
-
28
- @font-face {
29
- font-family: 'JetBrains Mono';
30
- src: url('/fonts/jetbrains-mono/JetBrainsMono-Italic.woff2') format('woff2');
31
- font-weight: 400;
32
- font-style: italic;
33
- font-display: swap;
34
- }
35
-
36
- @font-face {
37
- font-family: 'JetBrains Mono';
38
- src: url('/fonts/jetbrains-mono/JetBrainsMono-Medium.woff2') format('woff2');
39
- font-weight: 500;
40
- font-style: normal;
41
- font-display: swap;
42
- }
43
-
44
- @font-face {
45
- font-family: 'JetBrains Mono';
46
- src: url('/fonts/jetbrains-mono/JetBrainsMono-MediumItalic.woff2') format('woff2');
47
- font-weight: 500;
48
- font-style: italic;
49
- font-display: swap;
50
- }
51
-
52
- @font-face {
53
- font-family: 'JetBrains Mono';
54
- src: url('/fonts/jetbrains-mono/JetBrainsMono-SemiBold.woff2') format('woff2');
55
- font-weight: 600;
36
+ src: url('/fonts/jetbrains-mono/JetBrainsMono-Variable.woff2') format('woff2-variations'),
37
+ url('/fonts/jetbrains-mono/JetBrainsMono-Variable.woff2') format('woff2');
38
+ font-weight: 100 800;
56
39
  font-style: normal;
57
40
  font-display: swap;
58
41
  }
59
42
 
60
43
  @font-face {
61
44
  font-family: 'JetBrains Mono';
62
- src: url('/fonts/jetbrains-mono/JetBrainsMono-SemiBoldItalic.woff2') format('woff2');
63
- font-weight: 600;
45
+ src: url('/fonts/jetbrains-mono/JetBrainsMono-Italic-Variable.woff2') format('woff2-variations'),
46
+ url('/fonts/jetbrains-mono/JetBrainsMono-Italic-Variable.woff2') format('woff2');
47
+ font-weight: 100 800;
64
48
  font-style: italic;
65
49
  font-display: swap;
66
50
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kolkrabbi/kol-theme",
3
- "version": "0.19.0",
3
+ "version": "0.30.2",
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",