/* ikc-base.css — the design system's CSS layer, for EVERY shell that hosts a
   graph-ui bundle.
 *
 * EXTRACTED 2026-08-16, because it had been in index.html only. studio.html
 * loads the same renderer and had none of it: no token defaults, no typeface,
 * no state classes, no focus ring, no reduced-motion honour. So the one page
 * built to show the widget vocabulary was showing it without the design system,
 * and nothing said so — the same shape as a token nothing reads, one layer out.
 * A rule that lives in one of two shells is a rule half the product does not
 * have. `devbox run validate-dsl` now fails a shell that omits this file.
 *
 * The values here are DEFAULTS ONLY — the first paint, before
 * graph-ui.web.theme writes the active theme's tokens onto the root. The source
 * of truth is dsl/instances/themes.edn (ADR-graph-ui-v0-009-01); these mirror
 * its Amber theme so there is no flash of an unthemed page. */

:root {
  --ground:#f7db83; --panel:#f9e7a6; --raised:#fdf5d8; --card:#fdf6dd;
  --field:#fffbe9; --gutter:#efe1a8; --hover:#f3e6b0; --border:#e6c96a; --border-2:#eed893;
  --text:#34495d; --heading:#2b3d50; --muted:#6f7c86; --faint:#a08f5f;
  --accent:#2f9e80; --accent-2:#7fd0b8; --accent-soft:#cdebe0; --sel:#6fc9ad; --sel-text:#17403a;
  --good:#3f9e6f; --warn:#c0851f; --crit:#cf5a5a;
}

body { background: var(--ground); color: var(--text); }

/* THE TYPEFACE IS A TOKEN (2026-08-15). Set once on the root from the theme's
   own --font-ui / --font-mono, so changing the face in themes.edn — or in a
   DTCG file exported from Penpot — changes every island at once. Before this
   the face was a Tailwind default nobody had written down: a theme could
   restyle everything about the type except which type it was. */
:root { font-family: var(--font-ui); }

/* AND SO IS THE SIZE (2026-08-19). The face was made a token above and the size
   was left behind, so `--type-body-size` was declared, themed, and overridden
   everywhere by Tailwind's own `text-sm` — 14px against the theme's 15px. A
   theme could restate its body type and nothing moved.
   On BODY and not on :root, because `--type-body-size` is in rem and rem means
   the ROOT size. The fallback is amber's own value, for the moment before
   theme/apply-theme! writes the measured token set. */
/* `html body`, not `body`, and the extra element is load-bearing. Tailwind runs
   from a CDN script and injects its preflight at RUNTIME, so it lands after every
   <link> whatever the order in the shell. Its preflight sets
   `body { line-height: inherit }` over `html { line-height: 1.5 }`, which beat a
   plain `body` rule here and held the type at 1.5 against the token's 1.55.
   One extra element takes the specificity to 0,0,2 and no order can lose it. */
html body { font-size: var(--type-body-size, 0.9375rem);
            line-height: var(--type-body-line-height, 1.55); }
code, pre, .font-mono { font-family: var(--font-mono); }

/* STATE, MOTION AND FOCUS AS TOKENS (2026-08-15). These were Tailwind
   utilities — opacity-40, transition-colors, outline-2 — which is to say
   decisions nobody could find and no theme could restate. A theme that wants
   a calmer interface, a more visible focus ring, or a stronger disabled state
   now says so in themes.edn and every island follows. */
.ikc-dim      { opacity: var(--opacity-dim); }
.ikc-disabled { opacity: var(--opacity-disabled); }
/* Added 2026-08-16 with its readers. The class existed for two of the three
   steps and the third had no way to be asked for, so `opacity-75` stayed a
   Tailwind literal in core.cljs and --opacity-strong stayed a name nothing
   read — the same crack as :elevation-floating, one scale over. */
.ikc-strong   { opacity: var(--opacity-strong); }
/* ONE hover emphasis, replacing hover:opacity-90 on a button and
   hover:opacity-80 on a link — the same decision made twice, differing by a
   step nobody chose. */
.ikc-hover:hover { opacity: var(--opacity-hover); }

:where(button, a, [role="button"], input, textarea, select, summary) {
  transition: color var(--motion-fast) var(--motion-ease),
              background-color var(--motion-fast) var(--motion-ease),
              border-color var(--motion-fast) var(--motion-ease),
              opacity var(--motion-fast) var(--motion-ease);
}
:where(button, a, [role="button"], input, textarea, select, summary):focus-visible {
  outline: var(--focus-width) solid var(--focus-color);
  outline-offset: var(--focus-offset);
}

/* A reader who asks for less motion gets none, which is what the token being
   a token makes expressible at all. */
@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after { transition-duration: 0.01ms !important;
                           animation-duration: 0.01ms !important; }
}

/* WAITING, MADE VISIBLE (2026-08-17). George: "some UI indication that click is
   accepted and will take time to be accomplished". graph-ui.web.busy decides
   WHETHER to show these; this file decides what they look like, because that is
   the design decision and it belongs with the other ones.

   THIS IS WHAT --motion-slow WAS WAITING FOR. It was the single token in
   themes.edn that nothing read, and its own note said why it was declared
   anyway: "Nothing in the running UI animates over 300ms — panels and menus
   appear rather than slide — so this waits for the first thing that travels.
   … Consume or cut it when the canvas gains motion." A progress sweep is the
   first thing on this canvas that travels. */
@keyframes ikc-sweep {
  from { transform: translateX(-100%); }
  to   { transform: translateX(300%); }
}

/* The bar itself is a full-width track; the moving part is the ::after. An
   indeterminate bar is the honest form here — the cascade's length is not known
   in advance (it is one dispatch plus one :ui/read per affected source), so a
   percentage would be invented. */
.ikc-busy-bar {
  position: absolute; left: 0; right: 0; bottom: -1px;
  height: var(--focus-width);       /* the thinnest line the system already names */
  overflow: hidden; pointer-events: none;
}
.ikc-busy-bar::after {
  content: ""; position: absolute; inset-block: 0;
  width: 33%;
  background: var(--accent);
  animation: ikc-sweep var(--motion-slow) var(--motion-ease) infinite;
}
.ikc-busy-bar[data-phase="slow"]::after { background: var(--warn); }

/* Reduced motion needs its own rule, not just the blanket one above: an
   `infinite` animation with duration forced to 0.01ms does not stop, it completes
   thousands of times a second, which is a strobe — the opposite of what the
   reader asked for. So the sweep is switched OFF and the track fills instead. The
   indication survives; only the travel goes. */
@media (prefers-reduced-motion: reduce) {
  .ikc-busy-bar::after { animation: none !important; width: 100%;
                         opacity: var(--opacity-strong); }
}

/* A FIXED SLOT IN THE CHROME (2026-08-17). George: "it should live in a fixed
   space, the realignment of the elements after it is disturbing and breaks 'Do
   not make me think' principle." The readout was an ordinary inline flex child,
   so the session dropdown moved every time it appeared. The slot is now rendered
   in every phase and only its contents change — nothing outside it can move.

   Width in ch, not px: an island's sizes belong to the reader
   (ADR-graph-ui-v0-019-01), and the one intrinsic px here is the icon scale, which
   is a token. `--icon-sm` for the ring plus 4.5ch for at most "120s" or "×12".

   tabular-nums is not a flourish. Proportional digits change width as the seconds
   tick, so "9s" -> "10s" -> "11s" would jitter inside the very slot that exists to
   stop things moving. */
.ikc-busy-slot {
  display: inline-flex; align-items: center; gap: 0.35ch;
  flex: 0 0 auto;
  width: calc(var(--icon-sm) + 4.5ch);
  overflow: hidden; white-space: nowrap;
  font-variant-numeric: tabular-nums;
}

/* The hourglass: a ring that turns, sized from the icon scale. */
@keyframes ikc-spin { to { transform: rotate(360deg); } }
.ikc-busy-spinner {
  display: inline-block; flex-shrink: 0;
  width: var(--icon-sm); height: var(--icon-sm);
  border: var(--focus-width) solid var(--border-2);
  border-top-color: var(--accent);
  border-radius: 50%;
  animation: ikc-spin var(--motion-slow) linear infinite;
}
/* Slow says itself in colour on BOTH surfaces at once, so the words did not have
   to stay in the chrome to keep the meaning. */
.ikc-busy-spinner[data-phase="slow"] { border-top-color: var(--warn); }

/* THE ROW'S HOURGLASS OVERLAYS, IT DOES NOT DISPLACE. Same principle as the slot
   above, one level down and solved the other way round: reserving a gutter on
   every row would cost it on all 2029 of them, so the ring is taken out of flow
   at the row's trailing edge instead. A row WITH an accessory (a page count, a
   reference's text) would otherwise have had it shoved left mid-click.

   The transform lives on this wrapper and never on the ring: `.ikc-busy-spinner`
   animates `transform: rotate()`, so a centring `translateY(-50%)` on the same
   element would be overwritten by the first keyframe and the ring would sit high.
   Stretching top/bottom and centring with flex needs no transform at all. */
.ikc-busy-row {
  position: absolute; top: 0; bottom: 0; right: 0.25ch;
  display: flex; align-items: center;
  pointer-events: none;              /* never intercept the click it reports on */
}
/* ═══════════════════════════════════════════════════════════════════════════
   X-RAY — F-graph-ui-v0-010-01. THE DRAWING IS NOT HERE ANY MORE.
 *
 * It was, twice, and neither version worked. Rings and name plates were drawn on
 * each node's own wrapper (`position: relative` + a `::before` carrying
 * `attr(data-xray-name)`), and George's verdict on the result was "the element is
 * gone, scrambled, overlaped".
 *
 * WHY CSS COULD NOT DO IT. A stylesheet places each plate knowing only that
 * plate's own element — not how deep it sits, and not whether a sibling already
 * claimed the pixel. A parent and its first child share a top edge constantly in
 * flex-column layouts, so their plates overprinted into unreadable garbage. That
 * was guaranteed by the MEDIUM, not by a bad offset, which is why nudging the
 * plate from straddling the edge to sitting above it only moved the collision.
 * A second, quieter cost: `position: relative` on 68 wrappers re-parents the
 * containing block of anything absolutely positioned inside them.
 *
 * It now draws in ONE fixed overlay (graph-ui.web.xray/overlay) from rects
 * measured each frame, where "does this label overlap one already placed?" is
 * decidable — and is decided, by a pure, unit-tested function. Nothing is added
 * to any inspected element, so there is no rule to write here.
 *
 * `data-xray` on the render root and `data-xray-name`/`data-xray-detail` on each
 * wrapper are still stamped by render-node — they are how the overlay FINDS and
 * NAMES things, and are deliberately styleless.
 * ═══════════════════════════════════════════════════════════════════════════ */


@media (prefers-reduced-motion: reduce) {
  /* Same reasoning as the bar. A still ring, dimmed, still reads as "pending". */
  .ikc-busy-spinner { animation: none !important; opacity: var(--opacity-dim); }
  /* And MANTINE's loader, for the same reason — the daily-note strip uses Button
     `loading` rather than a section of its own, so this is the one place the
     blanket 0.01ms rule above would strobe a component we did not write. */
  .mantine-Loader-root, .mantine-Button-loader,
  .mantine-Loader-root *, .mantine-Button-loader * {
    animation: none !important;
    opacity: var(--opacity-dim);
  }
}

/* ═══════════════════════════════════════════════════════════════════════════
   THE PHONE — PLAN-ikc-v3-015-01 phase 6.
 *
 * Two rules, both of them here rather than in a widget, because both are true
 * of EVERY control and every page: what a widget tree declares per node is
 * :narrow (dsl/contexts/ui.edn v2.0-SNAPSHOT), and that says which nodes fold,
 * hide or scroll — a different question from these two.
 *
 * 1. NOTHING SCROLLS THE PAGE SIDEWAYS. A trapped horizontal scrollbar is the
 *    failure dev/layout_check.mjs already fails a build for on the desktop, and
 *    on a phone it also steals the swipe. The island decides what scrolls
 *    INSIDE itself (:scroll-area, :narrow :scroll); the document never does.
 *
 * 2. A FINGER IS 44px. --touch-target is the declared minimum
 *    (dsl/instances/themes.edn), and `pointer: coarse` is the browser telling
 *    us there is a finger rather than a mouse — so the rule applies to a phone
 *    and a tablet and leaves every desktop render byte-identical.
 *
 *    It is written once, for the ELEMENTS, and not per component. Twenty
 *    components draw a control between them; a list naming which ones to make
 *    bigger is only as complete as the last person who remembered it, and the
 *    smell of that mistake is a correct app with one un-tappable button.
 *
 *    `min-height`, never `height`: a control that is already taller keeps its
 *    height, and a text label still wraps inside it.
 * ═══════════════════════════════════════════════════════════════════════════ */

@media (pointer: coarse) {
  /* Rule 1. Scoped to a finger deliberately. On a DESKTOP, hiding the page's
     horizontal overflow would make a too-wide island unreachable instead of
     scrollable — Studio needs about 1450px and a 1200px window would simply
     lose the right of it, with nothing saying so. A phone has the opposite
     problem: the swipe is the only navigation it has. */
  html, body { max-width: 100%; overflow-x: hidden; }

  button,
  [role="button"],
  summary,
  select,
  input[type="checkbox"],
  input[type="radio"] {
    min-height: var(--touch-target);
  }

  /* An icon-only control must be as WIDE as it is tall, or it is a 44px-tall
     sliver 28px across. Text buttons are excluded: their width is their label,
     and forcing a minimum on those spreads a toolbar apart. */
  button:empty,
  .mantine-ActionIcon-root {
    min-width: var(--touch-target);
  }

  /* The chrome bar's own controls are spans with an :on-click, not buttons
     (graph-ui.web.core/tbi), so the selector above cannot reach them. They are
     the one hand-named case, and they are named by what they ARE. */
  .ikc-touch-target { min-width: var(--touch-target); min-height: var(--touch-target); }
}

/* ── COLOUR UTILITIES — the design system's own, 2026-09-01 ───────────────────
 *
 * WHY THESE EXIST, AND WHY THEY ARE NOT TAILWIND. Until today the renderer said
 * `text-gray-200` and meant `var(--text)`. It worked only because
 * css/ikc-tailwind.js handed the CDN a map bending Tailwind's whole palette onto
 * the tokens. So every colour decision was written as a NICKNAME and resolved
 * through a second table — the token was the declaration and `gray-200` was a
 * word for it. validate-dsl already refuses `shadow-md`, `z-50` and
 * `opacity-40` for exactly that reason: "a Tailwind utility that duplicates a
 * token is the token's replacement". Colour was the largest case and the one
 * the gate did not cover: 198 uses.
 *
 * WHY NOT text-[var(--text)], WHICH THE CODE ALREADY USES 107 TIMES. Hiccup's
 * shorthand — [:div.text-gray-200.p-2] — is a Clojure KEYWORD, and a keyword
 * cannot contain [ ] or ( ). So the arbitrary-value form is available ONLY
 * where an element already writes {:class "…"}. That is the whole explanation
 * for the codebase's 40/60 split, measured 2026-09-01: 107 token-form classes
 * in strings, ZERO in shorthand. These class names are plain identifiers, so
 * they work in both.
 *
 * THEY DUPLICATE NO VALUE. Each is one property pointing at one var(), so
 * themes.edn stays the single source of truth and a theme switch still moves
 * everything. Only the names that are actually used are here; validate-dsl
 * refuses a colour nickname, so the set grows on purpose.
 */
.ikc-fg-heading  { color: var(--heading) }
.ikc-fg-text     { color: var(--text) }
.ikc-fg-muted    { color: var(--muted) }
.ikc-fg-faint    { color: var(--faint) }
.ikc-fg-border   { color: var(--border) }
.ikc-fg-accent   { color: var(--accent) }
.ikc-fg-accent-2 { color: var(--accent-2) }
.ikc-fg-good     { color: var(--good) }
.ikc-fg-warn     { color: var(--warn) }
.ikc-fg-crit     { color: var(--crit) }

.ikc-bg-ground   { background-color: var(--ground) }
.ikc-bg-panel    { background-color: var(--panel) }
.ikc-bg-raised   { background-color: var(--raised) }
.ikc-bg-card     { background-color: var(--card) }
.ikc-bg-border   { background-color: var(--border) }

.ikc-bd-border   { border-color: var(--border) }
.ikc-bd-raised   { border-color: var(--raised) }
.ikc-bd-accent   { border-color: var(--accent) }
.ikc-bd-good     { border-color: var(--good) }
.ikc-bd-warn     { border-color: var(--warn) }
.ikc-bd-crit     { border-color: var(--crit) }

/* ── dockview, THEMED ONCE (ADR-graph-ui-v0-026-01) ───────────────────────────
 *
 * dockview ships ten themes of its own and applies one by class. Every one is a
 * fixed palette: `.dockview-theme-light` and `.dockview-theme-abyss` know
 * nothing about themes.edn, so a dock drew the Amber island in near-black —
 * seen on the first render, 2026-09-02.
 *
 * THE CLASS, NOT THE INSTANCE. The wrong repair is to style one island's dock.
 * dockview reads its own CSS custom properties, so mapping THOSE onto the
 * tokens themes each dock that will ever exist, and a theme switch moves them
 * with everything else. No island says anything about it.
 *
 * `.ikc-dock-theme` is handed to createDockview as its THEME, so dockview puts
 * the class on its own shell and none of its ten themes is applied over it.
 *
 * Only the colour variables are set. Sizes, radii and transitions are
 * dockview's own mechanism, not decisions this design system has made.
 */
/* AN ISLAND'S STACKING STAYS INSIDE THE ISLAND (2026-09-07).
 *
 * George: "Top bar island selector, when opened should be above other elements,
 * now the reader-v3 line between the panes stays above it." Measured the same
 * day: `.dv-sash` computes to `z-index: 99`, `position: absolute`, and its
 * `.dv-sash-container` is `z-index: auto` — so the 99 was not confined to the
 * dock at all. It competed in the ROOT stacking context, where the picker's
 * menu sits, and 99 beats it. The symptom is precise: a 4px vertical line
 * painted straight across an open dropdown.
 *
 * RAISING THE MENU WOULD BE THE WRONG REPAIR. dockview's stack goes to 1001
 * (drop indicators, the drag compass, floating titlebars), so a number that
 * clears today's sash is a number the next dockview feature beats. And
 * dsl/instances/themes.edn declares FOUR ordered layers ending at
 * `--layer-modal: 50`; a fifth literal in ClojureScript is the thing that
 * declaration exists to prevent.
 *
 * `isolation: isolate` makes this element a stacking context, so EVERY z-index
 * dockview uses — 99, 999, 1001 — is ordered against its siblings inside the
 * dock and against nothing outside it. The dock then takes its own place in the
 * application's ladder as one element. It changes no layout and no paint order
 * within the dock, which is why the sashes, the drop indicators and the drag
 * compass all still stack over the panes exactly as before.
 *
 * THIS IS THE SAME LESSON THE LADDER ALREADY RECORDS one screen down: the four
 * layers describe the renderer's OWN stacking, and Mantine's 1001 exposed that
 * in August. dockview is the second library to do it. The answer this time is
 * to contain the library rather than to out-number it.
 */
.ikc-dock { isolation: isolate; }

.ikc-dock-theme {
  /* the surface a group of panes sits on */
  --dv-group-view-background-color: var(--body);
  --dv-tabs-and-actions-container-background-color: var(--panel);

  /* the active pane's tab reads as the current one */
  --dv-activegroup-visiblepanel-tab-background-color: var(--card);
  --dv-activegroup-visiblepanel-tab-color: var(--heading);
  --dv-activegroup-hiddenpanel-tab-background-color: var(--panel);
  --dv-activegroup-hiddenpanel-tab-color: var(--muted);

  /* a group that does not hold the focus states it by going quieter */
  --dv-inactivegroup-visiblepanel-tab-background-color: var(--panel);
  --dv-inactivegroup-visiblepanel-tab-color: var(--muted);
  --dv-inactivegroup-hiddenpanel-tab-background-color: var(--panel);
  --dv-inactivegroup-hiddenpanel-tab-color: var(--faint);

  --dv-tab-divider-color: var(--border);
  --dv-separator-border: var(--border);
  --dv-paneview-header-border-color: var(--border);
  --dv-paneview-active-outline-color: var(--accent);

  /* the drag handle between panes, and where a pane would land */
  --dv-sash-color: var(--border);
  --dv-active-sash-color: var(--accent);
  --dv-drag-over-background-color: var(--accent-soft);
  --dv-drag-over-border-color: var(--accent);
  --dv-edge-dock-indicator-color: var(--accent);

  --dv-icon-hover-background-color: var(--hover);
  --dv-floating-titlebar-background-color: var(--panel);
  --dv-scrollbar-background-color: var(--border);
}

/* THE GRIP (George, 2026-09-08: "Put a slim drag grip on each pane's top edge.
   Make the pane border more pronounced and to show focus"). dockview moves a pane
   by dragging its TAB, and the tab strip was hidden on 2026-09-07 because it drew
   a title and a ✕ over every pane. So the strip stays, 10px tall, and the tab
   inside it is drawn as a short bar — no title, no ✕ — which is the drag handle.
   Where a pane sits is a runtime value the person owns (the session frame keeps
   it), so this grip is how "below another pane" happens: never a declaration.
   The active group — the one that holds the focus — takes the accent on its
   border and on its grip; every other group has the plain border. */
.ikc-dock-theme { --dv-tabs-and-actions-container-height: 10px; }
.ikc-dock-theme .dv-groupview { border: 1px solid var(--border); box-sizing: border-box; }
.ikc-dock-theme .dv-groupview.dv-active-group { border-color: var(--accent); box-shadow: inset 0 0 0 1px var(--accent); }
.ikc-dock-theme .dv-tabs-and-actions-container { background: var(--panel); }
.ikc-dock-theme .dv-active-group > .dv-tabs-and-actions-container { background: var(--accent-soft); }
.ikc-dock-theme .dv-tab { flex: 1 1 0; min-width: 0; padding: 0; height: 100%;
                          display: flex; align-items: center; justify-content: center; cursor: grab; }
.ikc-dock-theme .dv-tab:active { cursor: grabbing; }
.ikc-dock-theme .dv-tab .dv-default-tab { display: none; }   /* the title and the ✕ */
.ikc-dock-theme .dv-tab::before { content: ""; width: 36px; height: 4px; border-radius: 2px; background: var(--border); }
.ikc-dock-theme .dv-tab:hover::before,
.ikc-dock-theme .dv-active-group .dv-tab::before { background: var(--accent); }

/* PROGRESSIVE SUMMARIZATION (RESEARCH-ikc-v3-021-01 D5, PLAN-ikc-v3-026-01 D).
   A highlight's LAYER, 1 to 5, drawn ON TOP of its colour wash: layer 1 is the
   wash alone (the passage you saved), 2 is bold (the part you made bold), 3 is
   bold and underlined (the best of the bold), 4 doubles the line (a summary in
   your own words), 5 is italic too (a remix). One class per layer, so a layer-3
   span inside a layer-2 span is bold and washed twice. Declared in
   dsl/contexts/ui.edn (:markdown), drawn here and nowhere else. */
.ikc-hl-layer-2 { font-weight: 600; }
.ikc-hl-layer-3 { font-weight: 600; text-decoration: underline; text-decoration-thickness: 1px; }
.ikc-hl-layer-4 { font-weight: 600; text-decoration: underline double; }
.ikc-hl-layer-5 { font-weight: 700; text-decoration: underline double; font-style: italic; }

/* The coach mark (PLAN-ikc-v3-028-01 Phase D): the target wears a ring, the
   rest of the island a light wash. The wash lets pointer events through, so a
   person can still act on the target it points at. */
.ikc-coach-target  { outline: 2px solid var(--accent); outline-offset: 3px; border-radius: 4px; }
.ikc-coach-overlay { position: fixed; inset: 0; z-index: 50; pointer-events: none;
                     background: color-mix(in srgb, var(--text) 18%, transparent); }
.ikc-coach-anchor  { position: fixed; z-index: 60; }
.ikc-coach-badge   { position: fixed; z-index: 60; min-width: 22px; height: 22px; padding: 0 6px;
                     border-radius: 11px; background: var(--accent); color: var(--panel);
                     font-size: 12px; font-weight: 700; line-height: 22px; text-align: center;
                     box-shadow: var(--elevation-raised); pointer-events: none; }
.ikc-coach-card    { background: var(--panel); box-shadow: var(--elevation-floating); }
/* --panel is the theme's opaque pane tone; --surface is not a theme variable here, and a
   card painted with it was transparent (2026-09-08). The elevation is the theme's own. */
/* Every rule the mark needs lives HERE and not in a framework utility: the
   generated stylesheet holds only the utilities it saw, and a class it did not
   see draws nothing — which is how the wash blocked every click on 2026-09-08. */

/* ═══════════════════════════════════════════════════════════════════════════
   THE SCROLLBAR — 2026-09-11, PLAN-graph-ui-v0-016-02 follow-up.
 *
 * THE ONE PIECE OF CHROME NO TOKEN COULD REACH. This stylesheet styled no
 * scrollbar at all, so every scrollable pane in every island got the
 * browser's own — an OVERLAY bar on this platform: it appears while you
 * scroll, takes no space, and leaves nothing to aim at. George, 2026-09-11,
 * on the Studio's tabs: the absence of horizontal scroll. The content
 * scrolled; the bar did not exist.
 *
 * IT IS TOKENS, NOT A LOOK. dsl/instances/themes.edn declares the four —
 * scrollbar-size in :base beside touch-target, and three colours per theme —
 * so a theme owns its own chrome and the theme editor can move it. A rule
 * with literals here would be the thing the design system exists to refuse.
 *
 * BOTH SPELLINGS. `scrollbar-color` is the standard and Firefox honours it;
 * the ::-webkit- rules are what Chrome reads. Naming both is how one
 * declaration reaches both engines.
 *
 * NOT ON THE DOCUMENT. The page itself never scrolls sideways (rule 1 above),
 * so these apply to the elements that DO scroll and leave the document alone.
 * ═══════════════════════════════════════════════════════════════════════════ */
* {
  scrollbar-color: var(--scrollbar-thumb) var(--scrollbar-track);
  scrollbar-width: thin;
}
*::-webkit-scrollbar {
  width:  var(--scrollbar-size);
  height: var(--scrollbar-size);
}
*::-webkit-scrollbar-track {
  background: var(--scrollbar-track);
}
*::-webkit-scrollbar-thumb {
  background: var(--scrollbar-thumb);
  border-radius: var(--radius-sm);
  /* A thumb that touches the track's edges reads as a block; the border is the
     track showing through, which is what makes it look like a thumb. */
  border: 2px solid var(--scrollbar-track);
  background-clip: padding-box;
}
*::-webkit-scrollbar-thumb:hover { background: var(--scrollbar-thumb-hover); }
*::-webkit-scrollbar-corner      { background: var(--scrollbar-track); }

/* ═══════════════════════════════════════════════════════════════════════════
   A CONTROL'S OWN SURFACE — 2026-09-11.
 *
 * Mantine paints its default Button, Input and Select from four variables of
 * its own. Until today nothing pointed them at this project's tokens, so a
 * control kept Mantine's greys whatever the theme said — half of why a person
 * editing a swatch could not find what it moved.
 *
 * FOUR TOKENS, DECLARED PER THEME (dsl/instances/themes.edn :control). This
 * is the bridge and not the decision: the values are the theme's.
 *
 * `control-active` has no Mantine variable to take, so it is applied below to
 * the one state this codebase already spells: Mantine's `light` variant is
 * what graph-ui.web.widgets/tree-row-button gives the CURRENT row — today in
 * the daily nav, the focused tile, and the open level in the Studio's tabs.
 * ═══════════════════════════════════════════════════════════════════════════ */
/* THE SELECTOR MUST MATCH MANTINE'S OWN, and a plain :root does not. Mantine
   sets these under `:root[data-mantine-color-scheme='light']` — specificity
   (0,1,1) — so a bare `:root` (0,0,1) lost, silently: measured 2026-09-11,
   --mantine-color-default stayed #fff in Amber and #2e2e2e in Midnight while
   --control-face said #fdf5d8 and #242a35. Same shape, and ikc-base.css is the
   last stylesheet index.html loads, so ours wins on order. */
:root[data-mantine-color-scheme='light'],
:root[data-mantine-color-scheme='dark'] {
  --mantine-color-default:        var(--control-face);
  --mantine-color-default-border: var(--control-border);
  --mantine-color-default-hover:  var(--control-hover);
}

/* THE CURRENT CONTROL. `light` is Mantine's variant for a control that is in
   force, and tree-row-button uses it for :current and :today. Pointing it at a
   token is what lets a theme say how "this one is selected" looks. */
.mantine-Button-root[data-variant='light'] {
  background: var(--control-active);
}

/* ═══════════════════════════════════════════════════════════════════════════
   A CODE BLOCK GETS ITS NATIVE SCROLLBARS BACK — 2026-09-11.
 *
 * George: horizontal scroll is not present in the Declaration tab. It was
 * present and invisible, which is the same thing from a chair.
 *
 * Mantine's CodeHighlight wraps its own content in a ScrollArea, and a
 * ScrollArea hides the native bars (`scrollbar-width: none`, and
 * `::-webkit-scrollbar { display: none }`) so it can draw its own. Its own
 * VERTICAL bar appears; its HORIZONTAL bar reports data-state="hidden" and
 * zero height, because it lays content out with `min-width: 100%` and measures
 * no overflow to report. So a wide declaration scrolled 324 pixels with
 * nothing on screen saying it could.
 *
 * THE NATIVE BARS ARE THE ANSWER NOW, and they were not before this morning:
 * the scrollbar tokens give them a size and a colour, so a native bar is 11
 * pixels of real chrome that follows the theme. Undoing Mantine's hiding for
 * THIS component only is smaller than fighting its own scrollbar logic.
 *
 * SPECIFICITY IS THE WHOLE TRICK. Mantine's rules are one class (0,1,0); a
 * descendant selector is two (0,2,0) and wins without !important.
 * ═══════════════════════════════════════════════════════════════════════════ */
.mantine-CodeHighlight-scrollarea .mantine-ScrollArea-viewport {
  scrollbar-width: thin;
  scrollbar-color: var(--scrollbar-thumb) var(--scrollbar-track);
}
.mantine-CodeHighlight-scrollarea .mantine-ScrollArea-viewport::-webkit-scrollbar {
  display: block;
  width:  var(--scrollbar-size);
  height: var(--scrollbar-size);
}
.mantine-CodeHighlight-scrollarea .mantine-ScrollArea-viewport::-webkit-scrollbar-track {
  background: var(--scrollbar-track);
}
.mantine-CodeHighlight-scrollarea .mantine-ScrollArea-viewport::-webkit-scrollbar-thumb {
  background: var(--scrollbar-thumb);
  border-radius: var(--radius-sm);
  border: 2px solid var(--scrollbar-track);
  background-clip: padding-box;
}
.mantine-CodeHighlight-scrollarea .mantine-ScrollArea-viewport::-webkit-scrollbar-thumb:hover {
  background: var(--scrollbar-thumb-hover);
}
