/**
 * KOL components — molecule tier
 *
 * Pill / Tag / Badge — three pill-shaped status / label molecules with
 * overlapping use cases. Future consolidation candidate (see
 * docs/kol-migration/components/02-components-molecules.md §6 / §7).
 * For now they ship with their original CSS surface, just relocated
 * out of the kol-components.css kitchen sink.
 *
 * ─────────────────────────────────────────────────────────────────────
 * Pill — molecules/Pill.jsx
 *   Variants: outline / subtle / inverse
 *   Sizes:    sm / md / lg
 * ─────────────────────────────────────────────────────────────────────
 */

/* Every floating node (PopoverPanel root, with or without panel chrome).
 * Floats must top overlay chrome (.kol-overlay is z-100) — without this a
 * dropdown/menu/tooltip opened INSIDE a FullscreenOverlay stacks under the
 * sheet and its options intercept nothing. */
.kol-popover-float {
  /* above the ShellDrawer sheet (z 200) — a Dropdown in the settings drawer
   * opened BEHIND it (SettingsPanelApproved, 2026-08-27) */
  z-index: 210;
}

/* ── Dropdown — .kol-dd-* ─────────────────────────────────────────────────
 * IMPLEMENTED 2026-08-09. Dropdown.jsx stamped these classes and the docs
 * page recorded their contract (2026-07-08 chrome law) — but no stylesheet
 * ever carried them, so triggers rendered gapless bare buttons at whatever
 * width a resize listener guessed. The trigger IS button chrome (kol-btn
 * rules own fill/focus); these add ONLY trigger layout + the open/panel
 * fusion. No hover, no clicked state (2026-07-15 ruling) — rest and open. */
.kol-dd-trigger {
  justify-content: space-between;
  gap: var(--kol-spacing-1);
  width: 100%;
}
/* THE TRIGGER IS ON THE ICON LADDER (DropdownHeightAndHover, kol-fxr 2026-08-28).
 * A text button takes its height from padding — `.kol-btn-sm` is `4px 12px` on
 * mono-12, about 24 — while every icon control beside it is PINNED
 * (`.kol-btn-icon.kol-btn-sm` is 28×28, the 2026-07-28 law: "the box never
 * moves"). So a `sm` Dropdown sat 4px short of the `IconFrame` and `ThemeToggle`
 * on its own row — the picker-then-icon-cluster header that kol-r2b2 draws and
 * the estate is copying. The trigger is the one text control that always sits in
 * an icon row, so it takes the icon rungs: 28 / 32 / 36. Horizontal padding is
 * untouched; the vertical goes, because the flex box centres the label. */
/* 22/26/32/40 since 2026-09-02 — one height per size, so the trigger, the
 * IconFrame and the Input on a row are the same box (see .kol-btn-icon). */
/* ICON-ONLY TRIGGER (editor-set-is-behind-its-source, kol-fxr 2026-09-03). The
 * square comes from `.kol-btn-icon` (22/26/32/40, padding 0) and this only has
 * to undo the text trigger's own layout: `width: 100%` would stretch the square
 * across its column, and `space-between` would push a lone glyph to one edge.
 * A tool-rail dropdown is then the same box as the icon button beside it —
 * which is the whole reason fxr's ToolPalette hand-rolled a trigger instead. */
.kol-dd-trigger--icon {
  width: auto;
  justify-content: center;
  gap: 0;
  position: relative;
}
.kol-dd-trigger.kol-btn-xs { height: 22px; padding-block: 0; }
.kol-dd-trigger.kol-btn-sm { height: 26px; padding-block: 0; }
.kol-dd-trigger.kol-btn-md { height: 32px; padding-block: 0; }
.kol-dd-trigger.kol-btn-lg { height: 40px; padding-block: 0; }
/* REST AND OPEN ARE THE ONLY STATES (2026-07-15, re-called 2026-08-09 and
 * 2026-08-28). No :hover or :active rules for the trigger live here any more —
 * kol-components-atoms.css excludes `.kol-dd-trigger` from every variant's
 * hover and press instead. The pin-backs that stood here restated each
 * variant's REST colours to cancel the button's, and that failed twice: one
 * went stale when `.kol-btn-outline`'s rest border moved (2026-08-26), and a
 * pin-back out-specifies the tone, so a sunken trigger hovered into the
 * untoned fill. "No state" is an absence now, not a mirror. */

/* ONE PIECE MEANS ONE WIDTH (2026-08-09 user call — "they are connected").
 * The trigger reserves the widest option's width by stacking every label in
 * one grid cell (hidden ghosts), so the panel — pinned to the trigger by
 * matchReferenceWidth — is exactly as wide as the trigger at every value. */
/* text-align: the trigger is a real <button>, and the UA stylesheet centers
 * button text — the stretched label span then centers inside the widest-ghost
 * cell (a value floating at a random indent per list). Pin it to the leading
 * edge, Input's model. */
.kol-dd-label { display: inline-grid; min-width: 0; text-align: start; overflow: hidden; }
.kol-dd-label > span { grid-area: 1 / 1; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
/* Ghost slack: each hidden option reserves the PANEL row's trailing check
 * column on top of its label, so the fused one-piece width fits every row
 * without truncating (2026-08-09 night — closes the "Cen…" defect at the
 * trigger instead of un-fusing the panel). */
.kol-dd-ghost { visibility: hidden; padding-right: 1rem; }
/* THE RESERVATION YIELDS TO THE AVAILABLE WIDTH (DropdownGhostWidthAndListHeight,
 * kol-mirror 2026-08-28 — user, on the studio's 30-option Source picker in a
 * ~300px shelf: "1 it's way too tall, 2 it's not fitting"). The ghost stack
 * above reserves the widest option's width, and the 2026-08-09 one-piece ruling
 * assumed a trigger free to take it: with 30 long labels it computed ~640px,
 * overflowed its container and was clipped, while the portalled panel rendered
 * at the true 640 — a panel wider than and detached from its trigger. Capping
 * the trigger at its container keeps the fusion honest (the panel still matches
 * `rects.reference.width`, which is now the real one) and the label ellipsises.
 * A dropdown in a narrow chrome is where dropdowns live. */
/* `min-width: 0` beside the cap (kol-mirror's adoption, 2026-08-28): the case
 * that broke was a trigger inside a `flex items-center justify-between` row, and
 * a flex child's default `min-width: auto` floors it at its CONTENT — so the cap
 * resolved against a box the ghost stack had already pushed wide, and the
 * trigger still overflowed the shelf. The pair is the fix: the floor lets it
 * shrink, the cap stops it growing. */
.kol-dd-root { max-width: 100%; min-width: 0; }
.kol-dd-trigger { max-width: 100%; min-width: 0; }
.kol-dd-caret {
  flex-shrink: 0;
  transition: rotate var(--kol-transition-base);
}
.kol-dd-trigger[data-state="open"] .kol-dd-caret { rotate: 180deg; }
/* grey — `.kol-dd-trigger--grey` is an alias of the grey TONE now (see TONE,
 * below); the trigger is `.kol-btn` and paints from the tone properties */
/* open/panel fusion — the open trigger and its panel read as ONE piece:
 * trigger squares its bottom corners, panel squares its top, and the fill
 * continues (panel offset is -1 in Dropdown.jsx for the same reason). */
.kol-dd-trigger--open { border-bottom-left-radius: 0; border-bottom-right-radius: 0; }
/* Flex column so the viewport clamp (Popover size middleware sets maxHeight
 * on this element) reaches the list: the hairline keeps its 1px, the list
 * takes the rest and scrolls inside — the panel itself never overflows the
 * viewport (2026-08-09, kol-ds-fxr long-dropdown ticket). */
.kol-dd-panel {
  border-radius: 0 0 var(--kol-radius-sm) var(--kol-radius-sm);
  overflow: hidden;
  display: flex;
  flex-direction: column;
  /* the FLOATING surface of the trigger's tone (TONE, below): primary / grey /
   * sunken continue their fill, outline and ghost paint the page's ground —
   * `--kol-tone-ground` — so opening changes nothing under the panel. The
   * `--primary` / `--grey` / `--outline` classes are aliases of the tones. */
  background-color: var(--kol-tone-panel-bg, var(--kol-tone-bg, var(--kol-surface-secondary)));
  border: var(--kol-tone-panel-border-w, 0) solid var(--kol-tone-panel-border, transparent); /* outline: matches the trigger's rest border */
  border-top: 0; /* the trigger's bottom edge is the divider (docs page) */
}
.kol-dd-div  { border-top: 1px solid var(--kol-fg-08); }
/* A ROWS CEILING THE CONSUMER CAN REACH (same ticket): Popover's size
 * middleware clamps the PANEL to the viewport, which does nothing for a
 * 30-row slab opened at the top of a tall page — and its inline `maxHeight`
 * cannot be overridden from a consumer stylesheet, so the ceiling had to be
 * ours. `maxRows` (Dropdown, default 10) and `rowHeight` write these two
 * variables on the panel; the list scrolls at the ceiling as it always could. */
.kol-dd-list {
  padding: var(--kol-spacing-1);
  overflow-y: auto;
  min-height: 0;
  max-height: calc(var(--kol-dd-max-rows, 10) * var(--kol-dd-row-h, 2rem) + var(--kol-spacing-1) * 2);
}

/* Popover — default chrome for floating-ui-positioned panels.
 * Used by molecules/Popover.jsx PopoverPanel. */
.kol-popover {
  /* A TONED panel paints that tone's ground; unset it keeps surface-secondary,
   * so nothing existing moves (editor-chrome-review, kol-fxr 2026-09-03 — in
   * dark, surface-secondary is LIGHTER than the bar a menu drops from, so the
   * layering read inverted). The border is the OPAQUE ramp, not the alpha one:
   * a floating panel overlaps whatever is under it, and an alpha hairline
   * composites twice at every overlap — the same law the icons follow. */
  background: var(--kol-tone-bg, var(--kol-surface-secondary));
  border: 1px solid var(--kol-oq-04);
  border-radius: var(--kol-radius-sm);
  box-shadow: 0 8px 24px -8px rgba(0, 0, 0, 0.4),
              0 2px 6px -2px rgba(0, 0, 0, 0.3);
  z-index: var(--kol-z-tooltip, 300); /* the ladder's float tier, not nav's 1000 (EditorOverlaysOnFullscreenOverlay, user ruling 2026-08-27) */
  outline: none;
}

/* Tooltip — hover-triggered text label with optional kbd-style shortcut chip.
 * Used by molecules/Popover.jsx Tooltip. No border (intentionally distinct
 * from native browser tooltips); shortcut renders as a fg-tinted chip. */
.kol-tooltip {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  background: var(--kol-surface-secondary);
  border-radius: var(--kol-radius-sm);
  padding: 4px 4px 4px 8px;
  font-family: var(--kol-font-family-mono);
  font-size: 11px;
  line-height: 1;
  white-space: nowrap;
  pointer-events: none;
  z-index: var(--kol-z-tooltip, 300); /* the ladder's float tier, not nav's 1000 (EditorOverlaysOnFullscreenOverlay, user ruling 2026-08-27) */
  box-shadow: 0 6px 16px -4px rgba(0, 0, 0, 0.5);
}

.kol-tooltip-key {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-width: 16px;
  height: 16px;
  padding: 0 4px;
  background: var(--kol-fg-08);
  border-radius: 3px;
  font-size: 10px;
  letter-spacing: 0.02em;
  color: var(--kol-surface-on-primary);
}

.pill-inverse {
  display: inline-flex;
  align-items: center;
  padding: 4px 16px;
  border-radius: var(--kol-radius-full);
  background-color: var(--kol-surface-on-primary);
  color: var(--kol-surface-primary);
  border: none;
}

.pill-subtle {
  display: inline-flex;
  align-items: center;
  padding: 4px 16px;
  border-radius: var(--kol-radius-full);
  background-color: color-mix(in srgb, var(--kol-surface-on-primary) 16%, transparent);
  color: var(--kol-surface-on-primary);
  border: 1px solid transparent;
}

.pill-outline {
  display: inline-flex;
  align-items: center;
  padding: 4px 16px;
  border-radius: var(--kol-radius-full);
  background-color: var(--kol-surface-primary);
  color: var(--kol-surface-on-primary);
  border: 1px solid var(--kol-border-default);
}

/* Pill takes the TRACKING but NOT the transform (user rulings 2026-08-01).
 * Pill has no single base class, so its size classes carry it. The uppercase
 * exception is Tag's alone: a Pill's label IS authored at the call site, so the
 * no-`text-transform` law applies to it unchanged. */
.pill-sm,
.pill-md,
.pill-lg { letter-spacing: 0.04em; }

/* PADDING ONLY — type is kol-helper-*, applied in Pill.jsx. Same ruling as
 * .kol-tag--* above (2026-08-01). */
.pill-sm { padding: 2px 10px; }
.pill-md { padding: 4px 16px; }
.pill-lg { padding: 6px 20px; }


/* ─────────────────────────────────────────────────────────────────────
 * Tag — atoms/Tag.jsx.  REBUILT ON PILL'S MODEL (user ruling 2026-08-01).
 *
 * Was: four class schemes for one component (.tag-control, .tag, .tag-naked,
 * .tag-control-inverse), four declared variants, and exactly ONE :hover rule
 * between them. Passing `color` swapped the base class off .tag-control and
 * silently cost the chip its interaction state.
 *
 * Now: ONE scheme. `.kol-tag` is the box; `--primary|--secondary|--inverse` is
 * the look, matching Pill and Button's vocabulary; `--sm|--md|--lg` is the
 * scale. EVERY variant carries hover and active — a variant without a state is
 * the defect this rewrite exists to remove, and `pnpm validate:chrome` fails
 * any that lacks one.
 * ───────────────────────────────────────────────────────────────────── */

.kol-tag {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  border-radius: var(--kol-radius-full);
  border: 1px solid transparent;
  transition: background-color 0.15s ease, color 0.15s ease, border-color 0.15s ease;
  /* THE CHIP'S OWN TYPE (user rulings 2026-08-01, in two passes).
   *
   * First pass: the chip was moved onto the `kol-helper-*` ramp for its
   * `line-height: 1` and inherited `letter-spacing: 0.10em` with it — the
   * ramp's eyebrow tracking, and far too wide on a tag string. Set to `normal`.
   *
   * Second pass: `normal` is zero, which is too tight for caps. The chip is
   * uppercase at **0.04em** — between the two, and the value the user named.
   *
   * UPPERCASE IS AN EXPLICIT EXCEPTION to the no-`text-transform` law, and the
   * only one in the component tier. That law says casing is authored at the
   * CALL SITE — which works because a call site exists. A tag has none: the
   * string is `#domain/design-system`, generated from frontmatter data, so
   * there is nowhere to author it. The transform lives here or nowhere. */
  text-transform: uppercase;
  letter-spacing: 0.04em;
}

/* DATA CHIPS RENDER VERBATIM (2026-08-09). The uppercase above exists for
 * LABEL strings with no authoring site (frontmatter tags). A filename is
 * DATA — transforming it misquotes it (certificate.pdf ≠ CERTIFICATE.PDF).
 * FieldRow's file token wears this; any chip carrying user data should. */
.kol-tag.kol-tag--data { text-transform: none; letter-spacing: normal; }

/* PADDING ONLY. Type is the kol-helper-* ramp, applied at the call site
 * (Tag.jsx) — these rules used to hardcode font-size + font-weight and set no
 * line-height, so a 10px chip inherited the body's ~1.5 and stood 22px tall
 * beside 14px rows. Two owners of one concept is the drift this repo keeps
 * paying for; the class owns the type, the rule owns the box. */
/* the SIZE carries its type (WorkListingRowsAndFilters, 2026-08-27): the helper
 * rung that rode beside it in the atom (sm → 10 · md → 12 · lg → 14) lives here. */
/* xs — the panel rung (2026-09-01): one ladder, every family */
.kol-tag--xs { padding: 1px 8px; font-family: var(--kol-font-family-mono); font-size: 8px; line-height: 1; font-weight: 500; letter-spacing: 0.10em; }
.kol-tag--sm { padding: 2px 10px; font-family: var(--kol-font-family-mono); font-size: 10px; line-height: 1; font-weight: 500; letter-spacing: 0.10em; }
.kol-tag--md { padding: 4px 16px; font-family: var(--kol-font-family-mono); font-size: 12px; line-height: 1; font-weight: 500; letter-spacing: 0.06em; }
.kol-tag--lg { padding: 6px 20px; font-family: var(--kol-font-family-mono); font-size: 14px; line-height: 1; font-weight: 500; letter-spacing: 0.06em; }

/* primary — the filled chip. Mirrors .pill-subtle's weight. */
.kol-tag--primary {
  background-color: color-mix(in srgb, var(--kol-surface-on-primary) 16%, transparent);
  color: var(--kol-surface-on-primary);
}
.kol-tag--primary:hover {
  background-color: color-mix(in srgb, var(--kol-surface-on-primary) 24%, transparent);
}
.kol-tag--primary.is-active {
  background-color: color-mix(in srgb, var(--kol-surface-on-primary) 32%, transparent);
  border-color: currentColor;
}

/* secondary — the outlined chip. Mirrors .pill-outline. */
.kol-tag--secondary {
  background-color: var(--kol-surface-primary);
  color: var(--kol-surface-on-primary);
  border-color: var(--kol-border-default);
}
.kol-tag--secondary:hover {
  background-color: color-mix(in srgb, var(--kol-surface-on-primary) 08%, transparent);
}
.kol-tag--secondary.is-active {
  background-color: color-mix(in srgb, var(--kol-surface-on-primary) 16%, transparent);
  border-color: currentColor;
}

/* tertiary — secondary's fill, NO outline, the MONO voice at 80 (TagTertiary,
 * kol-website 2026-08-27, ruled on /work's rows: the outline read wrong beside
 * the display line and the helper voice was too loud under a title). The size
 * classes carry the helper type by default; tertiary re-types them on the mono
 * ramp — 10 / 12 / 14, weight 400, line-height 100%, 0.04em. */
.kol-tag--tertiary {
  background-color: var(--kol-surface-primary);
  color: var(--kol-fg-80);
  border-color: transparent;
}
.kol-tag--tertiary:hover {
  background-color: color-mix(in srgb, var(--kol-surface-on-primary) 08%, transparent);
  border-color: transparent;
}
.kol-tag--tertiary.is-active {
  background-color: color-mix(in srgb, var(--kol-surface-on-primary) 16%, transparent);
  border-color: transparent;
}
.kol-tag--tertiary.kol-tag--sm { font-size: 10px; font-weight: 400; line-height: 100%; letter-spacing: 0.04em; }
.kol-tag--tertiary.kol-tag--md { font-size: 12px; font-weight: 400; line-height: 100%; letter-spacing: 0.04em; }
.kol-tag--tertiary.kol-tag--lg { font-size: 14px; font-weight: 400; line-height: 100%; letter-spacing: 0.04em; }

/* StatusChip — the record-surface status control (reference: Framer CMS,
 * 2026-08-09; rebuilt off Tag onto the Dropdown-sm metrics the same day).
 * Tint + ink ride the --ui-* ladder; box/type live in the JSX (kol-btn-sm
 * values + mono-12). No tone → the neutral chip. */
/* OPAQUE tone fills (user frame 43, 2026-08-09): the tone blends INTO the
 * surface — a solid dark tint that holds steady over any row background.
 * The earlier transparent wash let zebra/hover rows bleed through the pill. */
.kol-status-chip {
  color: var(--kol-surface-on-primary);
  background-color: color-mix(in srgb, var(--kol-surface-on-primary) 8%, var(--kol-surface-primary));
}
/* ── FieldRow — .kol-field-row ────────────────────────────────────────────
 * Row anatomy: label column LEFT beside the control column (reference,
 * 2026-08-09). Lives here, not in a Tailwind arbitrary utility — package
 * chrome rides theme CSS (the SegmentedToggle lesson): an arbitrary class
 * silently missed generation in a consumer build and the rows stacked. */
.kol-field-row {
  display: grid;
  grid-template-columns: minmax(0, 12rem) minmax(0, 1fr);
}

/* Chevron weight (user frames 2026-08-09): at the chip size the set
 * chevron's 1.5 stroke renders a 0.75px hairline; 4 units read too thick.
 * 3 units on the 24 viewBox → 1.5px rendered at size 12. Chip-scoped: the
 * keyline law governs the set's drawings; chrome may weight a glyph in
 * context. */
.kol-status-chip svg path { stroke-width: 3px; }
.kol-status-chip--success {
  color: var(--ui-success);
  background-color: color-mix(in srgb, var(--ui-success) 15%, var(--kol-surface-primary));
}
.kol-status-chip--warning {
  color: var(--ui-warning);
  background-color: color-mix(in srgb, var(--ui-warning) 15%, var(--kol-surface-primary));
}
.kol-status-chip--error {
  color: var(--ui-error);
  background-color: color-mix(in srgb, var(--ui-error) 15%, var(--kol-surface-primary));
}
.kol-status-chip--info {
  color: var(--ui-info);
  background-color: color-mix(in srgb, var(--ui-info) 15%, var(--kol-surface-primary));
}
/* --primary — the Dropdown-primary trigger's fill + ink on the pill box
 * (select columns in record surfaces, user frame 2026-08-09: "use status
 * pill, but same look as dropdown primary"). Values mirror .kol-btn-primary. */
.kol-status-chip--primary {
  color: var(--kol-surface-on-primary);
  background-color: var(--kol-surface-secondary);
}

/* inverse — the flipped chip. Mirrors .pill-inverse. */
.kol-tag--inverse {
  background-color: var(--kol-surface-on-primary);
  color: var(--kol-surface-primary);
}
.kol-tag--inverse:hover {
  background-color: color-mix(in srgb, var(--kol-surface-on-primary) 88%, transparent);
}
.kol-tag--inverse.is-active {
  border-color: var(--kol-surface-primary);
}

.kol-tag-dismiss {
  display: inline-flex;
  align-items: center;
  cursor: pointer;
  opacity: 0.64;
  transition: opacity 0.15s ease;
}
.kol-tag-dismiss:hover { opacity: 1; }

/* The per-colour tag palette was DELETED 2026-08-01 with the `color` prop.
 * A chip's look is its variant now (kol-tag--primary|secondary|inverse), the
 * same vocabulary Pill and Button use. Tag colour BY TAXONOMY returns later
 * as its own decision, layered ON the variants rather than replacing them. */

.control-unified-inverse {
  display: inline-flex;
  align-items: center;
  padding: 4px 0;
  border-radius: var(--kol-radius-full);
  background-color: var(--kol-surface-on-primary);
  color: var(--kol-surface-primary);
  border: 1px solid transparent;
  transition: background-color var(--kol-transition-base),
              color var(--kol-transition-base);
}

.control-unified-inverse:hover {
  background-color: var(--kol-surface-primary);
  color: var(--kol-surface-on-primary);
}


/* ─────────────────────────────────────────────────────────────────────
 * Badge — molecules/Badge.jsx
 *   8 variants: default / secondary / destructive / outline / success /
 *               warning / critical / info
 *   3 sizes:    sm / md / lg
 *
 * Note: status colors (destructive / success / warning / critical / info)
 * use literal hex; could migrate to --ui-error / --ui-warning / etc.
 * tokens in a future pass.
 * ───────────────────────────────────────────────────────────────────── */

.kol-badge {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  border-radius: 4px;
  font-family: var(--kol-font-family-mono);
  font-weight: 470;
  transition: background-color var(--kol-transition-base),
              color var(--kol-transition-base);
}

.kol-badge-default {
  background-color: var(--kol-surface-on-primary);
  color: var(--kol-surface-primary);
  border: 1px solid transparent;
}

.kol-badge-secondary {
  background-color: color-mix(in srgb, var(--kol-surface-on-primary) 12%, transparent);
  color: var(--kol-surface-on-primary);
  border: 1px solid transparent;
}

/* Tones ride the --ui-* ladder with StatusChip's exact fill formula — Badge
 * and StatusChip are siblings and speak one language (user ruling
 * 2026-08-09, "like a button and dropdown"). Canonical tone name is
 * `error`; destructive/critical are legacy aliases of the same rule. */
.kol-badge-error,
.kol-badge-destructive,
.kol-badge-critical {
  color: var(--ui-error);
  background-color: color-mix(in srgb, var(--ui-error) 15%, var(--kol-surface-primary));
  border: 1px solid transparent;
}

.kol-badge-outline {
  background-color: transparent;
  color: var(--kol-surface-on-primary);
  border: 1px solid var(--kol-border-default);
}

.kol-badge-success {
  color: var(--ui-success);
  background-color: color-mix(in srgb, var(--ui-success) 15%, var(--kol-surface-primary));
  border: 1px solid transparent;
}

.kol-badge-warning {
  color: var(--ui-warning);
  background-color: color-mix(in srgb, var(--ui-warning) 15%, var(--kol-surface-primary));
  border: 1px solid transparent;
}

.kol-badge-info {
  color: var(--ui-info);
  background-color: color-mix(in srgb, var(--ui-info) 15%, var(--kol-surface-primary));
  border: 1px solid transparent;
}

.kol-badge-xs {
  height: 16px;
  padding: 0 4px;
  font-size: 8px;
}
.kol-badge-sm {
  height: 20px;
  padding: 0 6px;
  font-size: 10px;
}

.kol-badge-md {
  height: 24px;
  padding: 0 8px;
  font-size: 12px;
}

.kol-badge-lg {
  height: 28px;
  padding: 0 12px;
  font-size: 14px;
}

/* ─────────────────────────────────────────────────────────────────────
 * SegmentedToggle — molecules/SegmentedToggle.jsx
 *   Bordered strip of joined cells sharing one outer stroke, thin
 *   dividers between cells, filled active cell.
 *
 *   Chrome lives HERE, not in Tailwind utilities: Tailwind never scans
 *   package sources, so utility-class chrome inside a published
 *   component silently vanishes for every consumer. Any component whose
 *   look depends on flex/border/rounded utilities needs a kol-* class
 *   in this package instead.
 * ───────────────────────────────────────────────────────────────────── */
.kol-seg {
  display: flex;
  /* oq-08, opaque — the one outline border every control wears (user ruling
   * 2026-08-26); was fg-04, which doubled wherever two strips touched. */
  border: 1px solid var(--kol-oq-08);
  border-radius: var(--kol-radius-sm);
  overflow: hidden;
  /* The button-height LAW (2026-08-12): the strip's OUTER height is PINNED
   * to the .kol-btn ladder — 26 / 32 / 40 — regardless of what a cell holds
   * (icon or text) and regardless of variant (the filled variant drops the
   * group border, so padding-driven height ran 2px short — the bug that
   * forced the pin). Same principle as .kol-btn-icon's pinned square:
   * the box never moves. */
  box-sizing: border-box;
  height: 32px; /* md — .kol-btn-md */
}

/* Sizes mirror .kol-btn-{sm,md,lg} exactly: pinned height above + cell
 * padding per size (below) + mono type (12/14/16) applied in JSX. */
.kol-seg--xs { height: 22px; }
.kol-seg--sm { height: 26px; }
.kol-seg--lg { height: 40px; }
.kol-seg--xs .kol-seg-cell { padding: 4px 8px; }
.kol-seg--sm .kol-seg-cell { padding: 4px 12px; }
.kol-seg--lg .kol-seg-cell { padding: 8px 20px; }

.kol-seg-cell {
  flex: 1;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  padding: 6px 16px;  /* md — .kol-btn-md */
  border: none;
  cursor: pointer;
  white-space: nowrap;
  /* OPAQUE rest paint (estate opaque-icons law, 2026-08-12): cells host
   * icon labels, and alpha ink (--kol-fg-meta) compounds where strokes
   * overlap — oq-48 reads identical on the resting surface but stays
   * opaque. Hover/active use fg-emphasis, which is full ink (no alpha). */
  color: var(--kol-oq-48);
  /* THE FILL IS THE UNSELECTED STATE (user ruling 2026-08-15 — "same exactly
   * like before, just swapping stages"). The two treatments are unchanged;
   * which state wears which is inverted. A rest cell is a raised tile you can
   * press; the SELECTED one is the bare dark ground, because it is already
   * where you are. */
  background: var(--kol-surface-secondary);
  transition: color var(--kol-transition-base),
              background-color var(--kol-transition-base);
}

.kol-seg-cell + .kol-seg-cell { border-left: 1px solid var(--kol-oq-08); }

/* ONE STEP, not a leap. Rest is `oq-48` and hover went straight to full ink —
 * a 48 → 100 jump that read as the cell selecting itself under the cursor.
 * `oq-64` is the same single rung every other rest→hover pair in the estate
 * moves by. */
.kol-seg-cell:hover { color: var(--kol-oq-64); }

.kol-seg-cell.is-active {
  background: transparent;
  color: var(--kol-fg-emphasis);
}

/* Reads the quiet ring token so the segmented strip is part of the system-wide
 * focus off switch (FocusRingsInConsumers, 2026-08-15). This is the LAST
 * hardcoded focus colour in the theme — with it, `--kol-focus-ring` +
 * `--kol-focus-ring-quiet` set to transparent turns every ring off, which is
 * the whole point of the pair. Offset stays -2px: the cell's own inset, not
 * the rails' -1px. */
.kol-seg-cell:focus-visible {
  outline: 1px solid var(--kol-focus-ring-quiet);
  outline-offset: -2px;
}

/* Filled variant — the CORRECTED segmented state law (2026-08-12, second
 * ruling — SegmentedFilledStateFix): the SELECTED cell is the DARK FILLED
 * tile (input surface tone) + bright glyph — the dark state IS the selection
 * marker. Unselected cells are QUIET: transparent, dim, hover brightens.
 * NO outline ring, ever. (The 0.36.0 first reading — always-tiled cells +
 * inset ring on selected — was the inversion this block repeals.) */
.kol-seg--filled {
  border: none;
  gap: 1px;
  background: transparent;
}

.kol-seg--filled .kol-seg-cell {
  background: transparent;
  /* the dark tile rounds itself (SegBar reference geometry) */
  border-radius: var(--kol-radius-sm);
}

.kol-seg--filled .kol-seg-cell + .kol-seg-cell { border-left: none; }

.kol-seg--filled .kol-seg-cell.is-active {
  background: var(--kol-surface-secondary);
  color: var(--kol-fg-emphasis);
}

/* Tonal variant (2026-08-12, user ask): same quiet-unselected law, but the
 * clicked cell marks itself on the tertiary tone. */
.kol-seg--tonal .kol-seg-cell.is-active {
  background: var(--kol-surface-tertiary);
  color: var(--kol-fg-emphasis);
}

/* ─────────────────────────────────────────────────────────────────────
 * CardFeatureItem — hover zoom (CardFeatureHoverZoom, 2026-08-12)
 *   The card's VISUAL zooms slightly on card hover — not the card box —
 *   on the same 300ms clock as the existing border transition, so the two
 *   read as one state. Lives here, not in JSX utilities: motion chrome is
 *   DS CSS (the ComponentTailwindSourceTrap law, same day). The card's own
 *   overflow-hidden + radius clip the scale.
 * ──────────────────────────────────────────────────────────────────── */

/* the TEXT-ONLY tile (SectionCardItem with no visual — FoundrySpecimenSections,
 * 2026-08-27): kol-website's .feature-card frame + hover, verbatim — a 1% wash
 * and a 24% frame on hover, 300ms; hover only, never a selected state. */
.kol-card-feature--text {
  transition: background-color 300ms ease, border-color 300ms ease;
}
@media (hover: hover) {
  .kol-card-feature--text:hover {
    background-color: color-mix(in srgb, var(--kol-surface-on-primary) 1%, transparent);
    border-color: color-mix(in srgb, var(--kol-surface-on-primary) 24%, transparent);
  }
}

/* =============================================================================
 * Color-doc widget chrome — ColorRamp · SpectrumGrid
 *
 * Almost everything is Tailwind/inline in the components; only the ONE bit
 * Tailwind can't express against a dynamic grid template lives here: the
 * SpectrumGrid responsive narrowing. Fold into packages/theme (e.g.
 * kol-components-organisms.css) when these graduate from the lobby.
 *
 * How it works: SpectrumGrid declares its template inline with var() fallbacks —
 *   grid-template-columns: var(--kol-sg-label-w, 100px) repeat(N, minmax(0,1fr));
 *   gap: var(--kol-sg-gap, 4px);
 * so it renders correctly with NO stylesheet loaded. On narrow viewports we just
 * re-declare those two custom properties on the grid CLASS; the inline var()
 * reads pick them up (the class provides the value, so a media-query override
 * cascades — there's no competing inline *set* of the same property). The
 * per-cell hex readout is hidden separately via Tailwind `max-[900px]:hidden`.
 * ============================================================================= */

@media (max-width: 900px) {
  .kol-spectrum-grid {
    --kol-sg-label-w: 72px;
    --kol-sg-gap: 2px;
  }
}

/* The elder hardcoded SpectrumGrid block (grid-template-columns: 100px
   repeat(10,...), per-cell padding/hex chrome) lived in kol-framework.css and
   was deleted 2026-07-30 (task 8) rather than moved: it predates the inline
   var() contract above and would fight it by re-declaring the template the
   component sets itself. The two custom-property overrides above are the
   whole stylesheet contract. */


/* SwatchControls chrome — the only bespoke CSS the component needs.
 *
 * The paint chips + sample chip reuse ColorSwatch's `halo` variant for the
 * tuned double-ring, so the ONLY rule left is the NoneMarker's "set to none"
 * affordance: a red diagonal slash on white (a two-stop 45° gradient) over a
 * themed 1px ring. Neither is a Tailwind utility, so it lands here.
 *
 * Append to packages/theme/kol-components-molecules.css. */
.kol-swatch-none-marker {
  background: linear-gradient(
    45deg,
    #fff 0%,
    #fff 42%,
    #DC2626 42%,
    #DC2626 58%,
    #fff 58%,
    #fff 100%
  );
  box-shadow: 0 0 0 1px var(--kol-fg-32);
}

/* ─────────────────────────────────────────────────────────────────────
 * CodeBlock — molecules/CodeBlock.jsx (chrome REPLICATED from the elder
 * reference build, 2026-07-28; moved here from kol-framework.css — theme
 * is chrome's home). Text metrics/colors come from the component's oneDark
 * overrides (14px/1.6); this is frame + chip + copy button only.
 * ──────────────────────────────────────────────────────────────────── */
.kol-codeblock-wrapper {
  margin-block: 1.5rem;
}

/* THE BLOCK. Everything here is the SURFACE — fill, frame, font, wrapping.
 * The two dimensional values (pad and type size) moved OUT to the size classes
 * below (user ruling 2026-08-01: *"it has sizes, ok but its not set, so its
 * just whatever its defaulting to? is that good? instead of saying sm or md?"*).
 * They were unnamed constants sitting in this rule, so nothing could ask for a
 * smaller block and nothing recorded that a choice had been made. */
.kol-codeblock {
  /* verbatim from the reference build's rule (user DevTools, 2026-07-28) */
  background: var(--kol-surface-tertiary);
  border: 1px solid var(--kol-border-default);
  font-family: var(--kol-font-family-mono);
  white-space: pre-wrap;
  overflow-wrap: break-word;
  border-radius: var(--kol-radius-sm);
  line-height: 1.6;
  position: relative;
  overflow-x: auto;
}

/* THE SIZE AXIS — declared, not defaulted. `md` carries exactly the values the
 * base rule used to hide, so nothing changes appearance until a call site asks
 * for `sm`. Padding is the box, type is the ramp; both step together, because a
 * tight box around 14px type is a different defect from small type in a roomy
 * box. Size is INDEPENDENT of `bare`: bare removes the FRAME, size sets the
 * BOX. A bare block still has a size. */
.kol-codeblock--md {
  padding: var(--kol-spacing-4);
  font-size: var(--kol-text-body-02);
}

.kol-codeblock--sm {
  padding: var(--kol-spacing-3);
  font-size: var(--kol-text-body-03);
}

/* the ONE chip — filename if present, else the language. In-flow above the
 * code. VERBATIM from the live reference build (index-Bro9Jnn6.css). */
/* bare — the host owns the frame (PreviewCard Code tab); the border/radius go,
 * the CODE SURFACE stays (2026-07-30: a transparent bare block made the Code
 * tab a different color from every other code surface on the page). */
.kol-codeblock--bare {
  border: none;
  border-radius: 0;
}

.kol-codeblock-filename {
  font-family: var(--kol-font-family-mono);
  font-size: 11px;
  font-weight: 500;
  letter-spacing: 0.05em;
  text-transform: uppercase;
  color: color-mix(in srgb, var(--kol-surface-on-primary) 40%, transparent);
  margin-block-end: 0.75rem;
}

/* THE CONTROL'S LANE (CodeBlockMobileOverflow, kol-website 2026-08-25). The
 * copy button floats in the frame's top-right corner (.kol-frame-control), and
 * the chip row is the lane it sits in — when there IS a chip. A block with no
 * chip (a `text` fence, an unlabelled CMS block) starts its code on the row
 * the control occupies, and once the block is narrower than ~600px the first
 * line runs underneath it (measured: control y 3729–3761, line 1 y 3735–3756).
 * The first line reserves the control's column in that case and only that
 * case — 2rem is the control's box (.kol-copy-btn: 16px glyph + 0.5rem pad)
 * plus its inset; every other line, and every chipped block, is untouched.
 * `.kol-codeblock-line` is stamped per line by CodeBlock.jsx. */
.kol-codeblock:not(:has(.kol-codeblock-filename)) .kol-codeblock-line:first-child {
  padding-inline-end: calc(2rem + var(--kol-spacing-3));
}

/* ─────────────────────────────────────────────────────────────────────
 * FRAME CONTROL — a control that floats in a host frame's corner
 * (user ruling 2026-08-15).
 *
 * Position only. The control IS an existing atom — CopyButton (.kol-copy-btn
 * carries the 32×32 look, promoted 2026-08-09) or IconFrame — and this rule
 * says nothing about how it looks, only where it sits.
 *
 * It exists because the same 12px corner was hand-written three times and
 * shared zero times: `.kol-codeblock-copy` as `0.75rem`, and MediaCard twice
 * inline as `absolute top-3 right-3` (download) and `absolute top-3 left-3`
 * (select). Same value, two spellings, three copies — the 4px-five-ways
 * problem one layer up.
 *
 * NOT `overlay`. That word is ruled below (:704) and means the ONE floating
 * surface; this is in-frame chrome, which is a different thing.
 *
 * The inset spells --kol-spacing-3 (12px) rather than a literal, per the
 * tokens law. Default corner is top-right; the modifiers move it.
 *
 * `.kol-codeblock-copy` is kept as a DEPRECATED ALIAS so existing callers do
 * not break — it is the same rule, not a second one. New hosts use
 * `.kol-frame-control`. */
.kol-frame-control,
.kol-codeblock-copy {
  position: absolute;
  top: var(--kol-spacing-3);
  right: var(--kol-spacing-3);
}

.kol-frame-control--top-left {
  right: auto;
  left: var(--kol-spacing-3);
}

.kol-frame-control--bottom-right {
  top: auto;
  bottom: var(--kol-spacing-3);
}

.kol-frame-control--bottom-left {
  top: auto;
  right: auto;
  bottom: var(--kol-spacing-3);
  left: var(--kol-spacing-3);
}

/* ─────────────────────────────────────────────────────────────────────
 * OVERLAY CHROME — the ONE floating surface (user ruling 2026-08-01).
 *
 * There was none. `.kol-modal` is an unstyled hook, and ShellSearchOverlay
 * hand-wrote its own look at the call site: `rounded-[var(--kol-radius-2xl)]`,
 * `shadow-[0_20px_60px_rgba(0,0,0,0.4)]`, `bg-black/60`, `backdrop-blur-[1px]`
 * — four arbitrary values, none of them referenced anywhere else. The 20px
 * radius in particular appears NOWHERE else in this repo's chrome (which uses
 * `sm` and `full`, counted), which is exactly why it read as foreign.
 *
 * TagModeOverlay meanwhile rendered as a page-like <article> with no overlay
 * chrome at all — one product, two floating surfaces, nothing in common.
 *
 * Both wear these two classes now. `validate:chrome` C3 fails any arbitrary
 * rounded-[…] / shadow-[…] / bg-black/NN / backdrop-blur-[…] in package chrome.
 * ───────────────────────────────────────────────────────────────────── */

/* LOOK ONLY — the call site owns the box. Nine scrims wear it after the
 * 2026-09-03 sweep (overlay-scrim-outliers: Modal · FullscreenOverlay ·
 * ShellDrawer · ShellSearchOverlay · PageLayout's sidenav backdrop · FieldRow ·
 * ShortcutsOverlay · TouchDeviceOverlay · the workshop's shortcuts sheet), and
 * they are `fixed` or `absolute` by their own contract; baking position in
 * would force a wrong one on some. The class owns what was drifting: the tint
 * (60 / 50 / 50 across the first three files; an 8 % inverse wash + blur on
 * the two shell overlays the sweep caught). ParamSheet is untinted on purpose;
 * OverlayGlassPanel and PlaybackBar blur because they are surfaces, not scrims.
 * NO BLUR (user ruling 2026-09-01, OverlayScrimBlur): the 1px blur bought a
 * compositing layer on every overlay open, on a phone, for a separation the
 * 60% tint already carries. */
.kol-overlay-scrim {
  /* ONE SCRIM, ONE TINT (user ruling 2026-09-03: *"makes sense to me they are
   * the same no?"*). Was a raw `#000` at 60 % — a literal in a token system, and
   * 60 is not a ladder stop (…40 · 48 · 64 · 72…). `.kol-shell-drawer-scrim`
   * already spelled it correctly at 48, so the same gesture dimmed to 60 in the
   * site shell and 48 in the app shell. 48 for both: on the ladder, already
   * shipping, and the class was minted to BE one tint. */
  background-color: color-mix(in srgb, var(--kol-color-ab-black) 48%, transparent);
}

.kol-overlay-panel {
  position: relative;
  width: 100%;
  overflow: hidden;
  background-color: var(--kol-surface-primary);
  /* NO BORDER, NO SHADOW (user 2026-08-01). Both were tried and both were
   * wrong: the shadow was another product's floating-card idiom, and the
   * hairline left an empty palette reading as a bordered empty box. The SCRIM
   * is the separation — a 60% tint is already a stronger edge than an
   * 8%-opacity line. */
  /* `sm` — the radius this repo's chrome actually uses. */
  border-radius: var(--kol-radius-sm);
  /* NO SHADOW (user ruling 2026-08-01). The border and the scrim already
   * separate the panel; a drop-shadow on top is the floating-card idiom from
   * another product. --kol-shadow-overlay was added for this an hour earlier
   * and is deleted with it: it solved the wrong problem. */
}

/* ── Section — .kol-section-* ─────────────────────────────────────────────
 * Between-siblings rhythm for stacked inspector sections (InspectorSectionRhythm,
 * filed from kol-fxr 2026-08-15).
 *
 * Section's own anatomy — the label and the `gap-2` inner stack — stays as
 * Tailwind utilities in Section.jsx. Only the BETWEEN-siblings rule lives here,
 * because `+` cannot be expressed as a utility class: every consumer stacking
 * sections in a rail was therefore forced to invent a hook class and retype
 * this rule. kol-fxr's `.kol-params-section` block is the evidence, and it
 * dies on adopt.
 *
 * The rule is on the ADJACENT PAIR, not on the element, so the first section
 * in a stack never carries a stray top border and no consumer needs
 * `:not(:first-child)`.
 *
 * DENSITY WAS NOT ADDED. The ticket asked for a `density` prop wanting "a
 * tighter inner gap than gap-2" — but the consumer's own override is
 * `gap: 0.5rem`, which IS `gap-2`. The rule it maintains locally is a no-op
 * against Section's shipped default, so there is no second density to name
 * until a consumer produces a value that actually differs. */
.kol-section--divided + .kol-section--divided {
  border-top: 1px solid var(--kol-fg-08);
  /* 20px below the rule; the 20px above falls out of the rail body's own
   * gap-5, so a symmetric space–divider–space lands without this file
   * knowing anything about the rail.
   * px, not --kol-spacing-5 — that token is rem, and spacing here is px
   * (.kol-tag--sm, .kol-sidenav-group, .kol-control-* all set px directly). */
  padding-top: 20px;
}

/* ─────────────────────────────────────────────────────────────────────
 * Content family hover — molecules/ContentCard.jsx · ContentRow.jsx
 *
 * ONE rule, driven by a per-variant custom property the component sets
 * (`--kol-content-hover-bg`). The alternative was a hover utility per
 * variant in JSX, which Tailwind cannot generate from package source
 * anyway (the SegmentedToggle lesson — component chrome belongs in
 * kol-theme). A variant with no hover simply never sets the property,
 * and the rule resolves to nothing.
 *
 * The fill is an oq-* step per 05-control-chrome.md's state model; the
 * value itself is chosen in ContentCard/ContentRow's HOVER table.
 * ───────────────────────────────────────────────────────────────────── */
@media (hover: hover) {
  .kol-content-hover:hover {
    background-color: var(--kol-content-hover-bg);
  }
  /* border is its OWN class, not a fallback chain off the bg one: a variant
   * that steps its fill but not its frame (catalog) and one that steps its
   * frame but not its fill (work) are both real, and an undefined var in a
   * border-color declaration resolves to `unset` — which inherits, and quietly
   * repaints a border nobody asked to move. */
  .kol-content-hover-frame:hover {
    border-color: var(--kol-content-hover-border);
  }
}

/* ─────────────────────────────────────────────────────────────────────
 * ContentRow — the responsive step
 *
 * The row publishes its box as custom properties and reads them here, so
 * the md: step is ONE media query instead of per-variant Tailwind variants
 * that Tailwind cannot generate from package source anyway. A variant that
 * publishes no `-md` value falls back to its base and never steps.
 * ───────────────────────────────────────────────────────────────────── */
.kol-row {
  gap: var(--kol-row-gap);
  padding: var(--kol-row-pad);
  min-height: var(--kol-row-min-h);
  background: var(--kol-row-bg);
  /* the FALLBACK is not optional. A variant with no frame publishes no
   * `--kol-row-border`, and `border-color: var(--undefined)` is invalid at
   * computed-value time — it resolves to `unset`, which INHERITS, which is
   * currentColor, which is the text colour. That is how `default`'s hairline
   * divider rendered as a solid white rule. */
  border-color: var(--kol-row-border, transparent);
  transition: background-color 300ms var(--kol-ease-house),
              border-color     300ms var(--kol-ease-house);
}

/* A FIXED RUNG, not a floor (ContentRowRosterVariant, kol-chess 2026-08-31).
 * Every other row in the family follows its content; `roster` is a grid of
 * pick-targets, and a target whose height drifts with its own copy reads as
 * broken — measured at 34 · 40 · 50 · 58 across four passes of one component,
 * where a single long meta line pushed a tile out of line with its neighbours.
 * Both properties: `height` alone loses to the `min-height` above it whenever
 * the content is taller, which is the case this exists to stop. */
.kol-row--fixed {
  height: var(--kol-row-h);
  min-height: var(--kol-row-h);
}

/* fixed BELOW the md container only, the floor above it
 * (ContentRowShowcaseImageDrivenHeight, 2026-09-01): the showcase row's height
 * is a function of the image — thumb + padding — and content fits inside it.
 * `overflow: hidden` is deliberate here, not the silent-loss case: the rung is
 * the design, the text truncates by ramp, and the tags line goes single-row
 * below md so the cut lands on the chips. Released at md, where the floor in
 * the container query below resumes. */
.kol-row--fixed-sm {
  height: var(--kol-row-h-sm);
  min-height: var(--kol-row-h-sm);
  overflow: hidden;
}
@container (min-width: 768px) {
  .kol-row--fixed-sm {
    height: auto;
    min-height: var(--kol-row-min-h-md, var(--kol-row-min-h));
    overflow: visible;
  }
}

/* the divider is its OWN longhand, declared after the shorthand above so it
 * wins on order — `default`'s bottom rule is not the same thing as a framed
 * variant's border, and one property cannot carry both. */
.kol-row--divided {
  border-bottom: 1px solid var(--kol-fg-08);
}
/* `specs` — the row's trailing label/value column (RowVariantNamesAndSpecs,
 * 2026-08-29). It rides every horizontal variant because year · material ·
 * edition is a CONTENT difference, not a geometry one; a seventh page-named
 * variant for it would repeat the mistake that ticket opens with.
 * Type is declared HERE, not as `kol-mono-12` on the element — ARCHITECTURE §5:
 * a utility class on the markup ties at specificity and loses to whichever
 * sheet loads last. Values match the family's meta voice (kol-mono-12). */
.kol-row-specs {
  display: flex;
  flex-direction: column;
  gap: var(--kol-spacing-1);
  margin: 0;
}
.kol-row-specs > div {
  display: flex;
  justify-content: space-between;
  gap: var(--kol-spacing-3);
}
.kol-row-specs dt,
.kol-row-specs dd {
  margin: 0;
  font-family: var(--kol-font-family-mono);
  font-size: 12px;
  line-height: 16px;
  font-weight: 400;
}
.kol-row-specs dt {
  color: var(--kol-fg-meta);
}
.kol-row-specs dd {
  color: var(--kol-fg-emphasis);
  text-align: right;
}
/* THE THUMB IS A FIXED BOX (ContentRowsAndPrintCard, kol-website 2026-08-27 —
 * user: "the image should not control height, image should fit the row image
 * placeholder"). The box is `--kol-row-thumb` wide, sits at the top of the row,
 * and whatever is inside fills it (ContentMedia object-covers). Row height =
 * max(thumb, text) — an image's intrinsic height never sizes a row again.
 *
 * SQUARE IS THE DEFAULT, NOT THE LAW (RowThumbRatioDead, kol-website
 * 2026-08-30). `aspect-ratio: 1 / 1` was hard-coded here, and ContentRow passed
 * `ratio={null}` to ContentMedia, so the component's own documented `ratio`
 * prop reached nothing — /prints set `1 / 1.41421` for its A-series posters and
 * the render did not move. Now a variable with `1 / 1` as the fallback: every
 * existing consumer is pixel-identical, and the prop works. */
.kol-row > .kol-row-thumb {
  width: var(--kol-row-thumb);
  aspect-ratio: var(--kol-row-thumb-ratio, 1 / 1);
  align-self: flex-start;
}
.kol-row > .kol-row-thumb > * {
  height: 100%;
  width: 100%;
}
/* `thumb="fill"` (WorkListingRowsAndFilters, 2026-08-27): the row publishes
 * `--kol-row-thumb` as the rung minus its vertical padding — `/work`: 136 in a
 * 168 row at 16 padding.
 *
 * RULED 2026-08-30 (the ask's third box): under `fill`, that number is the
 * HEIGHT, not the width. "Fill" means fill the row's content height — that is
 * the whole promise of the value — so a non-square ratio has to widen the
 * thumb, never grow the row past its rung. Keeping it as the width would make
 * an A-series fill thumb 1.41× taller than the row it is filling, which is the
 * one thing the 08-27 ruling exists to prevent. Square is unaffected: at
 * `1 / 1` width and height are the same number either way. */
.kol-row > .kol-row-thumb.is-fill {
  width: auto;
  height: var(--kol-row-thumb);
}
/* WHICH AXIS PAYS for a non-square ratio (RowRungAndFillThumb, kol-website
 * 2026-08-30). The 08-30 ruling above made the rung the HEIGHT unconditionally,
 * so an A-series thumb on a 168 row went 136×136 → 96×136: the ratio came out
 * of the width, and the width is what sets a listing's rhythm against the rest
 * of the page. That was a ruling where a choice belonged — `ratioAxis="height"`
 * holds the width at the rung and lets the thumb grow taller instead
 * (136×192). Default is unchanged, so nothing that exists moves. */
.kol-row > .kol-row-thumb.is-fill.pays-height {
  width: var(--kol-row-thumb);
  height: auto;
}

/* CONTAINER query, not a viewport one (2026-08-15). The row steps on the width
 * of the WALL it sits in, for exactly the reason the card-wall law gives: the
 * shell rails eat width that viewport breakpoints cannot see, so a row inside a
 * 700px panel on a 1600px screen was taking the desktop step and overflowing.
 * It also means the step is VISIBLE in a resizable panel instead of only when
 * the whole browser is dragged.
 *
 * 768 is still the ruled `md` rung (08-breakpoints/01-values.md) — the number
 * did not change, only what it measures. A row with no container ancestor falls
 * back to its base values, which is the correct nothing-happens. */
@container (min-width: 768px) {
  .kol-row {
    gap: var(--kol-row-gap-md, var(--kol-row-gap));
    padding: var(--kol-row-pad-md, var(--kol-row-pad));
    min-height: var(--kol-row-min-h-md, var(--kol-row-min-h));
  }
  .kol-row > .kol-row-thumb {
    width: var(--kol-row-thumb-md, var(--kol-row-thumb));
  }
}

/* ─────────────────────────────────────────────────────────────────────
 * ContentCard — the work DRAWER
 *
 * A caption plate that rises over the bottom of the artwork on hover.
 * The card is image-only at rest by design: a work shelf is a wall of
 * images, and the caption is the reveal.
 *
 * Absolute, so it never adds height to the card and never reflows the
 * shelf. Opacity only — a translucent wash over a photograph is exactly
 * what the oq-* tier exists to avoid, but this plate is OPAQUE
 * (surface-inverse) and it is the plate's PRESENCE that animates, not
 * its transparency over the image.
 * ───────────────────────────────────────────────────────────────────── */
.kol-card-drawer {
  position: absolute;
  inset-inline: 0;
  bottom: 0;
  z-index: 10;
  opacity: 0;
  transition: opacity 300ms var(--kol-ease-house);
}

@media (hover: hover) {
  .group:hover > .kol-card-drawer {
    opacity: 1;
  }
}

/* touch has no hover — the caption must not be unreachable there */
@media (hover: none) {
  .kol-card-drawer {
    opacity: 1;
  }
}

/* The card's text plate steps its padding at md where the variant declares
 * one (work: 16 → 24, matching the shipped drawer's `p-4 md:p-6`). Same
 * custom-property mechanism as .kol-row — Tailwind cannot generate an
 * md: variant from package source, and these are per-variant data. */
/* the ruled `md` rung, same as .kol-row above — and a container query for the
 * same reason: a card in a narrow wall must not take the wide plate padding
 * because the BROWSER happens to be wide. */
@container (min-width: 768px) {
  .kol-card-plate {
    padding: var(--kol-plate-pad-md, var(--kol-plate-pad));
  }
}

/* The card's rest colours, for the same reason as .kol-row — an inline
 * background/borderColor outranks .kol-content-hover:hover, so the step
 * silently never rendered.
 * `background-clip: padding-box` (CardCornerLeak, kol-monitor 2026-08-28,
 * verified on a pixel crop): the fill and the border are both translucent
 * ink, and with the default border-box the fill runs UNDER the 1px border —
 * an opaque media child clips to the inner radius, the browser antialiases
 * the clip path and the border path separately, and a hairline of the
 * lighter fill bleeds through on the arc, bright only on the curve. */
.kol-card {
  /* NOT toned. A wrapper tone reaches the CONTROLS on a page and stops at the
   * card plate (user ruling 2026-09-03: "no just controls") — the plate is the
   * content-card system's box per variant. */
  background: var(--kol-card-bg);
  background-clip: padding-box;
  border-color: var(--kol-card-border);
  transition: background-color 300ms var(--kol-ease-house),
              border-color     300ms var(--kol-ease-house);
}


/* ─────────────────────────────────────────────────────────────────────
 * The house expand — molecules/SearchInput.jsx · organisms/ContentFilters.jsx
 *
 * A collapsed control that opens to its field: the search glyph widening
 * into an input. Two components hand-wrote the same three declarations
 * inline (`width 600ms`, `background 400ms`, `opacity 300ms`, all on the
 * house curve), which is how the curve came to be hardcoded in seven
 * places to begin with.
 *
 * Durations are STAGGERED on purpose and that is the whole character of
 * the move: the box travels longest (600), its fill settles sooner (400),
 * and the content inside arrives last and quickest (300) so it does not
 * smear across the widening box.
 *
 * The width VALUE stays inline — it is per-instance data (a consumer's
 * expanded width), not chrome. Only the motion lives here.
 * ───────────────────────────────────────────────────────────────────── */
.kol-expand {
  transition: width      600ms var(--kol-ease-house),
              background 400ms var(--kol-ease-house),
              border-color 400ms var(--kol-ease-house);
}

/* the content that rides inside an expanding box */
.kol-expand-content {
  transition: opacity 300ms var(--kol-ease-house);
}


/* Markdown is BOUNDED where it renders (SettingsPanelApproved, kol-r2b2
 * 2026-08-27): in the 320px column preview a document scaled to the frame; in
 * the overlay a readable sheet, not full-bleed over the page. */
/* THE DOCUMENT PAGE — one plate for every document (DocPageAndKindShowcase,
 * kol-r2b2 2026-08-27, user rulings): markdown · text · code · JSON · YAML on
 * `.kol-doc-page`. WHERE it sits decides its presentation:
 *   base       the plate — fg-04, radius sm, padding 24 (a demo, a page)
 *   overlay    an A-series page: 85vh tall, 85vh / √2 wide, never wider than the
 *              viewport minus the arrow gutters (10rem), scrolls inside
 *   column     the same document zoomed 0.5 on the frame (no plate); the
 *              children reset `zoom: 1` so it never compounds
 * The code block INSIDE the page is transparent, borderless and full width —
 * "yaml should follow json in the way it scales": it hugged short lines and sat
 * as a second, darker plate. Prose is bounded by the page, not by its own
 * measure; tables and pre wrap inside it. */
.kol-doc-page {
  background: var(--kol-fg-04);
  border-radius: var(--kol-radius-sm);
  padding: 24px;
  min-width: 0;
  overflow-wrap: anywhere;
}
.kol-doc-page .kol-codeblock-wrapper { margin: 0; }
.kol-doc-page .kol-codeblock { background: transparent; border: 0; width: 100%; }
.kol-doc-page .kol-prose { max-width: none; margin: 0; padding: 0; }
.kol-doc-page .kol-prose table { max-width: 100%; }
.kol-doc-page .kol-prose td,
.kol-doc-page .kol-prose th { overflow-wrap: anywhere; word-break: break-word; }
.kol-doc-page .kol-prose pre { white-space: pre-wrap; overflow-wrap: anywhere; }
/* 3:5 IS THE TALLEST A DOCUMENT PAGE MAY BE (BrowsePageRulingsAndSeams,
 * kol-r2b2 2026-09-02; user 2026-08-28: "this is not an approved ratio"). The
 * overlay page was A-series (1:√2) and the column preview had NO bound at all,
 * so the FILE decided the height — a 7 KB JSON drew a pane taller than the
 * browser. 3:5 is the portrait rung on the export-specs ladder below 4:5, and
 * the floor before a page reads as a 9:16 strip. Both presentations take it:
 * content scrolls inside the box, the box never grows to the file. */
.kol-overlay .kol-doc-page {
  aspect-ratio: 3 / 5;
  height: 85vh;
  width: auto;
  max-width: calc(100vw - 10rem);
  overflow-y: auto;
  overflow-x: hidden;
  margin-inline: auto;
}
.kol-column-browser-preview .kol-doc-page {
  aspect-ratio: 3 / 5;
  height: auto;
  overflow: auto;
  zoom: 0.5;
  background: transparent;
  border-radius: 0;
}
.kol-column-browser-preview .kol-doc-page :is(.kol-prose, .kol-codeblock-wrapper, .kol-doc-frontmatter) { zoom: 1; }
/* documents start at the top of the column frame with a small inset, and scroll */
.kol-column-browser-preview > div:first-child:has(.kol-doc-page) { align-items: flex-start; justify-content: flex-start; overflow: auto; }

/* THE QUICKTIME BAR (PlaybackBarAndAudioSheet, kol-r2b2 2026-08-27 — ruled
 * against the reference). White glyphs on the frosted strip whatever the theme:
 * the ghost IconFrame paints --kol-oq-48, a theme colour, and the bar sits on
 * media, not on the theme. The scrubber is a native range — a 2px track at 40 %
 * white, a tall 4 × 28 white pill for the knob — pseudo-elements, so CSS. */
.kol-playback-bar .kol-icon-frame { color: var(--kol-color-ab-white); opacity: 0.8; }
.kol-playback-bar .kol-icon-frame:hover,
.kol-playback-bar .kol-icon-frame:focus-visible { opacity: 1; }
.kol-playback-scrub {
  appearance: none;
  -webkit-appearance: none;
  height: 28px;
  background: transparent;
  outline: none;
  cursor: pointer;
}
.kol-playback-scrub::-webkit-slider-runnable-track { height: 2px; border-radius: 1px; background: color-mix(in srgb, var(--kol-color-ab-white) 40%, transparent); }
.kol-playback-scrub::-webkit-slider-thumb { -webkit-appearance: none; width: 4px; height: 28px; margin-top: -13px; border-radius: 2px; background: var(--kol-color-ab-white); }
.kol-playback-scrub::-moz-range-track { height: 2px; border-radius: 1px; background: color-mix(in srgb, var(--kol-color-ab-white) 40%, transparent); }
.kol-playback-scrub::-moz-range-thumb { width: 4px; height: 28px; border: 0; border-radius: 2px; background: var(--kol-color-ab-white); }

/* ColumnBrowser rows — ONE SELECTED STATE (ColumnBrowserSeams, kol-r2b2
 * 2026-08-28; user: "ONLY one selected state can exist, not TWO"). The row's
 * fill lives here, keyed on its state classes, not on a `bg-fg-04` utility a
 * consumer had to hang rules off. Hover and the keyboard cursor are the fill;
 * a selected row is the TRAIL at half strength unless its column is the
 * deepest one holding a selection — then it is the selection, full strength.
 * `:has(~ …)` says what a positional selector cannot: "no later column has a
 * selected row". */
/* ONE FILL ON SCREEN, AND IT MEANS SELECTED (BrowsePageRulingsAndSeams, kol-r2b2
 * 2026-09-02, user ruling 2026-08-28). Hover and the bare keyboard cursor now
 * paint NOTHING. With `autoFocus` on there is always a cursor row, so the
 * 08-28 fill sat on the list permanently and moved with every click — the hover
 * fill the user removed the day before, back under another name, and at fg-04 it
 * drowned the selection at fg-02.
 *
 * A selected row that is ALSO the cursor keeps its fill, and needs no rule of
 * its own: `:hover` and `.is-cursor` are (0,2,0), `.is-selected` is (0,2,0) and
 * comes LATER, so source order hands the tie to the selection. The consumer
 * needed explicit `.is-selected.is-cursor` selectors only because its override
 * sat in a later sheet; in one file the order is the mechanism. Do not reorder.
 *
 * The VALUES are unchanged and were never wrong — the trail at fg-02, the
 * deepest column's selection at fg-04. This is a precedence fix, not a palette
 * one; `autoFocus` and the three-state fill shipped together and have not been
 * correct together since. */
.kol-column-browser-row:hover,
.kol-column-browser-row.is-cursor { background-color: transparent; }
.kol-column-browser-row.is-selected { background-color: var(--kol-fg-02); }
.kol-column-browser-column:not(:has(~ .kol-column-browser-column .is-selected)) > .is-selected { background-color: var(--kol-fg-04); }

/* THE ROW IS A PILL (same ticket, user ruling 2026-08-27: "dont make selected
 * state move the layout"). A constant 4px inset on EVERY row, selected or not,
 * so the fill appears inside a gutter that was already there instead of the row
 * changing size; the column pads to match, so the first and last rows sit off
 * the frame by the same 4. Dividers are gone from the JSX — ColumnBrowser's Row
 * no longer emits `border-b`, because a hairline under a rounded fill draws the
 * box the pill is not. `.kol-column-browser-preview` is deliberately NOT in the
 * padding rule: it has no rows, and its own `p-4` is a Tailwind utility that
 * would out-rank this layer anyway. */
.kol-column-browser-row { margin-inline: 4px; }
.kol-column-browser-column { padding-block: 4px; }
.kol-column-browser-row.is-selected,
.kol-column-browser-row.is-cursor { border-radius: var(--kol-radius-sm); }

/* ColumnBrowser resize handles (ColumnBrowserResize, kol-r2b2 2026-08-27 —
 * Finder's edge handles, user ruling "that should be a set in ds"; native CSS
 * `resize:` rejected). The column border already there is the visual; the
 * strip is the HIT AREA — 8px, invisible at rest, a centred pill on hover and
 * while the pointer is captured (was an fg-08 wash until 0.79.0). The x handle is a flex sibling pulled back over the
 * column's right edge (net zero width); the y handle rides the root's bottom
 * edge, above the horizontal scroll box so it spans the visible width. */
.kol-column-browser-resize-x {
  position: relative;
  z-index: 1;
  flex: none;
  align-self: stretch;
  width: 8px;
  margin-left: -8px;
  cursor: col-resize;
  touch-action: none;
}
.kol-column-browser-resize-y {
  position: absolute;
  left: 0;
  right: 0;
  bottom: 0;
  z-index: 1;
  height: 8px;
  cursor: row-resize;
  touch-action: none;
}
/* THE GRAB PILL — ON THE LINE, FOLLOWING THE POINTER (BrowsePageRulingsAndSeams,
 * kol-r2b2 2026-09-02, user ruling; supersedes the "pointer-following was built
 * and rejected" note that stood here from ColumnBrowserChromeCorrections
 * 2026-08-28).
 *
 * The rejection was already contradicted by the DS itself: the rail's grab
 * (`kol-animation.css` § THE GRAB PILL, RailFlatGrabOpen, kol-mirror 2026-08-28
 * — *"make it like it is in kol-r2b2, it has animation and gsap"*) shipped the
 * pointer-follow, the 0.125 × 4.5rem geometry and the slow proximity fade the
 * SAME DAY, and its own comment noted the two handles were knowingly different.
 * One gesture, two shapes, is the ruling now. `useGrabEdge` drives both.
 *
 * THESE HANDLES ARE NOT THE SIDENAV'S, and that is the part to keep straight:
 *
 *   ACROSS  the rail's strip STRADDLES its line, so a strip-centred pill IS the
 *           line. These strips lie INSIDE the border they grab — the x strip is
 *           pulled back over the column's right edge (`margin-left: -8px`), the
 *           y strip sits on the root's inner bottom edge — so a strip-centred
 *           pill is always ~4px short of it. The FAR edge plus half the 1px
 *           border is the line: `calc(100% + 0.5px)`.
 *   AXES    the rail has one vertical edge; there are two here, and the pill on
 *           each travels the other way from what it resizes. The x handle reads
 *           `--kol-rail-grab-y`, the y handle `--kol-rail-grab-x`.
 *   REST    hidden, not dim — N handles standing at rest is noise where the
 *           SideNav has one. The rail's pill rests visible; these do not.
 *
 * Shared with the rail: the fine/long geometry and the 1800ms fade with its
 * 400ms rest delay and 40ms engaged delay. NOT shared, on the user's ruling
 * 2026-09-02 (*"its a bit different, its not like the sidenav"*): the CURVE is
 * kol-r2b2's ease-out, and the travel is `GRAB_COLUMN` in kol-component's
 * motion.js — a 2.8s chase on a 30px retarget, so the pill tracks the pointer
 * down the edge rather than landing and holding on the rail's 90px dwell. The
 * rail's own symmetric in-out was ruled for the rail, on a pill that rests
 * VISIBLE and only brightens; these are hidden at rest and the whole fade is
 * the reveal. `clamp()` holds the pill half its own length from each end. */
.kol-column-browser-resize-x::before,
.kol-column-browser-resize-y::before {
  content: '';
  position: absolute;
  translate: -50% -50%;
  border-radius: var(--kol-radius-full);
  background: var(--kol-fg-64);
  opacity: 0;
  transition: opacity 1800ms cubic-bezier(0.22, 1, 0.36, 1) 400ms, background-color 500ms ease;
}
.kol-column-browser-resize-x::before {
  width: 0.125rem;
  height: 4.5rem;
  left: calc(100% + 0.5px);
  top: clamp(2.25rem, var(--kol-rail-grab-y, 50%), calc(100% - 2.25rem));
}
.kol-column-browser-resize-y::before {
  width: 4.5rem;
  height: 0.125rem;
  top: calc(100% + 0.5px);
  left: clamp(2.25rem, var(--kol-rail-grab-x, 50%), calc(100% - 2.25rem));
}
/* `is-near` = the pointer is within GRAB.near of THIS handle's line (JS,
 * hysteresis at GRAB.sleep). The pill shows before you are on the 8px strip, so
 * the grab is discoverable instead of hidden. */
.kol-column-browser-resize-x.is-near::before,
.kol-column-browser-resize-x.is-dragging::before,
.kol-column-browser-resize-y.is-near::before,
.kol-column-browser-resize-y.is-dragging::before {
  opacity: 1;
  transition-delay: 40ms;
}
.kol-column-browser-resize-x.is-dragging::before,
.kol-column-browser-resize-y.is-dragging::before { background: var(--kol-fg-96); }

.kol-overlay .kol-prose {
  max-width: var(--kol-content-measure);
  margin-inline: auto;
  padding: 24px;
}

/* ─────────────────────────────────────────────────────────────────────
 * TONE — sunken (ControlToneSunken, kol-website 2026-08-28; was "inverse",
 * ControlToneInverse 2026-08-27). User, on brand's /icons over a `pageWash`:
 * "a flipped version of this color scheme, where the darker is background and
 * grey is the active … it would fit better on the light grey" — and then, on
 * the name: the control does not INVERT anything, it sits BELOW the plane it
 * is on — `sunken`. ("inverse" already meant four things: `tone` here,
 * `variant` on Tag/Pill, `theme` on Section, a boolean on Divider.) On a
 * washed plane the default grey well reads as a second plate, so the set takes
 * ONE tone: `tone="sunken"` (`inverse` aliased) on ViewToggle (icon) ·
 * Dropdown · Input · SearchInput · Button · IconFrame · ThemeToggle stamps
 * `.kol-tone-sunken`; the rules live HERE, after every default they out-rank,
 * so a consumer never restates them (brand hand-wrote the fill and GUESSED
 * the ink for its two icon buttons — that fork is what widening the set ends).
 *
 * THE SET: well `fg-inverse-96` (user ruling 2026-08-28, swapped from
 * `fg-ab-24` — measured on the dark theme, where the inverse tier is the
 * one that DARKENS: on the #121215 page inverse-96 lands ~14.2 against
 * absolute-24's 13.7, half a level apart. Unlike absolute it follows the
 * theme, so the light-theme well flips to the near-white inverse anchor) ·
 * active chip `fg-08` (user ruling 2026-08-28, down from the 0.78.1 fg-16) ·
 * inactive hover ab-white @ 8% (a dark well wants a LIGHTER hover) ·
 * ink `fg-96`. `.kol-tone-inverse` stays selected for any class
 * hand-stamped on the old name.
 * ───────────────────────────────────────────────────────────────────── */
/* ⚠️ `SunkenWellEatenByPageWash` IS NOT SOLVED HERE — see the ticket.
 * On 2026-08-30 these rules briefly painted `--kol-shell-page-wash` as a
 * background layer so the well would track a washed page. That was the wrong
 * layer (user: *"we are talking about components, wash affects background"*):
 * a control has no business reproducing a page-level film, and portalling
 * proved it — `.kol-dd-panel` renders at document.body, could not inherit the
 * property, and drew a different colour from its own trigger. Reverted.
 * The collision is a PAGE/token question and belongs there.
 *
 * THE SUNKEN TONE IS `surface-tertiary`, NOT `oq-inverse-96` (measured on
 * kol-fxr's /settings, 2026-08-30). The whole set read `--kol-oq-inverse-96`,
 * which is `color-mix(#0e0e11 96%, #fcfbf8)` — four percent of near-WHITE mixed
 * in. On the dark theme that lands at luminance **23.7 against an 18.2 page**:
 * a tone named "sunken" was rendering RAISED, and every control wearing it
 * (dropdown trigger, view toggle, icon frames, theme toggle) sat as its own
 * pale box on the page. That is the "multiple backgrounds" the user has
 * reported repeatedly and I have not fixed.
 *
 * The diagnosis was already written into this file, twenty lines down, in the
 * comment above `.kol-dd-panel` — it works out the 23.5-vs-18 arithmetic and
 * names `surface-tertiary` as the answer. It was never applied to the rules
 * beneath it. Written down is not fixed.
 *
 * `surface-tertiary` is `#0e0e11` in dark (14.2, genuinely below the page) and
 * `#ffffff` in light, so it is theme-correct on both sides where a mix toward
 * a fixed near-white could only ever be right on one. */
/* ═══════════════════════════════════════════════════════════════════════
 * TONE — the ground axis (tone-is-the-ground-axis, kol-client-olina 2026-09-03).
 *
 * User, on fxr's /settings: "it would be best to not have to set every
 * component but rather get a set with it already set" — and, reading Button's
 * variants: "I thought you would set these colors as variants … that is 6
 * tones right?". Until 0.134.0 `toneClass` returned a class for `sunken` and
 * '' for everything else: `tone` had ONE value, and the real ground vocabulary
 * — five of Button's eight variants — lived on Button alone, so Dropdown,
 * `.kol-control` and `.kol-dd-panel` each re-invented it with paints that
 * drifted (`primary` painting `surface-secondary`; the panel's outline
 * hardcoding `surface-primary`, so a dropdown opened over any other ground
 * changed the colour under it).
 *
 * SIX TONES, ONE MECHANISM. A tone is a bundle of `--kol-tone-*` custom
 * properties — rest, hover, press, pressed, and what a FLOATING surface of
 * that tone paints — and every control reads its paint from those properties
 * with its own old literal as the fallback:
 *
 *   .kol-btn / .kol-dd-trigger   fallback primary
 *   .kol-control                 fallback filled (= primary)
 *   .kol-icon-frame              fallback secondary
 *   .kol-dd-panel                fallback primary
 *   .kol-view-toggle well · .kol-expand--open · .kol-theme-toggle-*   their own
 *
 * Custom properties INHERIT, which is the whole point (ticket ask 3): one
 * `.kol-tone-grey` on a wrapper tones every control inside it, because a
 * control with no tone of its own reads the inherited values. A control that
 * IS handed a variant or tone sets the properties on itself, and an element's
 * own declaration beats inheritance — so the per-component prop stays the
 * exception, exactly as asked. The ground variants (`primary` · `secondary` ·
 * `outline` · `ghost` · `grey` on Button, `filled` / `outline` on the control
 * shell, the panel and icon-frame variants) are ALIASES of the tones — the
 * same bundle under `:where()`, specificity 0, so an explicit `tone` on the
 * same element wins regardless of source order. `danger` and `accent` are
 * semantic, not grounds: they stay variants and carry their own bundle. `nav`
 * is ghost's chrome rung — the ink one step brighter, plus aria-current — and
 * stays a variant (the ticket read it as ghost; the CSS says oq-80 against
 * ghost's oq-48, so folding it would have dimmed every close button and rail
 * row in the estate).
 *
 * THE FLOATING SURFACE PAINTS THE GROUND (ask 2). `--kol-tone-panel-bg` is
 * what a dropdown panel of that tone paints: primary/grey/sunken continue
 * their fill; outline and ghost paint `--kol-tone-ground` — the page's own
 * background, `surface-primary` unless the page says otherwise — so opening
 * a dropdown changes nothing under it. A page on another ground sets
 * `--kol-tone-ground` once at its root. (The panel is PORTALLED, so Dropdown
 * copies the trigger's resolved properties onto it on open — see Dropdown.jsx.)
 *
 * A bundle sets EVERY property, including the ones it leaves at default, so a
 * control with its own tone inside a differently-toned wrapper inherits
 * nothing from it — not even a hover gradient. (Weight is the variant's own,
 * never the tone's: a ground does not re-weigh a label.)
 *
 * `default` is not a tone: it is the ABSENCE of one — inherit the wrapper's,
 * else the family's fallback. (An alias of `primary` set on the element would
 * block the inheritance that ask 3 exists for.) `inverse` stays sunken's alias.
 * SEVEN since 0.138.0: `secondary` paints the page surface and `inverted` is
 * the text-colour fill the word `secondary` used to mean (tone-secondary-is-
 * inverse, the line of the user's ruling the first ticket did not carry).
 *
 * The rules live HERE, after every default they out-rank. Sunken's own
 * history — the well, its three-rung ladder, why it was raised until
 * 2026-08-30 — is the comment above; nothing about its values changed.
 * ═══════════════════════════════════════════════════════════════════════ */
.kol-tone-primary,
:where(.kol-btn-primary, .kol-control--filled, .kol-icon-frame-primary, .kol-dd-panel--primary) {
  --kol-tone-bg: var(--kol-surface-secondary);
  --kol-tone-image: none;
  --kol-tone-fg: var(--kol-surface-on-primary);
  --kol-tone-border: transparent;
  /* HOVER GOES DEEPER, NOT GREYER (editor-chrome-review 15, kol-fxr 2026-09-03
   * — user: *"this button tone primary, has werid hover state … should not grey
   * - should go deeper"*). It was `oq-08`, and `oq-*` flips toward the INK, so
   * in dark it LIGHTENED toward grey. The `ab` family is the one that flips
   * toward the GROUND in both themes — `oq-ab-96` is the well, 245 under a 250
   * page and 10 under an 18 page — so the step is measured DOWN from the page.
   *
   * ⚠ The rungs are read from the ground: 96 is four off it, 88 twelve. A
   * literal `oq-ab-04` is the FAR pole — near-black in light, near-WHITE in
   * dark — so the low numbers are the opposite of deeper and must not be used
   * for this.
   *
   * ONE STEP IS A TWO-STATE BUDGET (his ruling, and it generalises past this
   * button): *"whatever it is cut it in half, so if 8 then 4 … bc there are
   * hover and selected state"*. Hover takes the shallow rung and active the
   * next, so the two step instead of colliding. There is no 92 on this ladder,
   * so it is 96 → 88 rather than an exact halving. */
  --kol-tone-hover-bg: var(--kol-oq-ab-96);
  --kol-tone-hover-image: none;
  --kol-tone-hover-fg: var(--kol-surface-on-primary);
  --kol-tone-hover-border: transparent;
  --kol-tone-active-bg: var(--kol-oq-ab-88);
  --kol-tone-active-image: none;
  --kol-tone-pressed-bg: var(--kol-surface-on-primary);
  --kol-tone-pressed-image: none;
  --kol-tone-pressed-fg: var(--kol-surface-primary);
  --kol-tone-panel-bg: var(--kol-surface-secondary);
  --kol-tone-panel-border-w: 0;
  --kol-tone-panel-border: transparent;
}
/* `secondary` PAINTS THE PAGE SURFACE (tone-secondary-is-inverse, kol-client-olina
 * 2026-09-03; user: "I told you I wanted primary surface as a tone … that tone
 * should be called secondary. What is currently secondary should be called
 * inverted."). 0.134.0 lifted the name from Button's `secondary` VARIANT, which
 * was already an inverse, and the page's own colour had no tone at all. The
 * variant keeps its pixels — it aliases `inverted` below — only the tone word
 * moved. Hover / press are primary's rungs over the page colour. */
.kol-tone-secondary {
  --kol-tone-bg: var(--kol-surface-primary);
  --kol-tone-image: none;
  --kol-tone-fg: var(--kol-surface-on-primary);
  --kol-tone-border: transparent;
  --kol-tone-hover-bg: var(--kol-oq-08);
  --kol-tone-hover-image: none;
  --kol-tone-hover-fg: var(--kol-surface-on-primary);
  --kol-tone-hover-border: transparent;
  --kol-tone-active-bg: var(--kol-oq-16);
  --kol-tone-active-image: none;
  --kol-tone-pressed-bg: var(--kol-surface-on-primary);
  --kol-tone-pressed-image: none;
  --kol-tone-pressed-fg: var(--kol-surface-primary);
  --kol-tone-panel-bg: var(--kol-surface-primary);
  --kol-tone-panel-border-w: 0;
  --kol-tone-panel-border: transparent;
}
/* `inverted` — the text colour as fill: Button's / IconFrame's `secondary`
 * variant and the panel's `--secondary`, under the name the user gave it.
 * (`inverse` is NOT this: it is sunken's alias from 0.117.0, still on one
 * kol-website call, and stays that until it is dropped — the two words were
 * one letter apart and a live alias, so the new tone took the user's word.) */
.kol-tone-inverted,
:where(.kol-btn-secondary, .kol-icon-frame-secondary, .kol-dd-panel--secondary) {
  --kol-tone-bg: var(--kol-surface-on-primary);
  --kol-tone-image: none;
  --kol-tone-fg: var(--kol-surface-primary);
  --kol-tone-border: transparent;
  --kol-tone-hover-bg: var(--kol-oq-inverse-40);
  --kol-tone-hover-image: none;
  --kol-tone-hover-fg: var(--kol-surface-primary);
  --kol-tone-hover-border: transparent;
  --kol-tone-active-bg: var(--kol-oq-inverse-48);
  --kol-tone-active-image: none;
  --kol-tone-pressed-bg: var(--kol-surface-on-primary);
  --kol-tone-pressed-image: none;
  --kol-tone-pressed-fg: var(--kol-surface-primary);
  --kol-tone-panel-bg: var(--kol-surface-on-primary);
  --kol-tone-panel-border-w: 0;
  --kol-tone-panel-border: transparent;
}
.kol-tone-outline,
:where(.kol-btn-outline, .kol-control--outline, .kol-icon-frame-outline, .kol-dd-panel--outline) {
  --kol-tone-bg: transparent;
  --kol-tone-image: none;
  --kol-tone-fg: var(--kol-surface-on-primary);
  --kol-tone-border: var(--kol-oq-08);
  --kol-tone-hover-bg: var(--kol-oq-02);
  --kol-tone-hover-image: none;
  --kol-tone-hover-fg: var(--kol-surface-on-primary);
  --kol-tone-hover-border: color-mix(in srgb, var(--kol-surface-on-primary) 25%, var(--kol-surface-primary));
  --kol-tone-active-bg: var(--kol-oq-08);
  --kol-tone-active-image: none;
  --kol-tone-pressed-bg: var(--kol-surface-on-primary);
  --kol-tone-pressed-image: none;
  --kol-tone-pressed-fg: var(--kol-surface-primary);
  --kol-tone-panel-bg: var(--kol-tone-ground, var(--kol-surface-primary));
  --kol-tone-panel-border-w: 1px;
  --kol-tone-panel-border: var(--kol-oq-08);
}
.kol-tone-ghost,
:where(.kol-btn-ghost, .kol-icon-frame-ghost, .kol-dd-panel--ghost) {
  --kol-tone-bg: transparent;
  --kol-tone-image: none;
  --kol-tone-fg: var(--kol-oq-48);
  --kol-tone-border: transparent;
  --kol-tone-hover-bg: var(--kol-oq-04);
  --kol-tone-hover-image: none;
  --kol-tone-hover-fg: var(--kol-surface-on-primary);
  --kol-tone-hover-border: transparent;
  --kol-tone-active-bg: var(--kol-oq-08);
  --kol-tone-active-image: none;
  --kol-tone-pressed-bg: var(--kol-surface-on-primary);
  --kol-tone-pressed-image: none;
  --kol-tone-pressed-fg: var(--kol-surface-primary);
  --kol-tone-panel-bg: var(--kol-tone-ground, var(--kol-surface-primary));
  --kol-tone-panel-border-w: 0;
  --kol-tone-panel-border: transparent;
}
.kol-tone-grey,
:where(.kol-btn-grey, .kol-dd-trigger--grey, .kol-dd-panel--grey, .kol-icon-frame-grey) {
  --kol-tone-bg: var(--kol-oq-12);
  --kol-tone-image: none;
  --kol-tone-fg: var(--kol-surface-on-primary);
  --kol-tone-border: transparent;
  --kol-tone-hover-bg: var(--kol-oq-16);
  --kol-tone-hover-image: none;
  --kol-tone-hover-fg: var(--kol-surface-on-primary);
  --kol-tone-hover-border: transparent;
  --kol-tone-active-bg: var(--kol-oq-24);
  --kol-tone-active-image: none;
  --kol-tone-pressed-bg: var(--kol-surface-on-primary);
  --kol-tone-pressed-image: none;
  --kol-tone-pressed-fg: var(--kol-surface-primary);
  --kol-tone-panel-bg: var(--kol-oq-12);
  --kol-tone-panel-border-w: 0;
  --kol-tone-panel-border: transparent;
}
.kol-tone-sunken,
.kol-tone-inverse {
  --kol-tone-bg: var(--kol-surface-sunken);
  --kol-tone-image: none;
  --kol-tone-fg: var(--kol-fg-96);
  --kol-tone-border: transparent;
  --kol-tone-hover-bg: var(--kol-surface-sunken);
  --kol-tone-hover-image: linear-gradient(var(--kol-fg-04), var(--kol-fg-04));
  --kol-tone-hover-fg: var(--kol-fg-96);
  --kol-tone-hover-border: transparent;
  --kol-tone-active-bg: var(--kol-surface-sunken);
  --kol-tone-active-image: linear-gradient(var(--kol-fg-08), var(--kol-fg-08));
  --kol-tone-pressed-bg: var(--kol-surface-sunken);
  --kol-tone-pressed-image: linear-gradient(var(--kol-fg-08), var(--kol-fg-08));
  --kol-tone-pressed-fg: var(--kol-fg-96);
  --kol-tone-panel-bg: var(--kol-surface-sunken);
  --kol-tone-panel-border-w: 0;
  --kol-tone-panel-border: transparent;
}
:where(.kol-btn-nav) {
  --kol-tone-bg: transparent;
  --kol-tone-image: none;
  --kol-tone-fg: var(--kol-oq-80);
  --kol-tone-border: transparent;
  --kol-tone-hover-bg: var(--kol-oq-04);
  --kol-tone-hover-image: none;
  --kol-tone-hover-fg: var(--kol-surface-on-primary);
  --kol-tone-hover-border: transparent;
  --kol-tone-active-bg: var(--kol-oq-04);
  --kol-tone-active-image: none;
  --kol-tone-pressed-bg: var(--kol-surface-on-primary);
  --kol-tone-pressed-image: none;
  --kol-tone-pressed-fg: var(--kol-surface-primary);
  --kol-tone-panel-bg: var(--kol-tone-ground, var(--kol-surface-primary));
  --kol-tone-panel-border-w: 0;
  --kol-tone-panel-border: transparent;
}
:where(.kol-icon-frame-nav) {
  --kol-tone-bg: transparent;
  --kol-tone-image: none;
  --kol-tone-fg: var(--kol-surface-on-primary);
  --kol-tone-border: transparent;
  --kol-tone-hover-bg: transparent;
  --kol-tone-hover-image: none;
  --kol-tone-hover-fg: var(--kol-surface-on-primary);
  --kol-tone-hover-border: transparent;
  --kol-tone-active-bg: transparent;
  --kol-tone-active-image: none;
  --kol-tone-pressed-bg: var(--kol-surface-on-primary);
  --kol-tone-pressed-image: none;
  --kol-tone-pressed-fg: var(--kol-surface-primary);
  --kol-tone-panel-bg: var(--kol-tone-ground, var(--kol-surface-primary));
  --kol-tone-panel-border-w: 0;
  --kol-tone-panel-border: transparent;
}
:where(.kol-control--plain) {
  --kol-tone-bg: transparent;
  --kol-tone-image: none;
  --kol-tone-fg: var(--kol-fg-meta);
  --kol-tone-border: transparent;
  --kol-tone-hover-bg: transparent;
  --kol-tone-hover-image: none;
  --kol-tone-hover-fg: var(--kol-fg-meta);
  --kol-tone-hover-border: transparent;
  --kol-tone-active-bg: transparent;
  --kol-tone-active-image: none;
  --kol-tone-pressed-bg: var(--kol-surface-on-primary);
  --kol-tone-pressed-image: none;
  --kol-tone-pressed-fg: var(--kol-surface-primary);
  --kol-tone-panel-bg: var(--kol-tone-ground, var(--kol-surface-primary));
  --kol-tone-panel-border-w: 0;
  --kol-tone-panel-border: transparent;
}
:where(.kol-btn-danger, .kol-icon-frame-danger) {
  --kol-tone-bg: var(--ui-error);
  --kol-tone-image: none;
  --kol-tone-fg: var(--kol-color-ab-white);
  --kol-tone-border: transparent;
  --kol-tone-hover-bg: color-mix(in srgb, var(--ui-error) 80%, var(--kol-surface-primary));
  --kol-tone-hover-image: none;
  --kol-tone-hover-fg: var(--kol-color-ab-white);
  --kol-tone-hover-border: transparent;
  --kol-tone-active-bg: color-mix(in srgb, var(--ui-error) 70%, var(--kol-surface-primary));
  --kol-tone-active-image: none;
  --kol-tone-pressed-bg: var(--kol-surface-on-primary);
  --kol-tone-pressed-image: none;
  --kol-tone-pressed-fg: var(--kol-surface-primary);
  --kol-tone-panel-bg: var(--ui-error);
  --kol-tone-panel-border-w: 0;
  --kol-tone-panel-border: transparent;
}
:where(.kol-btn-accent, .kol-icon-frame-accent) {
  --kol-tone-bg: var(--kol-accent-primary);
  --kol-tone-image: none;
  --kol-tone-fg: var(--kol-accent-on-primary);
  --kol-tone-border: transparent;
  --kol-tone-hover-bg: var(--kol-accent-primary-strong);
  --kol-tone-hover-image: none;
  --kol-tone-hover-fg: var(--kol-accent-on-primary);
  --kol-tone-hover-border: transparent;
  --kol-tone-active-bg: color-mix(in srgb, var(--kol-accent-primary) 70%, var(--kol-surface-primary));
  --kol-tone-active-image: none;
  --kol-tone-pressed-bg: var(--kol-surface-on-primary);
  --kol-tone-pressed-image: none;
  --kol-tone-pressed-fg: var(--kol-surface-primary);
  --kol-tone-panel-bg: var(--kol-accent-primary);
  --kol-tone-panel-border-w: 0;
  --kol-tone-panel-border: transparent;
}

/* ─── the families that keep their own plates ─── */
.kol-view-toggle { background-color: var(--kol-tone-bg, var(--kol-surface-secondary)); }
/* the sunken well's chip ladder (rest · +fg-04 hover · +fg-08 selected) — on the
 * element, or inherited from a sunken wrapper */
: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); }
: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); }
/* the open search shell — was `bg-fg-04` on the element, sunken by class */
.kol-expand--open { background-color: var(--kol-tone-bg, var(--kol-fg-04)); }
/* THE SEGMENTED STRIP, SUNKEN (segmented-toggle-sunken-tone, kol-client-olina
 * 2026-09-03; user, on kol-fxr's labs rail: "is this a variant? its not what you
 * have"). It was not — it was fxr's kol-labs.css:220-226, and olina's inspector
 * carried the same three lines. Promoted verbatim: the shell's ring goes, the
 * selected cell takes the well and the fg-96 ink; rest cells stay as the
 * default paints them. `filled` / `tonal` keep their own selected law. On the
 * element, or inherited from a sunken wrapper. */
:is(.kol-seg.kol-tone-sunken, .kol-seg.kol-tone-inverse, .kol-tone-sunken .kol-seg):not(.kol-seg--filled) { border-color: transparent; }
:is(.kol-seg.kol-tone-sunken, .kol-seg.kol-tone-inverse, .kol-tone-sunken .kol-seg):not(.kol-seg--filled) .kol-seg-cell.is-active {
  background: var(--kol-surface-sunken);
  color: var(--kol-fg-96);
}
/* ⛔ THE TRIGGER IS EXCLUDED — rest and open are its only states (law, twice
 * at the top of this file). Pressed on a sunken button out-ranks its hover, as
 * it always did: this is the one rule the generic `.kol-btn-pressed` cannot
 * express, because hover carries three `:not()`s of specificity. */
.kol-btn.kol-tone-sunken.kol-btn-pressed:not(.kol-dd-trigger),
.kol-tone-sunken .kol-btn.kol-btn-pressed:not(.kol-dd-trigger) {
  background-color: var(--kol-tone-pressed-bg);
  background-image: var(--kol-tone-pressed-image);
  color: var(--kol-tone-pressed-fg);
}


/* ─────────────────────────── DocsToc · rail ───────────────────────────
 * The fisheye rail render of `DocsToc` (`variant="rail"`, docstoc-rail-variant,
 * kol-client-olina 2026-09-03 — built and run there before filing).
 *
 * `--m` is the gaussian magnitude (0..1) the component writes per row on
 * pointer move; EVERY visual here derives from it, so the JS sets one number
 * per row and the cascade does the rest. This is CSS rather than utilities
 * because each rule reads a custom property through `calc()` and sets a
 * child's resting state from the row's variable — neither is expressible as a
 * Tailwind class, which is the §5 test for when a rule belongs in the theme.
 * ────────────────────────────────────────────────────────────────────── */

.kol-toc {
  position: fixed;
  top: 50%;
  transform: translateY(-50%);
  z-index: var(--kol-z-sticky);
  display: flex;
  flex-direction: column;
  padding-block: var(--kol-spacing-8);
}
/* The lens scales about the edge the rail is pinned to, so the column's outer
 * edge stays a straight line while the type breathes. The wide inner padding is
 * the lens's room to grow into without the labels colliding with the page. */
.kol-toc--right {
  right: 0;
  align-items: flex-end;
  padding-right: var(--kol-spacing-6);
  padding-left: var(--kol-spacing-16);
}
.kol-toc--left {
  left: 0;
  align-items: flex-start;
  padding-left: var(--kol-spacing-6);
  padding-right: var(--kol-spacing-16);
}

.kol-toc-row {
  --m: 0;
  display: flex;
  align-items: center;
  gap: var(--kol-spacing-3);
  padding-block: 2px;
  text-decoration: none;
}
.kol-toc--right .kol-toc-row { justify-content: flex-end; }
.kol-toc--left  .kol-toc-row { flex-direction: row-reverse; justify-content: flex-end; }

/* Away from the pointer the row is small and quiet; under it, large and lit.
 * BOTH directions matter — grow-only reads as a bump, not a scrub. */
.kol-toc-label {
  /* `transform` is ignored on a non-replaced INLINE element, and this label is
   * a bare <span>: the scale below did nothing at all until this line. It cost
   * the filer a round trip with the user — opacity and width worked and only
   * the scale did not. */
  display: inline-block;
  white-space: nowrap;
  opacity: calc(0.26 + 0.74 * var(--m));
  transform: scale(calc(0.78 + 0.42 * var(--m)));
  will-change: transform, opacity;
}
.kol-toc--right .kol-toc-label { transform-origin: right center; }
.kol-toc--left  .kol-toc-label { transform-origin: left center; }

.kol-toc-rule {
  height: 1px;
  background: var(--kol-fg-24);
  width: calc(8px + 26px * var(--m));
}

/* The section you are actually IN reads at rest — full ink and a size step — so
 * the column says where you are before the pointer arrives. It rests where an
 * inactive row lands only under the lens. */
.kol-toc-row[data-active] .kol-toc-label {
  opacity: calc(0.92 + 0.08 * var(--m));
  transform: scale(calc(1 + 0.2 * var(--m)));
  color: var(--kol-fg-96);
}
.kol-toc-row[data-active] .kol-toc-rule {
  width: calc(22px + 26px * var(--m));
  background: var(--kol-fg-64);
}

/* Settle on leave — NEVER while the pointer is driving. A transition on a
 * continuous input lags behind the cursor and reads as sluggish; the component
 * stamps `data-live` on pointer-enter, which is what this selector reads. */
.kol-toc:not([data-live]) .kol-toc-label,
.kol-toc:not([data-live]) .kol-toc-rule {
  transition: opacity 260ms ease-out, transform 260ms ease-out, width 260ms ease-out;
}

/* Graduations — the ruler between the labels. Shorter and fainter than a
 * heading's stroke at every point of the curve, so the labelled marks stay the
 * ones you read; they exist to give the lens something continuous to cross.
 * 3px pitch: ten between labels puts a section at ~52px, so a thirteen-section
 * page still stands inside a laptop viewport. */
.kol-toc-tick { height: 3px; padding-block: 0; }
.kol-toc-tick .kol-toc-rule {
  width: calc(5px + 11px * var(--m));
  background: var(--kol-fg-12);
}

@media (prefers-reduced-motion: reduce) {
  .kol-toc-label { opacity: 1; transform: none; }
  .kol-toc-rule  { width: 12px; }
  .kol-toc-row[data-active] .kol-toc-rule { width: 24px; }
  .kol-toc-tick .kol-toc-rule { width: 5px; }
  .kol-toc-label, .kol-toc-rule { transition: none; }
}
