/* ════════════════════════════════════════════════════════════════════
   mike.css v3.2.3 — one file, twelve cascade positions, four engines.
   https://github.com/Deufel/css · the CDN: cdn.jsdelivr.net/gh/Deufel/css@v3.2.3/mike.css

   THE SPINE (declared once, below; the only place layer order is set)
     reset          the modern reset — no sizes, no colours
     core.color     surfaces and ink as positions on a three-anchor walk
     core.type      the type scale IS the spacing system (lh / em only)
     core.shell     the .page shell: the pg-* regions and their anatomy
     theme          ALL config — every knob has its single home here
     base           bare-element defaults (consumes the cores)
     composition    the layout primitives (.row .column .grid .bento …)
     block          the components — <blk-*>, styled HTML, one callout each
     utility        call-site helpers — late, so a helper beats a block
     exception      what overrides a block from outside: shipped variants,
                    standing states (data-ui-state), modes (print, focus,
                    view transitions)
     visibility     the display gates — revert-layer; LAST, so a gate out-
                    ranks every display a block ever sets

   The four cores are orthogonal: each reads nothing from the others
   (the shell reads type's lh — the one sanctioned direction).
   Everything from composition down is a CONSUMER and may read all four.

   THE THREE LAWS
   1 Zero specificity. Every selector is :where()-wrapped; within a layer
     only source order breaks a tie. A specificity fight is a bug.
   2 No !important. Important declarations invert layer order and would
     beat the gates. None, anywhere: paper is a print block in exception.
   3 Locality. A block's parts and states nest inside its one rule;
     nothing about a block lives anywhere else. Visibility is the single
     exception, by design: a part wears a device or pointer gate, and the
     gate layer owns the only two states, none and revert-layer — so a
     part may use any display. A block is ONE layout (v3.2.0): it fills
     the cell it is handed and never rearranges by the cell's size.

   THE VOCABULARY A TEMPLATE MAY USE
     <blk-*>          a block; the tag is the hook
     .cell            a box the page sizes (composition) — a grid track, a
                      region; a block fills it and never changes its layout
     gates            .mobile .tablet .desktop .ultrawide · .small .medium .large ·
                      .c-* · .fine .coarse (visibility)
     helpers          .suc .inf .wrn .dgr · .num · .bg · .bw · .glass …
                      (utility — the closed list)
     state            data-ui-state="…" tokens, matched ~= ; native and
                      ARIA state first (disabled, aria-pressed, :open)
     engine           --hue --hue-shift --hue-lock --bg --lift --fg
                      --type --scale --gap — inline where a value is data

   DERIVATION: a mechanical re-layering of system.css (2026-10-08) — every
   rule body carried verbatim, re-homed on the new spine:
     core.layout → core.shell + composition (split at the primitives)
     components  → block        utility → utility      media → visibility
     skin        → exception    view-transitions → exception
   Comments inside carried-over sections may still name the old layers.

   CAVEATS (to resolve when the templates are migrated)
   · .block (gf-608) is kept as an alias of .cell and also answers the
     old `@container block` name; its body rule stays but any <blk-*>
     child arranges itself and wins.
   · .c-portrait/.c-landscape/.c-short/.c-tall are superseded by the
     shapes; left in place for existing templates.
   ════════════════════════════════════════════════════════════════════ */
@layer reset,
       core.color, core.type, core.shell,               /* the three cores — orthogonal, standalone */
       theme, base,                                      /* all config · bare-element defaults */
       composition, block, utility,                      /* layout primitives · components · call-site helpers */
       exception, visibility;                            /* states, variants, modes · the display gates, last */

/* ── registrations — the only @property declarations; everything else is config in theme ── */
@property --bg { syntax: "<number>"; inherits: false; initial-value: 0 }
/* a surface's position must not leak to children */
@property --fg { syntax: "<number>"; inherits: true;  initial-value: -0.85 }
/* animated (link hover) */
@property --hue-shift { syntax: "<number>"; inherits: true;  initial-value: 0 }
/* animated (link hover) */
@property --lift { syntax: "*";        inherits: false }
/* must not inherit; MUST have no default anywhere — unset → guaranteed-invalid → --_pos falls back to --bg */
@property --gap { syntax: "*";        inherits: false }
/* per-composition rhythm knob (style API); must not leak into nested compositions — unset → guaranteed-invalid → the primitive's own default */
@property --rise { syntax: "*";        inherits: false }
/* .rise's cqb share (style API); must not leak into nested sized canvases */
@property --_sem-ink { syntax: "*";        inherits: false }
/* ════════════════════════════════════════════════════════════════════
   type.css — the TYPE concern. Standalone, orthogonal, complete.

   Type IS the spacing system: everything downstream spaces itself in
   em / lh, so one knob moves size, lead, tracking, weight — and thereby
   every gap and padding in the system. There is deliberately NO separate
   spacing API.

   PUBLIC API:
     --type   the step, INHERITED (gf-68): set it on an element or a
              container — the subtree follows unless an element sets its
              own. Canonical steps are ints −2…+5 (h1=5 … h6=0,
              small=−1); the scale is continuous, so fractional steps are
              legal (the system uses −0.5 itself). Absolute, never
              compounding: −1 inside −1 is still −1.
     --scale  regional zoom, inherits.
   PUBLIC OUTPUTS:
     --_font-size   the resolved, unfloored size. Components may read it
                    for the iOS floor idiom:
                    font-size: max(16px, var(--_font-size))
     --_control-h   THE shared control height — resolved per element
                    against its OWN unfloored size, so every set-height
                    control lands on one height at a given --type.

   ORTHOGONALITY: this file reads nothing from colour or layout.
   REGISTRATION: one. --type must not inherit (a step means the same
   thing everywhere it is set; components opt in per-subtree with the
   `--type: inherit` idiom). --scale and all config are plain inherited
   custom properties with their single home in @layer theme.
   ════════════════════════════════════════════════════════════════════ */
/* --type INHERITS (gf-68, canon change): a container's step flows to
   everything below it, the way every author already assumes. Safe because
   the scale is ABSOLUTE (pow off the configured base, not em-relative) —
   inheritance cannot compound: −1 inside −1 is still −1. Elements that set
   their own step keep it (normal cascade). This retired .type-flow, whose
   utility-layer placement had a wart the native cascade fixes: it squashed
   component-set steps (a .badge's −2) instead of respecting them. */
@property --type { syntax: "<number>"; inherits: true; initial-value: 0 }
/* ════════════════════════════════════════════════════════════════════
   components (continued) — the FULL port. Every painter converted to the
   two-channel contract; every rogue colour formula rebuilt on the
   anchors. Audit trail per component in component-check.html.
   ════════════════════════════════════════════════════════════════════ */
/* --row-width: registered because non-inheritance is load-bearing —
   a field's row share must never leak to siblings/children. */
@property --row-width { syntax: "<number>"; inherits: false; initial-value: 1 }


/* ════════════════════════════════════════════════════════════════════
   reset — minimal, modern-only, deferential to the cores.
   No font-size / line-height (type owns them). No px/rem spacing.
   ════════════════════════════════════════════════════════════════════ */
@layer reset {
  *, *::before, *::after { box-sizing: border-box; margin: 0; background-repeat: no-repeat; }
  :where(html) { interpolate-size: allow-keywords; -webkit-text-size-adjust: none; text-size-adjust: none; }
  :where(body, p, h1, h2, h3, h4, h5, h6, figure, blockquote, dl, dd, ul, ol) { margin-block: 0; }
  :where(img, picture, svg, video, canvas) { display: block; max-inline-size: 100%; block-size: auto; }
  :where(input, button, textarea, select) { font: inherit; letter-spacing: inherit; color: inherit; background: none; border: 0; }
  :where(textarea) { resize: vertical; }
  :where(fieldset) { margin: 0; padding: 0; border: 0; min-inline-size: 0; }
  :where(legend) { padding: 0; }
  :where(button, [role="button"], label, summary, input[type="file"]::file-selector-button, .btn) { cursor: pointer; }
  :where(:disabled, [aria-disabled="true"]) { cursor: not-allowed; }
  :where(a) { color: inherit; text-decoration: none; }
  :where(ul, ol):where([role="list"]) { list-style: none; padding: 0; }
  :where(table) { border-collapse: collapse; }
  :where(summary) { list-style: none; }
  :where(:focus-visible) { outline: 2px solid var(--focus); outline-offset: 2px; }
  :where(body) { overflow-wrap: break-word; overscroll-behavior: none; }
  :where(p, li) { text-wrap: pretty; }
  :where(h1, h2, h3, h4, h5, h6) { text-wrap: balance; }
}


/* the semantic ink-commit flag; unset → components fall back to resting --fg */
/* ════════════════════════════════════════════════════════════════════
   core.color — the resolver.
   ════════════════════════════════════════════════════════════════════ */
@layer core.color {
  :where(*) {
    --_h: var(--hue-lock, calc(var(--hue) + var(--hue-shift)));

    /* anchors, re-hued live; --chroma is the mono multiplier */
    --_a-neg:  oklch(from var(--anchor-neg)  l calc(c * var(--chroma)) var(--_h));
    --_a-zero: oklch(from var(--anchor-zero) l calc(c * var(--chroma)) var(--_h));
    --_a-pos:  oklch(from var(--anchor-pos)  l calc(c * var(--chroma)) var(--_h));

    /* position — absolute --bg, or --lift above the host's published
       position. --lift unset → --_rel guaranteed-invalid → fall back. */
    --_rel: calc(var(--surf-bg) + var(--lift));
    --_pos: var(--_rel, var(--bg));

    /* state modulation in position space (v1 math, unchanged) */
    --_sp: clamp(0, var(--_pos), 1);
    --_sn: clamp(0, calc(0 - var(--_pos)), 1);
    --_bgs: calc( var(--_sp)*(1 - var(--state)) - var(--_sn)*(1 - var(--state)*var(--st-neut)) + var(--state)*var(--st-hover) );

    /* THE SURFACE: a linear walk through the three anchors */
    --_p: clamp(0, var(--_bgs), 1);
    --_n: clamp(0, calc(0 - var(--_bgs)), 1);
    --_bg: color-mix(in oklch,
             color-mix(in oklch, var(--_a-zero), var(--_a-pos) calc(var(--_p) * 100%)),
             var(--_a-neg) calc(var(--_n) * 100%));

    /* INK POLES — lightness derived from the host colour (--surf).
       Inside oklch(from …), l is the host's own lightness 0–1; the clamp
       is a step function on the flip: light host → the -d pole (dark
       ink), dark host → the -l pole (light ink). */
    --_ink-n: oklch(from var(--surf)
                calc(var(--fg-ink-d) + clamp(0, (var(--flip) - l) * 100, 1) * (var(--fg-ink-l) - var(--fg-ink-d)))
                calc(0.01 * var(--chroma)) var(--_h));
    --_ink-c: oklch(from var(--surf)
                calc(var(--fg-chr-d) + clamp(0, (var(--flip) - l) * 100, 1) * (var(--fg-chr-l) - var(--fg-chr-d)))
                calc(var(--fg-chroma) * var(--chroma)) var(--_h));

    /* THE INK: the same walk, from the host out to a pole */
    --_fgs: calc( var(--fg) * (1 + var(--state) * var(--st-fg-gain)) );
    --_fp: clamp(0, var(--_fgs), 1);
    --_fn: clamp(0, calc(0 - var(--_fgs)), 1);
    color: color-mix(in oklch,
             color-mix(in oklch, var(--surf), var(--_ink-n) calc(var(--_fn) * 100%)),
             var(--_ink-c) calc(var(--_fp) * 100%));

    /* derived tokens — one more application of the idea each. The border
       mixes toward the ink pole, so it flips lighter-in-dark for free. */
    --border: color-mix(in oklch, var(--surf), var(--_ink-n) var(--border-mix));
    --focus:  oklch(from var(--_a-pos) l calc(c * var(--focus-boost)) h);
  }

  /* the painter contract as a class — publish a surface on ad-hoc markup */
  :where(.bg) { background-color: var(--_bg); --surf: var(--_bg); --surf-bg: var(--bg); }

  /* a themed element establishes its own surface, so its subtree re-themes */
  :where([data-ui-theme], .light, .dark) { background-color: var(--_bg); --surf: var(--_bg); --surf-bg: var(--bg); }

  /* interactive elements are state islands; reset, then own pointer state */
  :where(button, a, [role="button"], summary, .clickable, .btn):where(:not([disabled], [aria-disabled="true"])) {
    --state: 0;
    &:hover  { --state: var(--st-hover); }
    &:active { --state: calc(-2 * var(--st-hover)); }
  }

  /* disabled — drain ink toward the surface + wash the chroma */
  :where([disabled], [aria-disabled="true"]) { --fg: -0.3; --chroma: 0.15; }
}


/* ════════════════════════════════════════════════════════════════════
   core.type — the scale.
   ════════════════════════════════════════════════════════════════════ */
@layer core.type {
  :where(*) {
    /* modular scale — ONE ratio, applied at BOTH viewport endpoints.
       Fluidity comes from the min/max BASE sizes growing across the
       viewport, NOT from differing ratios — a given --type is a fixed
       multiple of body at every width. pow() takes the raw step, so
       −1/−2 fall on the same ratio 0/+1/+2 climb on. */
    --_t-min: calc(var(--cfg-type-min) * pow(var(--cfg-ratio), var(--type)));
    --_t-max: calc(var(--cfg-type-max) * pow(var(--cfg-ratio), var(--type)));
    --_font-size: calc(
      clamp( min(var(--_t-min), var(--_t-max)),
        calc( var(--_t-min) + (var(--_t-max) - var(--_t-min))
          * (100vi - var(--cfg-fluid-min-vp)) / (var(--cfg-fluid-max-vp) - var(--cfg-fluid-min-vp)) ),
        max(var(--_t-min), var(--_t-max)) ) * var(--scale) );
    font-size: var(--_font-size);

    /* THE shared control height — per element, off its OWN unfloored
       size: height = unfloored font × --cfg-control-lead + 2px border.
       Consumers set block-size to this and CENTRE content, so
       line-height never enters the box math — which is what makes a
       button and a field match exactly at every step. */
    --_control-h: calc(var(--_font-size) * var(--cfg-control-lead) + 2px);

    letter-spacing: clamp(-0.04em, calc(0.01em - var(--type) * 0.012em), 0.04em);
    line-height:    clamp(1.1, calc(1.5 - var(--type) * 0.075), 1.6);

    /* optical weight — a function of --type, like size. Climbs only
       above step 0; the clamp floor keeps body/small at base weight. */
    font-weight: clamp(
      var(--cfg-wght-base),
      calc(var(--cfg-wght-base) + var(--type) * var(--cfg-wght-step)),
      var(--cfg-wght-max)
    );
  }
}


/* ════════════════════════════════════════════════════════════════════
   layout.css — the LAYOUT concern. Standalone, orthogonal, complete.

   Pure geometry: the .page shell, the composition primitives, and the
   responsive gates. Spacing is lh/em ONLY — layout consumes Type's
   rhythm (the one sanctioned inter-core dependency, and it points one
   way: layout reads lh; type reads nothing back).

   Colour appears in exactly one form: the painter contract from
   color.css (pg-* regions are surface publishers: --surf + --surf-bg). No colour math,
   no literals, no other --_* reads.

   NOT here (they are components, not layout): .drawer, .modal, .menu —
   they paint, animate, and carry borders. .hud IS here: it is pure
   positioning (a click-through slot grid).

   Layers in this file: core.layout · utility (geometry slice) · media.
   The gates MUST cascade after components (the display battle), which is
   why `media` is a separate late position in the spine — a concern may
   span layers; the file is the concern boundary.
   ════════════════════════════════════════════════════════════════════ */
@layer core.shell {
  /* the viewport lock is BOUND to .page (was a global html,body rule that
     clipped bare documents): a .page fills the viewport and lets .pg-main
     scroll — the shell never does; a non-.page document scrolls normally. */
  :where(html:has(.page), body:has(.page)) { height: 100%; overflow: hidden; overscroll-behavior: none; }

  /* THE BLOWOUT GUARD: grid/flex items default to min-size:auto, so one
     wide child silently pushes a track past the viewport (the classic
     mobile overflow). Shell regions must always be shrinkable; content
     inside them wraps or scrolls instead. */
  :where(.page) > :where(*) { min-inline-size: 0; }

  /* .page — 10-slot app grid; slots opt-in via pg-* children, empty
     tracks collapse. Only nav/main/aside/toolbar scroll; the shell never does.
     It is also the NAMED shell container ("page") — shell chrome outside the
     pg-main containers (the n3 rail) gates on it with @container page (…),
     the container version of the width gates. Inline-size containment is
     safe here: the page's width comes from the viewport, never its content. */
  :where(.page) {
    container-type: inline-size; container-name: page;
    display: grid;
    /* THE FULL-HEIGHT-NAV TOPOLOGY (2026-08). The banner is the ONE
       full-width band — system-wide things interrupt the whole frame,
       nav included. The nav column runs from under it to the viewport
       bottom; header/subheader/footer are CONTENT-side bands. This is
       safe for every page: a page with no pg-navigation child leaves the
       "n" column empty, the auto track collapses to 0, and h/s/f span
       the full width exactly as before — public pages and the mobile
       breakpoint (nav display:none, dock in "f") need no special case. */
    grid-template:
      "b  b  b  b  b"  auto
      "n  h  h  h  h"  auto
      "n  s  s  s  s"  auto
      "n  mh mh mh a"  auto
      "n  ms ms ms a"  auto
      "n  t  m  ma a"  1fr
      "n  t  mf mf a"  auto
      "n  f  f  f  f"  auto /
      auto auto 1fr auto auto;
    height: 100svh; overflow: hidden; overscroll-behavior: none;

    /* centered caps the frame on ULTRA-WIDE screens only (2026-09-10, per
       Mike) — a laptop never sees the margins. */
    &:where(.centered) { max-inline-size: 1600px; margin-inline: auto; }

    /* FOCUS MODE — the SERVER stamps data-ui-state="focus" on .page from
       the stored pref (gf-262; the hidden checkbox is retired): the chrome
       collapses and the grid becomes a single column of the main rows. */
    &:where([data-ui-state="focus"]) {
      grid-template:
        "mh" auto
        "ms" auto
        "m"  1fr
        "mf" auto / 1fr;
    }

    /* regions paint their own surface — set --bg on any pg-* child and it
       renders. This is the PAINTER CONTRACT from color.css (publisher
       form) — the one sanctioned colour reference in this file. */
    & > :where([class^="pg-"], [class*=" pg-"]) {
      background-color: var(--_bg);
      --surf: var(--_bg);
      --surf-bg: var(--bg);
    }

    & > :where(.pg-banner)      { grid-area: b;  container-type: inline-size; }
    & > :where(.pg-header)      { grid-area: h;  container-type: inline-size; }
    & > :where(.pg-subheader)   { grid-area: s;  container-type: inline-size; }
    /* RAILS ARE DECLARED, TYPE-DERIVED, COMMAND-DRIVEN. The labeled
       width is the published knob --rail-w (lh, so it rides the type
       scale). The icon width is the item's anatomy spelled out: glyph
       (1.15em) + the item's inline padding (2 × 0.6em) + the region
       inset (2 × 0.25lh) + the region's 1px border — the same
       "type-derived + constant border term" shape as --_control-h.
       Every omitted pixel SHRINKS THE GLYPH rather than overflowing
       (the reset caps svg at max-inline-size: 100%). overflow-x is
       clipped: an overflow-y:auto axis pair cannot keep x visible,
       and mid-animation the labels are wider than the rail. The
       transition rides the motion config like every other one here.
       Collapse is an EXPLICIT WIDTH (gf-71b): the server stamps
       --rail-w (var(--rail-w-icon) when collapsed); labels drop out
       through the container-tier gates. No density attribute. */
    /* Rails are QUERY CONTAINERS too (gf-22): their widths are DECLARED
       (--rail-w / the icon anatomy), never content-derived, so inline-size
       containment is safe — and rail content can gate on the component
       tiers (.small at icon width, .medium/.large when labeled) instead of
       inferring density from attributes. */
    & > :where(.pg-navigation)  { grid-area: n;  inline-size: max-content; min-inline-size: var(--pg-navigation-w, 8lh); max-inline-size: calc(2 * var(--pg-navigation-w, 8lh)); container-type: inline-size; overflow-x: clip; overflow-y: auto; min-height: 0; overscroll-behavior: contain; transition: min-inline-size calc(var(--cfg-motion) * 0.2s) ease; }
    & > :where(.pg-toolbar)     { grid-area: t;  inline-size: max-content; min-inline-size: var(--pg-toolbar-w, 8lh); max-inline-size: calc(2 * var(--pg-toolbar-w, 8lh)); container-type: inline-size; overflow-x: clip; overflow-y: auto; min-height: 0; overscroll-behavior: contain; transition: min-inline-size calc(var(--cfg-motion) * 0.2s) ease; }
    /* rail children are SHRINK-PROOF (gf-72b): a short screen must
       SCROLL the rail (overflow-y above), never squeeze its groups —
       squeezed groups compress and overlap their content, which read as
       "the icons shrink". Same scroll-safety the toolbar's filling
       spread-column already declared. */
    & > :where(.pg-navigation, .pg-toolbar) > * { flex-shrink: 0; }
    /* the COLLAPSED width is the ANATOMY (gf-72): glyph (1.15em) +
       item inline padding (0.6em × 2) + rail padding (0.25lh × 2). The
       server stamps --pg-navigation-w / --pg-toolbar-w to this token;
       labels drop through the tier gates, the item stays left-aligned
       with unchanged padding, so the glyph never moves — and sits
       centered because the width wraps it exactly, at every type size. */
    --rail-w-icon: calc(1.15em + 1.2em + 0.5lh);
    & > :where(.pg-main-header) { grid-area: mh; container-type: inline-size; }
    /* ms — a band under the main header: tabs, crumbs, filter strips
       that belong to main but must not scroll with it. */
    & > :where(.pg-main-subheader) { grid-area: ms; container-type: inline-size; }
    /* pg-main is the page's SIZE CONTAINER — and pg-main-header is one too
       (above): controls in the header (the views control's labels) gate on
       .small/.medium/.large exactly like content in main, resolving against
       the CONTENT column's width rather than the viewport. The @container
       gates are WIDTH queries, so inline-size is the right type; it also
       avoids the block-axis containment that container-type:size would
       impose. */
    /* pg-main does NOT scroll (gf-39, per Mike): it is the fixed FRAME,
       and every component inside owns its own scrolling (.scroll-y on a
       roster body, a section, a chart). That single inversion retires the
       whole old knot — scrollbar-gutter reservation at the region, the
       no-block-padding doctrine, and the per-view padding hand-rolls it
       forced. A page whose whole flow should scroll (the marketing
       landing) opts in with .scroll-y on the region itself. */
    & > :where(.pg-main)        { grid-area: m;  overflow: hidden; min-height: 0; container-type: inline-size; }
    /* a lone direct <section> is the common canvas payload — it scrolls
       itself by default, so no view has to remember to say so. ONLY when
       pg-main is the clipped frame: a region that opted into .scroll-y
       (the marketing landing) must keep natural-height children, or every
       section shrinks to fit and clips itself. */
    & > :where(.pg-main:not(.scroll-y)) > :where(section) { overflow-y: auto; overscroll-behavior: contain; min-block-size: 0; scrollbar-gutter: stable; } /* the gutter reserved (feedback #7): content must not shift when a scrollbar appears */
    /* a section whose COMPONENT owns scrolling opts out with .owns-scroll
       (gf-66): the section becomes the fixed flex column filling the
       frame and its scrolling child clips instead — so corner badges and
       focus rings at the section's edges stop being sheared by the
       section's own scroll box (overflow-y:auto clips BOTH axes). */
    & > :where(.pg-main:not(.scroll-y)) > :where(section.owns-scroll) { overflow: visible; block-size: 100%; display: flex; flex-direction: column; min-block-size: 0; scrollbar-gutter: auto; } /* a section that does not scroll reserves no gutter (EventOS feedback #509) */
    & > :where(.pg-main-footer) { grid-area: mf; container-type: inline-size; }
    /* ASIDES ARE CONTENT-SIZED: the flanks are rare, one-off surfaces
       (a specialized widget, a form, a mini list) and auto width
       generalizes for that — the thing brings its own size. So no
       containment (it would strip content-sizing and collapse the
       auto track) and no declared width. Consequence: aside content
       cannot gate on the aside itself; a one-off layout doesn't need
       to. pg-main-aside (ma) is the main-scoped flank — it starts
       under the main header, inboard of the page-scoped pg-aside. */
    & > :where(.pg-aside)       { grid-area: a;  overflow-y: auto; min-height: 0; overscroll-behavior: contain; }
    & > :where(.pg-main-aside)  { grid-area: ma; overflow-y: auto; min-height: 0; overscroll-behavior: contain; }
    & > :where(.pg-footer)      { grid-area: f;  container-type: inline-size; }
  }
  /* .wash — the page's tint: a static, viewport-fixed UNDERLAY of two soft
     pools in the surface's own colour — the element's --bg resolves the
     first, a real child at the next ladder step the second (the engine
     resolves --_bg per element, so a pseudo cannot carry its own hue).
     No blur, no motion; it re-themes with the hue and with day/night.
     (gf-359, Mike: "persistent on all pages from top to bottom") */
  :where(:has(> .wash)) { isolation: isolate; } /* the host is the stacking
     context: the pools paint above ITS surface and under its content */
  :where(.wash) {
    position: fixed; inset: 0; z-index: -1; pointer-events: none;
    --bg: 0.26;
    background: radial-gradient(65% 60% at 18% 6%, var(--_bg), transparent 74%);
    & > :where(*) {
      position: absolute; inset: 0;
      --bg: 0.26; --hue-shift: 30;
      background: radial-gradient(55% 55% at 86% 92%, var(--_bg), transparent 74%);
    }
  }
  /* .hud — full-viewport overlay; click-through container, interactive
     children. Slot classes: t/c/b row × l/c/r column. Pure positioning —
     no paint — which is why it lives in layout, unlike drawer/modal/menu. */
  :where(.hud) {
    position: fixed; inset: 0; display: grid; grid-template-areas: "hud";
    pointer-events: none; z-index: 1;
    /* a hud CLEARS THE BANNER (EventOS gf-527, 2026-10-05): the pg-banner
       is the page's top track — one line and its padding — and a fixed
       hud over a page that shows one starts beneath it, or the mark and
       the doors would paint over the words */
    .page:has(> .pg-banner) ~ & { inset-block-start: 1.5lh; }
    & > :where(*) { grid-area: hud; justify-self: center; align-self: center; pointer-events: auto; }
    /* slot ALIGNMENT lives in @layer utility below: slot classes must beat
       component defaults (e.g. the fab recipe's place-self) — a slot is a
       geometry utility, and utilities cascade after components. */
  }

  /* ── shell anatomy — the standard geometry INSIDE the pg-* regions,
     owned here so every screen composes it instead of redeclaring it. ── */
  /* pg-main is the content stack, ONE consistent shape (gf-39, per Mike):
     the system's own 0.25lh unit as BOTH gap and uniform padding. Safe
     now because pg-main no longer scrolls (core.layout) — the old
     no-block-padding doctrine existed only to protect a sticky head in a
     region-level scroller, and that scroller is gone. --gap overrides. */
  :where(.pg-main) { gap: var(--gap, 0.25lh); padding: 0.25lh; }
  /* THE ONE-ROW GUARANTEE. The title row (a .spread child of the header —
     the class stays for the height rule) is a NOWRAP two-cell grid:
     title+views left, filters+verbs right, sized by need (minmax(0,1fr) /
     auto). Nothing here may ever stack; controls SHRINK instead (the views
     control's labels yield via the container gates, resolved against
     pg-main-header). flex-wrap was how this became two ragged rows. */
  :where(.pg-main-header) > :where(.spread) {
    display: grid; grid-template-columns: minmax(0, 1fr) auto;
    align-items: center; gap: var(--gap, 0.25lh);
    & > :where(:first-child) { min-inline-size: 0; }
  }
  /* the icon rail (a <nav> filling pg-toolbar): tight stack, thin inset.
     (railToolbar's <div class="pg-toolbar"> keeps composition defaults.) */
  :where(nav.pg-toolbar) { gap: 0.1lh; padding: 0.25lh; }

  /* [RETIRED 2026-08] the page-gated labeled-rail restyle — the shell
     never infers rail shape from page width; the rail's declared
     --rail-w (gf-71b: server-stamped) owns it. */
  /* NAV/HEADER ALIGNMENT (gf-12): the nav's identity block mirrors the
     header's two rows — .nav-org is the title row's height by construction
     (avatar = control height at step 0), and the search row's margins
     compress so gap + search + gap lands exactly on the tabs row, putting
     the first <hr> on the header's bottom line. The -0.09lh absorbs the
     px-constant parts (control border, tab underline); measured within
     ±0.6px across the sm/md/lg size settings. */
  :where(.pg-navigation) :where(.nav-search) { margin-block: -0.09lh; }

  /* the rail FOOT anatomy (gf-42, canonical with its .nav-* siblings):
     the account block keeps ONE height across densities — the icon
     rail's bare avatar occupies the expanded card's natural height (two
     text lines + card padding + hairlines), so collapsing never jumps
     the foot — and the version tag centers itself in the foot column. */
  :where(.pg-navigation) :where(.nav-account) { min-block-size: calc(2.7lh + 2px); justify-content: center; }
  :where(.pg-navigation) :where(.nav-version) { align-self: center; }

  /* a spread-column filling the toolbar pins its ends to the region's
     full height (sections top, help bottom) and stays scroll-safe. */
  :where(.pg-toolbar) > :where(.spread-column) { flex: 1; min-block-size: 0; }
  /* toolbar fields (the collection search, filter chips) size to their
     content — .field's fill-the-row default is for forms, not the NOWRAP
     title row (a search input sizes by its size="" attribute here). */
  :where(.pg-main-header) :where(.field) { inline-size: auto; }
}




/* ════════════════════════════════════════════════════════════════════
   theme — ALL colour config. A theme = --hue + two banks of
   (three anchors · flip · st-neut). Everything else is engine tuning.
   ════════════════════════════════════════════════════════════════════ */
@layer theme {
  :root {
    /* ── defaults ── */
    --hue: 255;
    --state: 0;
    --chroma: 1;
    --surf: var(--anchor-zero);        /* until something paints, contrast the paper */
    --surf-bg: 0;
    --cfg-dark: 0;                     /* exported mode flag (downstream layers key on it) */

    /* ── THE CONTENT LADDER (2026-10-01, Mike: the Open Props spelling —
       .content-1 · .content-2 · .content-3): the three widths a surface
       may take, in ch so they resolve in the WEARER's type and grow with
       --type and the text-size preference; 94vw keeps a floating surface
       off the viewport's edges. 1 a column (a wizard station, a confirm,
       a form alone) · 2 reading (a settings leaf, prose, the default
       dialog) · 3 working (a record page's grid, a workbench dialog). */
    --content-1: min(40ch, 94vw);
    --content-2: min(70ch, 94vw);
    --content-3: min(100ch, 94vw);

    /* ── engine tuning (plain numbers; ink-pole L pairs are 0–1) ── */
    --fg-ink-d: 0.16; --fg-ink-l: 0.96;   /* neutral ink L on light / dark hosts */
    --fg-chr-d: 0.45; --fg-chr-l: 0.80;   /* chroma  ink L on light / dark hosts */
    --fg-chroma: 0.11;
    --st-hover: 0.35; --st-fg-gain: 0.12;
    --border-mix: 25%;
    --focus-boost: 1.6;

    /* ── the banks — sources; live values default to light ── */
    --lt-flip: 0.66; --lt-st-neut: 1;
    --lt-neg:  oklch(84% 0.006 0);
    --lt-zero: oklch(99% 0.004 0);
    --lt-pos:  oklch(52% 0.160 0);
    --dk-flip: 0.90; --dk-st-neut: 3;
    --dk-neg:  oklch(6%  0.012 0);
    --dk-zero: oklch(22% 0.010 0);
    --dk-pos:  oklch(68% 0.170 0);

    --flip: var(--lt-flip); --st-neut: var(--lt-st-neut);
    --anchor-neg: var(--lt-neg); --anchor-zero: var(--lt-zero); --anchor-pos: var(--lt-pos);

    /* semantic hues — .suc/.inf/.wrn/.dgr consume these */
    --hue-suc: 145; --hue-inf: 255; --hue-wrn: 75; --hue-dgr: 25;

    color-scheme: light-dark;
  }

  /* ── theme switch — OS default · runtime attribute · static island.
     The OS rule is guarded against every explicit trigger, so a manual
     light choice wins over prefers-color-scheme: dark. The dark remap is
     stated twice (a media query can't join a selector list) — change both. */
  @media (prefers-color-scheme: dark) {
    :root:not([data-ui-theme]):not(.light):not(.dark) {
      color-scheme: dark; --cfg-dark: 1;
      --flip: var(--dk-flip); --st-neut: var(--dk-st-neut);
      --anchor-neg: var(--dk-neg); --anchor-zero: var(--dk-zero); --anchor-pos: var(--dk-pos);
    }
  }
  /* :root[…] (0,1,1) — MUST outrank the :root base block (0,1,0) AND the
     OS media rule; the old :where() wrapper had specificity ZERO, so an
     explicit dark could never beat the base light anchors (dark rendered
     light; only System worked). Manual choice > OS > base, by specificity. */
  :root[data-ui-theme="dark"], .dark {
    color-scheme: dark; --cfg-dark: 1;
    --flip: var(--dk-flip); --st-neut: var(--dk-st-neut);
    --anchor-neg: var(--dk-neg); --anchor-zero: var(--dk-zero); --anchor-pos: var(--dk-pos);
  }
  :root[data-ui-theme="light"], .light {
    color-scheme: light; --cfg-dark: 0;
    --flip: var(--lt-flip); --st-neut: var(--lt-st-neut);
    --anchor-neg: var(--lt-neg); --anchor-zero: var(--lt-zero); --anchor-pos: var(--lt-pos);
  }
}


/* ════════════════════════════════════════════════════════════════════
   theme (type slice) — ALL type config, including everything that used
   to hide in @property initial-values.
   ════════════════════════════════════════════════════════════════════ */
@layer theme {
  :root {
    --scale: 1;

    /* the scale shape. Default = "md": type 0 ≈ 14.5px phone / 15.5px
       desktop — just under 16px, so editable fields lean on the
       input-only font floor to dodge iOS focus-zoom. */
    --cfg-type-min: 0.90625rem;
    --cfg-type-max: 0.96875rem;
    --cfg-ratio: 1.2;
    --cfg-control-lead: 2.06;          /* line-box 1.5 + 2×0.28 vertical padding */
    --cfg-fluid-min-vp: 375px;
    --cfg-fluid-max-vp: 1280px;

    /* optical weight */
    --cfg-wght-base: 400; --cfg-wght-step: 90; --cfg-wght-max: 850;

    --font-ui:      "Roboto", system-ui, sans-serif;
    --font-mono:    "Roboto Mono", ui-monospace, monospace;
    --font-display: "Roboto", system-ui, sans-serif;
  }

  /* ── size switch — reshapes the scale (base + ratio) ── */
  [data-ui-size="sm"] { --cfg-type-min: 0.780rem; --cfg-type-max: 0.840rem; --cfg-ratio: 1.125; }  /* dense · Major2nd */
  [data-ui-size="md"] { --cfg-type-min: 0.840rem; --cfg-type-max: 0.920rem; --cfg-ratio: 1.125; }  /* denser default · Major2nd */
  [data-ui-size="lg"] { --cfg-type-min: 0.920rem; --cfg-type-max: 1.000rem; --cfg-ratio: 1.200; }  /* roomy · Minor3rd */
  [data-ui-size="xl"] { --cfg-type-min: 1.125rem; --cfg-type-max: 1.250rem; --cfg-ratio: 1.250; }  /* marketing · Major3rd */

  /* ── font switch — orthogonal to skin; consumers load the webfonts ── */
  [data-ui-font="grotesk"]  { --font-ui:"Hanken Grotesk",system-ui,sans-serif; --font-display:"Hanken Grotesk",sans-serif; --font-mono:"Spline Sans Mono",ui-monospace,monospace; font-family:var(--font-ui); }
  [data-ui-font="neo"]      { --font-ui:"Inter",system-ui,sans-serif; --font-display:"Inter",sans-serif; --font-mono:"JetBrains Mono",ui-monospace,monospace; font-family:var(--font-ui); }
  [data-ui-font="serif"]    { --font-ui:"Newsreader",Georgia,serif; --font-display:"Newsreader",Georgia,serif; --font-mono:"Spline Sans Mono",ui-monospace,monospace; font-family:var(--font-ui); }
  [data-ui-font="techno"]   { --font-ui:"Space Grotesk",system-ui,sans-serif; --font-display:"Space Grotesk",sans-serif; --font-mono:"Space Mono",ui-monospace,monospace; font-family:var(--font-ui); }
  [data-ui-font="humanist"] { --font-ui:"IBM Plex Sans",system-ui,sans-serif; --font-display:"IBM Plex Sans",sans-serif; --font-mono:"IBM Plex Mono",ui-monospace,monospace; font-family:var(--font-ui); }
}


/* ════════════════════════════════════════════════════════════════════
   theme — cross-concern config that fits no core.
   ════════════════════════════════════════════════════════════════════ */
@layer theme {
  :root {
    --cfg-radius: 9px;      /* geometry — consumed by components */
    --cfg-motion: 1;        /* motion multiplier (a later formal pass) */
  }
  [data-ui-radius="0"] { --cfg-radius: 0px; }
  [data-ui-radius="1"] { --cfg-radius: 6px; }
  [data-ui-radius="2"] { --cfg-radius: 12px; }
  [data-ui-radius="3"] { --cfg-radius: 22px; }
  [data-ui-motion="off"]   { --cfg-motion: 0 }
  [data-ui-motion="on"]    { --cfg-motion: 1 }
  [data-ui-motion="debug"] { --cfg-motion: 10 }
  /* the ::view-transition pseudos hang off <html>, but the pref attribute
     rides the #html div — :has() carries it up so motion off/debug governs
     the cross-document transition too (2026-09-01) */
  :root:has([data-ui-motion="off"])   { --cfg-motion: 0 }
  :root:has([data-ui-motion="debug"]) { --cfg-motion: 10 }
  @media (prefers-reduced-motion: reduce) { :root, [data-ui-motion] { --cfg-motion: 0 } }
}


/* ════════════════════════════════════════════════════════════════════
   base (type slice) — the element → step mapping.
   ════════════════════════════════════════════════════════════════════ */
@layer base {
  /* PIN the root: the scale drives everything BELOW html; html stays a
     fixed 16px so `rem` never shifts and cfg-type-min applies ONCE. */
  :where(html) { font-family: var(--font-ui); font-size: 100%; }

  :where(h1) { --type: 5 } :where(h2) { --type: 4 } :where(h3) { --type: 3 }
  :where(h4) { --type: 2 } :where(h5) { --type: 1 } :where(h6) { --type: 0 }
  :where(h6) { font-weight: 600; }   /* step 0 = body weight; nudge so it reads as a heading */
  :where(small) { --type: -1 }
  :where(b, strong) { font-weight: 700; }
  :where(code, kbd, samp, pre) { font-family: var(--font-mono); }
}


/* ════════════════════════════════════════════════════════════════════
   base — bare-element defaults consuming the cores.
   ════════════════════════════════════════════════════════════════════ */
@layer base {
  /* the document surface — a publisher */
  :where(body) { --bg: -0.5; background-color: var(--_bg); --surf: var(--_bg); --surf-bg: var(--bg); min-block-size: 100svh; }

  :where(svg) { color: currentColor; }   /* the bridge: SVG ink = engine ink */
  /* half a CSS pixel (feedback #138): a constant hairline — one device
     pixel on a dense screen, and never the full-then-faint flicker a
     1px rule shows across DPRs */
  :where(hr)  { border: 0; block-size: 0.5px; background: var(--border); margin-block: 1lh; }
  /* .vr — THE VERTICAL RULE (EventOS gf-591, Mike 2026-10-07): hr's line
     stood on end, on the inline-start edge of the element that begins a
     new column — a grid area, a flex item — so the seam rides the
     content and needs no track of its own. hr's ink, hr's weight. */
  :where(.vr) { border-inline-start: 0.5px solid var(--border); padding-inline-start: 0.5em; }

  /* links — engine-native: chromatic ink, hover = full chroma + hue turn.
     The transition is why --fg / --hue-shift stay registered. */
  :where(a:not([class])) {
    --fg: 0.85; text-decoration: underline; text-underline-offset: 0.15em;
    transition:
      --fg        calc(var(--cfg-motion) * 0.2s) ease-out,
      --hue-shift calc(var(--cfg-motion) * 0.2s) ease-out;
  }
  :where(a:not([class])):hover { --fg: 1; --hue-shift: 30; }

  /* inline <code> — a quiet recessed chip (consumer-painter: reads --lift) */
  :where(code) {
    --lift: -0.09; --type: -0.5;
    padding: 0.1em 0.35em;
    border-radius: max(2px, calc(var(--cfg-radius) - 3px));
    border: 1px solid var(--border);
    background-color: var(--_bg); --surf: var(--_bg);
  }
  :where(pre) {
    --bg: 0.08; --type: -0.5;
    padding: 0.5lh 0.7em; border-radius: var(--cfg-radius);
    background-color: var(--_bg); --surf: var(--_bg); --surf-bg: var(--bg);
    overflow-x: auto;
    & > :where(code) { --type: inherit; padding: 0; border: 0; border-radius: 0; background: none; }
  }

  /* native fields — themed surface + border; height locked to the shared
     control height. The 16px glyph floor exists ONLY to dodge iOS focus-zoom,
     which is a coarse-pointer behavior — so it now applies on coarse pointers
     only. On fine pointers the floor made every field ignore its --type (body
     inputs were already floored, so stepping a form to -1 changed nothing);
     desktop controls now scale truly with their step, and touch keeps the
     zoom dodge. */
  :where(input:not([type="checkbox"]):not([type="radio"]):not([type="range"]), textarea, select) {
    --bg: 0; background-color: var(--_bg); --surf: var(--_bg); --surf-bg: var(--bg);
    border: 1px solid var(--border); border-radius: var(--cfg-radius);
    font-size: var(--_font-size);
    padding-inline: 0.8em;
  }
  @media (pointer: coarse) {
    :where(input:not([type="checkbox"]):not([type="radio"]):not([type="range"]), textarea, select) {
      font-size: max(16px, var(--_font-size));
    }
  }
  :where(input:not([type="checkbox"]):not([type="radio"]):not([type="range"]), select:not([multiple])) {
    block-size: var(--_control-h); padding-block: 0;
  }
  :where(textarea) { min-block-size: var(--_control-h); padding-block: 0.28em; }

  /* ── file input ──────────────────────────────────────────────────────
     The native control is two things in one box: a button and a filename.
     Unstyled it reads as a foreign object — a grey OS button wedged inside a
     themed field. The outer box already takes the field recipe above; this
     gives the button half the button recipe by hand, because a pseudo-element
     cannot take a class and so cannot inherit one.
     Sized a touch shorter than the field it sits in, so it reads as nested
     rather than as a second control butted against the first. All the nesting
     geometry rides the lh rhythm (one 0.2lh inset ring around the button),
     so the button scales with the same line-height control as every other
     block measure.
     The ring is realized as margin-block on the button, NOT by centering the
     container: Blink lays the button + filename out as one inline line box
     and ignores flex/align-content on the input's internals, and that line
     box top-pins in the fixed-height field. An atomic inline's MARGIN box is
     what sizes the line box, so a margin ring of (0.2lh − 1px border term —
     the same constant --_control-h carries) grows the line box to exactly
     the field's content height and the button self-centers in any engine. */
  :where(input[type="file"]) {
    display: flex; align-items: center;
    padding-inline: 0.2lh;
    &::file-selector-button {
      font: inherit; font-weight: 600; line-height: 1;   /* v3.1.4: the words fit the button at every --type (EventOS #730: a step down clipped them) */
      block-size: calc(var(--_control-h) - 0.4lh);
      margin-block: calc(0.2lh - 1px);
      padding-inline: 0.9em; margin-inline-end: 0.5lh;
      border: 0; border-radius: calc(var(--cfg-radius) - 0.15lh);
      background-color: color-mix(in oklch, currentColor 12%, transparent);
      color: inherit; cursor: pointer;
      transition: background-color calc(var(--cfg-motion) * 0.12s) ease;
    }
    &::file-selector-button:hover {
      background-color: color-mix(in oklch, currentColor 20%, transparent);
    }
  }

  /* ── semantic encoding (see semantic-encoding.md) ──
     Two independent axes: color = valence, typography = certainty/time. */

  /* certainty: an estimate/projection reads as "not yet real". */
  :where(.est) { font-style: italic; }
  :where(.est)::before { content: "~"; opacity: 0.7; }

  /* time: a past period is quieted ink — legible, clearly behind. Hue is
     untouched, so a past delta keeps its valence colour. */
  :where(.past) { --fg: -0.55; }

  /* valence for NUMBERS — a self-contained formatting family, separate from
     the .suc/.dgr hue-LOCK helpers (those pin hue for buttons/graphs and are
     a different concern). These set a high-chroma --fg toward a stated hue,
     so the ink itself reads green/red. The author picks the class from
     trend × polarity (a falling cost is favorable → .num-good). The number
     the delta annotates stays plain ink; only the delta gets a class. */

  /* .num — a numeric cluster (value + unit + delta) that reads as one unit:
     never wraps, digits align in columns (tabular), delta rides the baseline. */
  :where(.num) {
    white-space: nowrap;
    font-variant-numeric: tabular-nums;
    & > :where(.delta) { margin-inline-start: 0.4em; }
  }
  :where(.delta) { --type: -1; font-variant-numeric: tabular-nums; white-space: nowrap; }
  :where(.num-good) { --hue-lock: var(--hue-suc); --fg: 0.85; }
  :where(.num-bad)  { --hue-lock: var(--hue-dgr); --fg: 0.85; }
  :where(.num-flat) { --fg: -0.6; }
  /* .number — a live numeric readout: mono, tabular, right-aligned, a fixed
     width of --digits characters (default 3). A value that changes never
     moves its neighbours. NOTHING SHIFTS is a law of the engine: a count,
     a clock, a slider's value wears .number. (2026-09-29) */
  :where(.number) { display: inline-block; font-family: var(--font-mono); font-variant-numeric: tabular-nums; min-inline-size: calc(var(--digits, 3) * 1ch); text-align: end; white-space: nowrap; }

  /* .focus-fab — the expand/collapse control docked bottom-right of pg-main.
     Shows the expand icon normally; when focus mode is active (page has the
     checked toggle) it swaps to the collapse icon. Quiet until hover. */
  :where(.focus-fab) {
    position: absolute; inset-block-end: 0.6lh; inset-inline-end: 0.6lh;
    z-index: 2; cursor: pointer;
    display: inline-flex; align-items: center; justify-content: center;
    inline-size: var(--_control-h); block-size: var(--_control-h);
    border: 1px solid var(--border); border-radius: var(--cfg-radius);
    --bg: -0.7; background-color: var(--_bg); --surf: var(--_bg); --surf-bg: var(--bg);
    --fg: -0.6;
    & > :where(svg) { inline-size: 1.1em; block-size: 1.1em; }
    &:hover { --fg: -1; --lift: 0.1; }
    & > :where(.i-collapse) { display: none; }
  }
  :where(.page[data-ui-state="focus"]) :where(.focus-fab) {
    & > :where(.i-expand) { display: none; }
    & > :where(.i-collapse) { display: inline; }
  }

  /* .search-box — icon + input + optional kbd as ONE bordered control at
     control height. The label is the control; the input inside is stripped
     (borderless, transparent) so the box's own surface and border show. */
  :where(.search-box) {
    display: inline-flex; align-items: center; gap: 0.4em;
    box-sizing: border-box;
    block-size: var(--_control-h);
    padding-inline: 0.7em;
    border: 1px solid var(--border);
    border-radius: var(--cfg-radius);
    & > :where(svg) { inline-size: 1.05em; block-size: 1.05em; flex: 0 0 auto; opacity: 0.6; }
    & > :where(input) {
      flex: 1 1 auto; min-inline-size: 0; inline-size: 100%;
      block-size: 100%; margin: 0; padding: 0;
      border: 0; background: none; box-shadow: none;
      font: inherit; color: inherit;
    }
    & > :where(input:focus) { outline: 0; }
    & > :where(kbd) { flex: 0 0 auto; opacity: 0.55; }
    &:focus-within { border-color: var(--focus); }
  }

  :where(input[type="checkbox"], input[type="radio"]) {
    --fg: 0.8; accent-color: currentColor;
    inline-size: 1.15em; block-size: 1.15em; flex: 0 0 auto;
    @media (pointer: coarse) { font-size: max(1.4rem, var(--_font-size)); }
  }
  :where(input[type="radio"]) { border-radius: 50%; }

  /* ── the one-time code — ONE input drawn as six cells (Mike, 2026-09-24):
     the attribute that is the semantics is the selector, so a phone's
     autofill lands the whole code in one stroke. A monospace glyph is
     1ch wide; letter-spacing makes each glyph own a cell plus a gap,
     the start padding centres the first, the gradient paints six cells
     of the field's own surface and no seventh. The engine's focus ring
     rings the whole field — a single control, one ring. ── */
  /* the code's host measures the room (EventOS gf-476): six cells and
     five gaps fit any width the field is given — a column-rung card was
     clipping the sixth */
  :where(label, fieldset):has(> input[autocomplete="one-time-code"]) { container-type: inline-size; }
  :where(input[autocomplete="one-time-code"]) {
    --_gap: 0.4em; --_cell: min(2.4em, (100cqi - 5 * var(--_gap)) / 6);
    font-family: var(--font-mono); font-variant-numeric: tabular-nums; --type: 2;
    inline-size: calc(var(--_cell) * 6 + var(--_gap) * 6); max-inline-size: 100%;
    block-size: calc(var(--_cell) * 1.25); padding: 0;
    padding-inline-start: calc((var(--_cell) - 1ch) / 2);
    letter-spacing: calc(var(--_cell) + var(--_gap) - 1ch);
    text-align: start; caret-color: var(--focus);
    border: 0; border-radius: 0; background-color: transparent;
    background-image: repeating-linear-gradient(90deg,
      var(--_bg) 0 var(--_cell),
      transparent var(--_cell) calc(var(--_cell) + var(--_gap)));
    background-size: calc(var(--_cell) * 6 + var(--_gap) * 5) 100%;
    background-repeat: no-repeat;
    &::placeholder { color: transparent; }
  }


  /* scrollbars — SCOPED to the things that scroll (moved off `*`) */
  :where(html, .pg-main, .pg-navigation, .pg-aside, .pg-toolbar, .scroll-x, .scroll-y, pre, textarea) {
    scrollbar-width: thin;
    scrollbar-color: var(--border) transparent;
  }

  /* smooth scrolling — the scroll containers, not just html: anchor jumps
     (the n3 landmark rail) scroll pg-main. Reduced-motion gets instant. */
  :where(html, .pg-main, .pg-navigation, .pg-aside, .pg-toolbar) {
    scroll-behavior: smooth;
  }
  @media (prefers-reduced-motion: reduce) {
    :where(html, .pg-main, .pg-navigation, .pg-aside, .pg-toolbar) {
      scroll-behavior: auto;
    }
  }
}


/* ════════════════════════════════════════════════════════════════════
   composition — the layout primitives (was the second half of core.layout).
   Pure geometry, zero specificity; a block placed inside one keeps its own
   insides, because block cascades later.
   ════════════════════════════════════════════════════════════════════ */
@layer composition {  /* ── composition primitives — zero specificity, pure geometry.
     Gap = 0.25lh: spacing derives from line-height (Type owns rhythm).
     [cleanup: calc(0.25 * 1lh) → 0.25lh — identical value, less noise] */
  /* every composition reads --gap (registered non-inheriting): rhythm is a
     published knob, not an inline gap declaration. Unset → the default.

     THE RHYTHM LADDER (gf-231, per Mike: "keep the css up to date and
     prevent drift" — 419 inline gaps across 23 values had crept in).
     --gap takes ONLY these steps; a usage site picks a rung, never a
     number:
       block rhythm (lh)  0 · 0.25lh (default) · 0.5lh (breathing) · 1lh (section)
       inline rows  (em)  0.25em (chips, buttons) · 0.5em · 1em
     view/ratchet_test.go counts off-ladder values per .templ; the
     count may only go down. */
  :where(.column) { display: flex; flex-direction: column; gap: var(--gap, 0.25lh); }
  :where(.row)    { display: flex; flex-direction: row; flex-wrap: wrap; align-items: center; gap: var(--gap, 0.25lh); }
  :where(.split)  { display: grid; grid-template-columns: 1fr 1fr; gap: var(--gap, 0.25lh); }
  :where(.spread) { display: flex; flex-direction: row; flex-wrap: wrap; justify-content: space-between; align-items: center; gap: var(--gap, 0.25lh); }
  :where(.spread-column) { display: flex; flex-direction: column; justify-content: space-between; gap: var(--gap, 0.25lh); }

  :where(.lcr) {
    display: grid; grid-template-columns: 1fr auto 1fr; align-items: center; gap: var(--gap, 0.25lh);
    & > :where(:first-child:not(style):not(script))      { justify-self: start  }
    & > :where(:nth-child(2 of :not(style):not(script))) { justify-self: center }
    & > :where(:last-child:not(style):not(script))       { justify-self: end    }
  }

  :where(.flank, .flank-start) {
    display: flex; flex-direction: row; align-items: center; gap: var(--gap, 0.25lh);
    & > :where(:first-child:not(style):not(script)) { flex: 0 0 auto }
    & > :where(:last-child:not(style):not(script))  { flex: 1 1 auto; min-inline-size: 0 }
  }
  :where(.flank-end) {
    display: flex; flex-direction: row; align-items: center; gap: var(--gap, 0.25lh);
    & > :where(:first-child:not(style):not(script)) { flex: 1 1 auto; min-inline-size: 0 }
    & > :where(:last-child:not(style):not(script))  { flex: 0 0 auto; min-inline-size: 0 }
  }

  /* .frame — a box at a ratio its one child fills; --ratio is the knob
     (16 / 9 by default; a block's cell on the lab bench rides it, gf-608) */
  :where(.frame) { aspect-ratio: var(--ratio, 16 / 9); overflow: hidden; & > * { inline-size: 100%; block-size: 100%; object-fit: cover } }
  /* .block — A SELF-MANAGING CELL (EventOS gf-608, Mike 2026-10-08: "each
     block is able to manage itself … container queries for portrait or
     landscape"). The block is a SIZE query container with ONE child, its
     body: a flex column in portrait, a row in landscape (wider than 3:2)
     — the words first, the big thing beside them. A block fills the
     cell a grid track, a .frame or a .stress hands it and never sizes
     itself: size containment collapses it under an auto-height parent.
     Inside it the tiers answer by its width and the gates below by its
     orientation and height; a block queries nothing about itself, which
     is why the body is a child. */
  /* THE CELL (v3.2.0, EventOS gf-753: the shape layer retired): a size
     container the page may query — `@container (width < 96ch)` on a block's
     parent to stack a summary on a phone — never a shape. A block fills it. */
  :where(.cell) { container-type: size; min-inline-size: 0; min-block-size: 0; overflow: hidden; }
  :where(.block) { display: grid; container-type: size; container-name: block; min-inline-size: 0; min-block-size: 0; overflow: hidden; }   /* [mike.css] the gf-608 alias */
  :where(.block) > :where(*) { display: flex; flex-direction: column; gap: var(--gap, 0.25lh); min-inline-size: 0; min-block-size: 0; }
  @container block (aspect-ratio > 3/2) { :where(.block) > :where(*) { flex-direction: row; align-items: stretch; } }
  :where(.grid)  { display: grid; gap: var(--gap, 0.25lh); grid-template-columns: repeat(auto-fit, minmax(min(100%, var(--grid-min, 16rem)), 1fr)); }
  /* .grid > .row-wide — the HERO cell (feedback #67): one child takes the
     whole row at every column count (1 / -1, never a span, so a
     one-column phone grid never grows an implicit column). */
  :where(.grid > .row-wide) { grid-column: 1 / -1; }
  :where(.ngrid) { display: grid; gap: var(--gap, 0.25lh); grid-template-columns: repeat(var(--cols, 2), 1fr); grid-template-rows: repeat(var(--rows, 2), 1fr); grid-auto-rows: auto; }
  /* ── bento — THE BIG-PAGE GRID (EventOS gf-571, Mike 2026-10-07): cells
     of --cell fill the width and flow dense; a child spans two with
     .span-2 or takes the row with .row-wide; under two cells' width the
     span falls, so a phone is one column. Nothing else. ── */
  :where(.bento) { display: grid; gap: var(--gap, 0.5lh); grid-template-columns: repeat(auto-fill, minmax(min(100%, var(--cell, 18rem)), 1fr)); grid-auto-flow: dense; container-type: inline-size; }
  :where(.bento) > :where(.span-2) { grid-column: span 2; }
  :where(.bento) > :where(.row-wide) { grid-column: 1 / -1; }
  @container (inline-size < 38rem) { :where(.bento) > :where(.span-2) { grid-column: span 1; } }
  :where(.stack) { display: grid; grid-template-areas: "stack"; & > * { grid-area: stack } }
  /* .rise — an EXPLICIT viewport share (2026-08-31, per Mike: explicit
     dynamic units, never flex negotiation, no magic lengths): the block
     takes --rise hundredths of the small viewport as a MINIMUM, so
     content taller than its share (a phone) pushes past it and the
     canvas scrolls — the honest degradation. One number, one meaning. */
  :where(.rise)  { min-block-size: calc(var(--rise, 50) * 1svb); flex-shrink: 0; }
  /* flex-shrink 0 (2026-09-01): in a flex column a .rise row could be
     SHRUNK below its own content when the page ran long (the stacked
     phone split painted its second card over the list) — the share is a
     floor, and content taller than it means the canvas scrolls. */
  :where(.track) { display: grid; grid-auto-flow: column; grid-auto-columns: minmax(var(--track-min, 6rem), max-content); gap: var(--gap, 0.25lh); overflow-x: auto; }

  :where(.hero) {
    display: grid;
    grid-template: "t t t" auto "l m r" 1fr "b b b" auto / auto 1fr auto;
    height: 100%; overflow: hidden;
    & > :where(.top)    { grid-area: t }
    & > :where(.bottom) { grid-area: b }
    & > :where(.left)   { grid-area: l; overflow-y: auto; min-height: 0 }
    & > :where(.main)   { grid-area: m; overflow-y: auto; scrollbar-gutter: stable; min-height: 0 }
    & > :where(.right)  { grid-area: r; overflow-y: auto; min-height: 0 }
  }

}


/* ════════════════════════════════════════════════════════════════════
   components — the representative set, on the new painter.
   ════════════════════════════════════════════════════════════════════ */
@layer block {

  /* ── button — bare <button> is the component; .btn = same recipe on
     any element (an <a href> keeps real link semantics). Default carries
     ONLY a relative --lift; emphasis is the .pri/.sec family (skin).
     Consumer-painter: publishes --surf, never --surf-bg. ─────────────── */
  :where(button, .btn) {
    text-decoration: none;
    --lift: 0.2;
    --fg: var(--_sem-ink, -0.85);
    display: inline-flex; align-items: center; justify-content: center;
    gap: 0.45em;
    min-block-size: var(--_control-h);
    padding: 0 0.8em;
    border-radius: var(--cfg-radius);
    border: 1px solid var(--border);
    font-weight: var(--cfg-btn-weight, 500);
    user-select: none; -webkit-user-select: none;
    & > svg { inline-size: 1.15em; block-size: 1.15em; flex: 0 0 auto; }
  }
  /* paint at ZERO specificity so later 0-spec recipes (tabs-underline,
     .menu rows) can neutralize it; .sec repaints itself in skin, which
     outranks this layer regardless. */
  :where(button:not(.sec), .btn:not(.sec)) { background-color: var(--_bg); --surf: var(--_bg); }
  :where(button[aria-pressed="true"], .btn[aria-pressed="true"]) { --lift: 0.55; --fg: 1; }

  /* ── .field — wear the native field's BOX on any element: a popover-trigger
     <button>, a Rocket component's trigger, any custom control. Same surface,
     border, control-height, and left-aligned text as an <input>, so a trigger
     reads as a field, not a button. Inside a form it inherits the recessed
     tint like real fields (see the form rules). Layout (full-width in a label)
     comes from the form label-grid, which now counts .field as a control. ── */
  :where(.field) {
    --bg: 0; background-color: var(--_bg); --surf: var(--_bg); --surf-bg: var(--bg);
    --lift: initial;
    display: inline-flex; align-items: center; justify-content: flex-start; gap: 0.5em;
    inline-size: 100%; block-size: var(--_control-h);
    padding: 0 0.8em;
    border: 1px solid var(--border); border-radius: var(--cfg-radius);
    font: inherit; font-weight: inherit; text-align: start; color: inherit;
  }
  /* custom-element hosts (Rocket) are inline by default — let them fill the
     field row when their trigger is a .field. */
  :where(date-picker, day-picker, multi-select, single-select):has(.field) { display: block; inline-size: 100%; }
  /* the combobox pair (gf-191): host is the control slot; its input or
     committed .field chip-box fills it. */
  :where(combo-box, combo-select, group-pick) { display: block; inline-size: 100%; }
  /* hold-confirm's host box belongs HERE, not in its rocket's injected
     sheet (gf-198 FOUC ledger: client-state hosts get their display
     rule in the engine so the pre-upgrade frame is already right). */
  :where(hold-confirm) { display: inline-flex; position: relative; }
  :where(element-pick) { display: inline-flex; } /* gf-272: the feedback HUD's picker host */
  :where(bug-report) { display: contents; }      /* gf-277: wraps the HUD form, adds no box */
  :where(combo-box, combo-select, group-pick) > :where(input) { inline-size: 100%; }
  /* the committed chip truncates inside its field (gf-237b): a COLA
     brand name can run to eighty characters, and a chip that will not
     shrink hands the whole dialog a horizontal scrollbar. */
  :where(combo-box, combo-select, group-pick) > :where(.field) > :where(.tag) { min-inline-size: 0; overflow: hidden; flex: 0 1 auto; }
  /* the clear × sits at the END of the field, small — a search bar's
     clear (feedback #20); the chip's glyph is a declared box */
  :where(combo-box, combo-select, group-pick) > :where(.field) > :where([data-cb-clear], [data-gp-clear]) { margin-inline-start: auto; --type: -2; --fg: -0.5; }
  :where(combo-box, combo-select, group-pick) > :where(.field) > :where(.tag) > :where(span) > :where(svg) { inline-size: 1em; block-size: 1em; flex: 0 0 auto; }
  :where(combo-box, combo-select, group-pick) > :where(.field) > :where(.tag) > :where(span) { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
  /* the calculator host paints its own card; it just needs to be a block */
  :where(mini-calc) { display: block; }
  /* icon-only: an EXPLICIT class (gf-72) — the old aria-label-inferred
     recipe made styling ride an accessibility attribute, and buttons
     started dropping their labels to dodge the geometry. .icon declares
     the shape; aria-label goes back to meaning accessibility. */
  /* ── .link — the LINK recipe on any element (gf-107, per Mike): a
     command that READS as navigation (the jump cells) keeps its honest
     <button> and wears link voice. Mirrors the bare-link rules in base;
     placed after the button recipe so the flat geometry wins by order. */
  :where(.link) {
    min-block-size: 0; padding: 0; border: 0; background: transparent;
    display: inline; font-weight: inherit;
    --fg: 0.85;
    text-decoration: underline; text-underline-offset: 0.15em;
    transition:
      --fg        calc(var(--cfg-motion) * 0.2s) ease-out,
      --hue-shift calc(var(--cfg-motion) * 0.2s) ease-out;
  }
  :where(.link):hover { --fg: 1; --hue-shift: 30; }

  :where(button, .btn):where(.icon) {
    min-block-size: var(--_control-h); min-inline-size: var(--_control-h); padding: 0; aspect-ratio: 1;
    /* a SQUARE control must never stretch: in a stretching column the
       cross-axis fill + aspect-ratio ballooned the whole button. */
    align-self: center;
  }
  /* ── lock — THE LOCK (EventOS gf-559): an icon button whose shackle
     swings open as it is pressed; aria-pressed is the state, the path's
     d transitions where the browser can (Chromium, Firefox), the motion
     is --cfg-motion's. The glyph is lucide's lock, the open form its
     lock-open shackle. */
  :where(button.lock, .btn.lock) > :where(svg) > :where(path:last-child) { transition: d calc(var(--cfg-motion) * 0.3s) ease; }
  :where(button.lock, .btn.lock):where([aria-pressed="true"]) > :where(svg) > :where(path:last-child) { d: path("M7 11V7a5 5 0 0 1 9.9-1"); }
  :where(.btn-touch):where(button, .btn) {
    min-block-size: max(44px, var(--_control-h)); min-inline-size: max(44px, var(--_control-h));
    aspect-ratio: 1; padding: 0;
  }

  /* ── card / surface — publishers ── */
  :where(.card, .Card) {
    --bg: 0;
    padding: 0.4lh 0.5em;
    border-radius: var(--cfg-radius);
    border: 1px solid var(--border);
    background-color: var(--_bg); --surf: var(--_bg); --surf-bg: var(--bg);
  }
  :where(.Card) { border-color: var(--focus); }
  /* .veil — a glass card laid over its .stack sibling (Mike, 2026-10-02:
     the lab's "column glass card" veil as one word): the card's edge, the
     frosted surface, its content centred both ways. What it covers stays
     in place underneath; lift it by removing it (data-show, or the server
     leaving it out). */
  :where(.veil) {
    display: flex; flex-direction: column; align-items: center; justify-content: center;
    gap: var(--gap, 0.5lh); padding: 1lh 1em; text-align: center;
    border: 1px solid var(--border); border-radius: var(--cfg-radius);
    background-color: color-mix(in oklch, var(--_bg) calc(var(--glass-alpha, 0.65) * 100%), transparent);
    -webkit-backdrop-filter: blur(var(--glass-blur, 0.5lh));
    backdrop-filter: blur(var(--glass-blur, 0.5lh));
    --surf: var(--_bg); --surf-bg: var(--bg);
  }
  /* .surface — a lift step with no drawn line; consumer-painter (does NOT
     republish --surf-bg — the cycle rule), so nested surfaces step off
     the nearest absolute surface, not off each other. */
  :where(.surface, .Surface) {
    --lift: 0.08;
    padding: 0.4lh 0.5em; border-radius: var(--cfg-radius);
    background-color: var(--_bg); --surf: var(--_bg);
  }
  :where(.Surface) { --lift: 0.16; }

  /* ── tag ── */
  /* a TAG that is a BUTTON (the lens pills) keeps tag geometry — the
     control-height floor made every pill a full button tall (gf-105) */
  :where(button):where(.tag) { min-block-size: 0; padding: 0.1em 0.4em; }


  :where(.tag) {
    --bg: 0.16; --fg: var(--_sem-ink, -0.85); --type: -2;
    display: inline-flex; align-items: center; gap: 0.3em;
    padding: 0.1em 0.4em;
    border: 1px solid var(--border);
    border-radius: calc(var(--cfg-radius) / 2);
    background-color: var(--_bg); --surf: var(--_bg); --surf-bg: var(--bg);
    white-space: nowrap; user-select: none; -webkit-user-select: none;
    & > svg { inline-size: 1em; block-size: 1em; flex: 0 0 auto; }
  }
  :where(.tag[data-ui-state="off"], .tag[aria-disabled="true"]) { --bg: 0.1; --fg: -0.4; --chroma: 0.15; }

  /* ── nav items — two shapes, consumer-painters, state rides ARIA.
     [2026-08-12 trim] margin:0 / background:none / text-decoration:none
     dropped — the reset already provides all three. color:inherit
     dropped for cause: components outranks core.color, so that declared
     color was DEFEATING the element's own --fg ink — the knobs below are
     real now. border:0 and font:inherit are NOT belts and stay: they
     neutralize the button recipe (1px border, weight 500) so a
     <button class="nav-item"> renders like an <a class="nav-item">. ── */
  :where(.nav-item, .nav-icon) {
    --lift: 0.0; --fg: -0.75;
    border: 0;
    border-radius: var(--cfg-radius);
    font: inherit; cursor: pointer;
    background-color: var(--_bg); --surf: var(--_bg);
    /* the glyph is a DECLARED box (gf-72b): flex-frozen AND exempt from
       the reset's fluid max-inline-size cap — a scrollbar or any other
       inline squeeze must never shrink it (the reset's cap is for
       content images, not anatomy glyphs). */
    & > svg { flex: 0 0 auto; max-inline-size: none; }
  }
  /* THE CONTROL-HEIGHT DOCTRINE, applied: block-size comes from
     --_control-h and content centres, so the line box never enters the
     box math — a labeled item and an icon-only item are the SAME
     height at every size step (padding-block would re-admit the line
     box; it stays 0). justify-content joins the button-recipe
     neutralizations: the button recipe centres, anchors don't — left
     is the rail's alignment in both densities, so the glyph never
     moves when the label yields. Labels ellipsize inside the declared
     rail width. */
  :where(.nav-item) {
    display: flex; align-items: center; justify-content: flex-start; gap: 0.6em;
    min-block-size: var(--_control-h);
    padding: 0 0.6em; text-align: start;
    & > svg { inline-size: 1.15em; block-size: 1.15em; }
    & > :where(span) { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
    & > :where(.tag) { margin-inline-start: auto; }
  }
  :where(.nav-icon) {
    display: flex; flex-direction: column; align-items: center; justify-content: center;
    gap: 0.15em; aspect-ratio: 1;
    min-block-size: var(--_control-h); min-inline-size: var(--_control-h);
    padding: 0.3em;
    & > svg { inline-size: 1.25em; block-size: 1.25em; }
    & > small { --type: -2; font-weight: 600; line-height: 1; }
  }
  /* active: the full-blast pole pill — lift overdrives past any host
     (clamped at the pole), max neutral ink flips against it. NO
     font-weight: the pill is the signal, and a weight change re-flows
     the label (active-jiggle) — worse here than most places, because
     the nav column is content-sized and a bolder longest-label would
     change the NAV'S OWN WIDTH per page. */
  /* any truthy aria-current activates — same vocabulary the .tabs recipe
     speaks (gf-185: a stepped dialog's rail is .nav-items wearing
     aria-current="step"; the rail idiom is ONE grammar everywhere). */
  :where(.nav-item, .nav-icon):where([aria-current]:not([aria-current="false"]), [aria-pressed="true"]) {
    --lift: 2; --fg: -1;
  }
  /* NO density display rules (gf-23; the attribute itself retired gf-71b
     for the stamped --rail-w). What shows at a width is declared by the
     markup with the component tiers — .small / .medium / .large — the
     system's one responsive idiom. The old attribute-driven span/tag
     hiding was a second mechanism doing the gates' job, and every
     exception it needed (the badge dot, the search trigger) was a
     symptom of that duplication. */

  /* ── form + field — the recessed panel; controls raised & borderless ── */
  :where(form, [role="form"]) {
    --bg: -0.8; background-color: var(--_bg); --surf: var(--_bg); --surf-bg: var(--bg);
    padding: 0.4lh 0.5em;
    border: 1px solid var(--border);
    border-radius: var(--cfg-radius);
    & :where(input:not([type="checkbox"]):not([type="radio"]):not([type="range"]), textarea, select, .field) {
      --bg: 0.1;
      border-color: transparent;   /* the raised surface defines it; the line returns for states */
    }
  }
  /* ONE FRAME PER SURFACE: a form ANYWHERE inside a card, modal, or
     menu composes INTO that surface — the host owns the frame, the
     recessed-panel recipe stands down (no border, no fill, no
     padding). Kills the frame-in-frame stutter (card > form > fields)
     without touching any page's markup. Fields keep their raised
     recipe from the form rule above. (.menu joined gf-65 — a config
     popover's inner form painted a second border inside the menu's
     own. Descendant since gf-219b: the direct-child scoping missed a
     form one wrapper deep in a dialog — the third recurrence of this
     wound; the law was always about the FRAME OWNER, not the DOM
     distance. The recessed panel survives where it earns its keep:
     standalone page forms outside framed hosts.) */
  :where(.card, .modal, .menu, .veil) :where(form, [role="form"]) {
    --bg: 0; background: none;
    padding: 0; border: 0; border-radius: 0;
  }
  /* A form composed INTO a flow row is inline machinery — a verb
     carrier, not a panel; the row's host owns the frame (gf-215: the
     Discovery convert button grew a padded box because its native
     form sat in a .row, missing the child-only stand-down above; the
     .roster form one-off among the graduated recipes was the same
     wound). */
  :where(.row, .spread, .oneline) > :where(form, [role="form"]) {
    --bg: 0; background: none;
    padding: 0; border: 0; border-radius: 0;
  }
  /* fieldset RHYTHM (gf-174, per Mike): a group breathes a little
     after itself — the row's internal 0.25lh gap stays tight, the
     seam between groups reads. */
  :where(form, [role="form"]) > :where(fieldset:not(:last-child)) { margin-block-end: 0.35lh; }
  /* ── LOCKED (gf-175, per Mike) — the READING state of a form. Not
     disabled: ink stays full, the labels stay, but the field chrome
     stands down (values read as text) and controls stop taking input.
     Spelling: data-ui-state="locked" on the form (aria-readonly does
     not apply to role=form; real readonly still belongs on the text
     controls for AT) — client-side the state rides a signal +
     data-attr so it SURVIVES fat-morphs, the orgEdit precedent. The
     unlock/save/cancel buttons stay live: only controls sleep. ── */
  :where(form, [role="form"]):where([data-ui-state="locked"]) {
    & :where(input:not([type="checkbox"]):not([type="radio"]), textarea, select, .field) {
      --bg: 0; background: none; border-color: transparent; pointer-events: none;
    }
    & :where(select) { appearance: none; }
    & :where(input:is([type="checkbox"], [type="radio"])) { pointer-events: none; }
    /* the ACTION ROW (the form's last .spread — Cancel · Save) folds
       away while locked and glides open on unlock (gf-263): block-size
       auto ↔ 0 through interpolate-size, timed by --cfg-motion. */
    & > :where(.spread:last-child) { block-size: 0; opacity: 0; pointer-events: none; }
  }
  /* the row clips ITSELF in both states (a glide must never leak overflow
     into a scroll container — the scrollbar flash); the clip margin keeps
     the buttons' focus rings visible when open. */
  :where(form) > :where(.spread:last-child) {
    overflow: clip; overflow-clip-margin: 4px;
    transition: block-size calc(var(--cfg-motion) * 0.25s) ease, opacity calc(var(--cfg-motion) * 0.2s) ease;
  }
  /* (the frame-in-frame table rule moved to UTILITY, gf-24 — it lived
     here for months as a DEAD rule: the table recipe sits later in this
     layer and out-ordered it at equal zero specificity, which is exactly
     the "weird borders" a card-hosted table showed. Third instance of
     the ordering law; overrides of late-file recipes belong in utility.) */
  /* :not(.search-box) — a search-box IS a label around its input (the box
     is the control), and this recipe was capturing it inside forms:
     display:grid dropped the input onto the grid's second row, under the
     box (the gf-22 lab caught it live on the roster search). The recipe
     is for labeled FIELDS; a search-box labels nothing. */
  :where(form, [role="form"]) :where(label:has(> input, > select, > textarea, > .field, > date-picker, > day-picker, > multi-select, > single-select, > combo-box, > combo-select, > group-pick):not(.search-box):not(:has(> input:is([type="checkbox"], [type="radio"])))) {
    display: grid;
    grid-template-columns: auto 1fr;
    grid-template-rows: 1lh auto;
    align-items: baseline; column-gap: 0.5em;
    & > :where(span)  { --type: 0; --fg: -0.75; grid-column: 1; }
    & > :where(small) {
      --type: -1; --fg: -0.8; grid-column: 2; justify-self: start;
      block-size: 1lh; display: inline-flex; align-items: center; line-height: 1;
    }
    & > :where(input, select, textarea, .field, date-picker, day-picker, multi-select, single-select, combo-box, combo-select, group-pick) { grid-column: 1 / -1; inline-size: 100%; }
    &:where(:has(:where(input, select, textarea):required)) > :where(span)::after {
      content: " *"; --hue-lock: var(--hue-dgr); --fg: 0.8;
    }
    /* STATE RIDES THE OUTLINE, NOT THE SURFACE (gf-174, per Mike):
       the locked hue colors the border via --focus, and the field's
       background is re-hued BACK to ambient (same l/c, the theme's
       hue) — a stated field keeps the neutral raised surface; only
       the rim and the small say what's wrong. */
    &:has([aria-invalid="true"]) {
      & > :where(small) { --hue-lock: var(--hue-dgr); --fg: 1; }
      /* the invalid CONTROL wears the rim — directly, or nested inside a
         combobox host (gf-191): same treatment either way, and ONLY the
         control — the lock sits on the element itself, so the host's
         menu and siblings inherit nothing. */
      & > :where([aria-invalid="true"]),
      & > :where(combo-box, combo-select) > :where([aria-invalid="true"]) {
        --hue-lock: var(--hue-dgr); border-color: var(--focus);
        background-color: oklch(from var(--_bg) l c calc(var(--hue) + var(--hue-shift)));
      }
    }
    &:is(.suc, .inf, .wrn, .dgr) > :where(small) { --fg: 1; }
    &:is(.suc, .inf, .wrn, .dgr) > :where(input, select, textarea) {
      border-color: var(--focus);
      background-color: oklch(from var(--_bg) l c calc(var(--hue) + var(--hue-shift)));
    }
  }
}


@layer block {

  /* ── badge — corner count/dot; the parent anchors it automatically ── */
  :where(.badge) {
    --hue: var(--hue-dgr); --bg: 0.5; --fg: -1; --type: -2;
    position: absolute; inset-block-start: 0; inset-inline-end: 0;
    translate: 35% -35%;
    display: inline-flex; align-items: center; justify-content: center;
    min-inline-size: 1.6em; block-size: 1.6em; padding-inline: 0.35em;
    border-radius: 999px; font-weight: 600; font-style: normal; /* an <i>, not italics (feedback #122) */
    background-color: var(--_bg); --surf: var(--_bg); --surf-bg: var(--bg);
    user-select: none; -webkit-user-select: none;
    &:empty { min-inline-size: 0.7em; block-size: 0.7em; padding: 0; }
  }
  :where(:has(> .badge)) { position: relative; }

  /* ── avatar — monogram / icon / image chip, sized by the control height ── */
  :where(.avatar) {
    --bg: 0;
    padding: 0;   /* a button.avatar must stay square: kill the button padding */
    display: inline-flex; align-items: center; justify-content: center;
    block-size: var(--_control-h); aspect-ratio: 1; flex: 0 0 auto; min-inline-size: var(--_control-h);
    border: 1px solid var(--border); border-radius: var(--cfg-radius);
    background-color: var(--_bg); --surf: var(--_bg); --surf-bg: var(--bg);
    font-family: var(--font-display); font-weight: 500; letter-spacing: 0.02em; text-transform: uppercase;
    user-select: none; -webkit-user-select: none;
    & > :where(img) { inline-size: 100%; block-size: 100%; object-fit: cover; }
    & > :where(svg) { inline-size: 70%; block-size: 70%; }
  }
  :where(.avatar:has(> img)) { overflow: hidden; }   /* clip only when cropping — a corner badge must straddle */

  /* ── app-icon — THE HOME TILE (gf-407 rev 6, Mike 2026-09-23: a
     SANCTIONED new class, the third after .wash and the landing's
     @scope): the native shape (rev 8) — the GLYPH alone in a squircle,
     the word floating beneath it, the badge on the squircle's corner.
     The home page's grid of sections — what the nav rail is on a wider
     screen. The squircle is the glyph's own box: the .sec fill and rim
     on the host's surface, glass behind, corner-shape where the engine
     draws one. A tile has its OWN size (5em, type-scaled) and sits
     centred in a .grid's equal columns (--grid-min: 5em, --gap: 0.5lh
     — FOUR across a phone, Mike 2026-09-23), like icons on a phone. Distinct from .nav-icon, the rail's flat tile. ── */
  :where(.app-icon) {
    display: flex; flex-direction: column; align-items: center;
    gap: 0.4em; inline-size: 5em; flex: 0 0 auto;
    justify-self: center; /* a .grid of equal columns, each tile centred in its cell */
    --lift: initial; --bg: 0.4; --fg: -0.9; /* the word beneath stays neutral; the box carries the fab's ink */
    font: inherit; cursor: pointer; text-decoration: none;
    /* a <button class="app-icon"> (the dock) renders like the <a>: the
       button recipe's box, border, fill and weight stand down here */
    min-block-size: 0; padding: 0; border: 0; background-color: transparent; font-weight: inherit;
    /* the squircle is the glyph — or an .avatar (rev 11: the dock's
       Profile tile), the initials at the glyph's weight */
    /* :is, not :where — the button recipe's nested `& > svg` is a TYPE
       selector (0,0,1) and would out-rank a zero-specificity box here */
    & > :is(svg, .avatar) {
      flex: 0 0 auto; max-inline-size: none; box-sizing: border-box;
      /* the tile's own 5em, restated: a percentage does not resolve for
         an svg inside a <button> (the dock's tiles) */
      inline-size: 5em; block-size: auto; aspect-ratio: 1; padding: 1.4em;
      --fg: var(--_sem-ink, 0.7);
      /* the UA sheet clips a non-root svg (overflow: hidden) and Safari
         clips its SHADOW with it (Mike, iOS, light mode) — the box paints out */
      overflow: visible;
      /* the squircle is a .fab-2 (rev 15, Mike): the glass float's fill,
         rim, shadow and blur — hover lifts it 2px, it never reads pressed */
      background-color: oklch(from var(--_bg) l c h / clamp(0.24, calc(0.32 + var(--state) * 0.5), 0.62));
      border: 1px solid oklch(from var(--surf) calc(l + (var(--cfg-dark) * 2 - 1) * 0.16) 0.12 var(--_h) / clamp(0.4, calc(0.5 + var(--state) * 0.4), 0.9));
      box-shadow: 0 0.3lh 1lh oklch(0 0 0 / calc(0.26 * (1 - var(--cfg-dark)))),
                  0 0.3lh 1.2lh oklch(0 0 0 / calc(0.3 * var(--cfg-dark))),
                  inset 0 1px 0 oklch(1 0 0 / calc(0.16 * var(--cfg-dark)));
      -webkit-backdrop-filter: blur(0.7lh) saturate(1.5);
      backdrop-filter: blur(0.7lh) saturate(1.5);
      border-radius: calc(var(--cfg-radius) * 2.5); corner-shape: squircle;
      transition: translate calc(var(--cfg-motion) * 0.12s) ease, background-color calc(var(--cfg-motion) * 0.12s) ease;
    }
    @media (pointer: fine) { &:hover > :is(svg, .avatar) { translate: 0 -2px; } }   /* the lift is a mouse's (EventOS #884): a thumb never hovers */
    &:active > :is(svg, .avatar) { translate: 0 0; }
    & > small { --type: -2; font-weight: 600; line-height: 1; text-align: center; }
    /* the badge rides the squircle's corner: the tile is the anchor, the
       squircle its full-width top */
    & > :where(.badge) { inset-block-start: 0; inset-inline-end: 0; }
    /* the avatar's initials at the glyph's weight: its own em is bigger,
       so its box is the HOST's width (not 5 of its own em — feedback
       #147) and its padding stands down; ranked with the box rule above */
    & > .avatar { --type: 2; font-weight: 600; inline-size: 100%; block-size: auto; min-inline-size: 0; padding: 0; }
    &:where([aria-current]:not([aria-current="false"])) > :is(svg, .avatar) { --bg: 1; --fg: -1; background-color: var(--_bg); }
  }

  /* an avatar riding a .nav-item is a GLYPH (gf-21): it takes the item
     svg's own footprint (1.15em OF THE ITEM), so the rail has ONE glyph
     column — org mark, monogram and icons land on the same axis in both
     densities, and the icon-anatomy rail width fits a mark exactly (the
     control-height avatar overshot it and clipped). The monogram shrinks
     via --scale (the engine's zoom knob), and the box DIVIDES the scale
     back out — em resolves against the element's own scaled font, so
     1.15em/0.62 at scale 0.62 is exactly 1.15× the item's em. A logo svg
     scales by the recipe's 70% rule. Placed AFTER the avatar recipe —
     same layer, zero specificity, source order decides. */
  :where(.nav-item) > :where(.avatar) {
    --scale: 0.62;
    block-size: calc(1.15em / 0.62); inline-size: calc(1.15em / 0.62);
    min-inline-size: calc(1.15em / 0.62);
    padding: 0;
    border-radius: max(2px, calc(var(--cfg-radius) - 5px));
  }
  /* a LOGO riding a .nav-item is the same glyph WITHOUT the avatar
     (feedback #25): the avatar's box, no border, no surface; the svg
     fills the square and keeps its own aspect (the class+type rule
     outranks the general .org-mark svg sizing on purpose). */
  :where(.nav-item) > :where(.org-mark) {
    block-size: 1.15em; inline-size: 1.15em; min-inline-size: 1.15em; flex: 0 0 auto;
    justify-content: center;
  }
  .nav-item > .org-mark > svg { block-size: 100%; inline-size: 100%; }

  /* ── .list-item — a quiet row surface for one list entry (a notification,
     a checklist step, a request row): ends spread apart, soft raise, radius.
     An <a> list-item is its own affordance (no underline); a link INSIDE one
     is the entry's title (weight, no underline). Add .start when the body
     runs multi-line beside a leading glyph. ── */
  :where(.list-item) {
    --bg: 0.05;
    display: flex; align-items: center; justify-content: space-between; gap: var(--gap, 0.6em);
    padding: 0.3lh 0.55em;
    border-radius: var(--cfg-radius);
    background-color: var(--_bg); --surf: var(--_bg);
    text-decoration: none; color: inherit;
    & :where(a:not(.btn)) { font-weight: 600; text-decoration: none; }
    /* the words shrink before they push a neighbour out (v3.1.13, EventOS
       #796); a control at the row's end keeps its size (v3.1.14, #797) */
    & > :where(*) { min-inline-size: 0; }
    & > :where(button, .btn, fieldset, hold-confirm, .tag) { flex: none; }
  }

  /* ── .kicker — a tiny uppercase group label above a run of items ── */
  :where(.kicker) {
    --type: -1; --fg: 0.5;
    font-weight: 600; text-transform: uppercase; letter-spacing: 0.05em;
  }

  /* ── .org-mark — an inline logo lock-up: the raw brand SVG rides the
     text line instead of dropping to a block (the img/svg reset). ── */
  :where(.org-mark) { display: inline-flex; align-items: center; }

  /* ── .guide — the standard section-index synopsis page: a reading
     measure, generous block rhythm. Parts are .guide-part rows: a leading
     glyph nudged to the first text line, then block-flow text. ── */
  :where(.guide) {
    inline-size: 100%; max-inline-size: calc(40ch + (var(--measure, 2) - 1) * 30ch); margin-inline: auto; /* the reading rung of the measure ladder */
    padding-block: 1.5lh;
    display: flex; flex-direction: column; gap: var(--gap, 1lh);
  }
  :where(.guide-part) {
    display: flex; gap: 0.7em; align-items: start;
    & > :where(svg) { flex: 0 0 auto; margin-block-start: 0.2lh; --fg: -0.45; }
    /* the words shrink and break, never push the row's verbs out (v3.1.12, EventOS #764) */
    & > :where(:not(svg)) { min-inline-size: 0; overflow-wrap: anywhere; }
  }

  /* ── alert — a standing message with a semantic hue ── */
  :where(.alert) {
    --bg: 0.12; --fg: -0.85;
    display: flex; align-items: flex-start; gap: 0.35lh;
    padding: 0.4lh 0.6em;
    border-radius: var(--cfg-radius); border: 1px solid var(--border);
    background-color: var(--_bg); --surf: var(--_bg); --surf-bg: var(--bg);
    & > :where(svg:first-child) {
      inline-size: 1.15em; block-size: 1.15em; flex: 0 0 auto;
      margin-block-start: 0.1em; --fg: 0.8;
    }
    & > :where(:last-child) { flex: 1 1 auto; min-inline-size: 0; }
  }

  /* ── blockquote — quoted content: tinted surface, inline accent marks ── */
  :where(blockquote) {
    --bg: 0.1; --fg: 0.9; --type: -1;
    max-inline-size: 65ch;
    padding: 0.5lh 0.7em; border-radius: var(--cfg-radius);
    background-color: var(--_bg); --surf: var(--_bg); --surf-bg: var(--bg);
    font-style: italic;
    & > :where(p) { --type: inherit; --fg: 1; margin: 0; display: inline; }
    & > :where(p) ~ :where(p) { display: block; margin-block-start: 0.5lh; }
    & > :where(p:first-of-type)::before { content: "\201C"; color: var(--focus); }
    & > :where(p:last-of-type)::after   { content: "\201D"; color: var(--focus); }
    & > :where(cite) {
      display: block; --type: inherit; --fg: -0.85;
      text-align: end; font-style: normal;
      &::before { content: "— "; }
      & :where(cite) { --type: inherit; font-weight: 600; font-style: normal; }
    }
  }

  /* ── the table FRAME (gf-106→107, per Mike): border + radius live on
     the SCROLL CONTAINER — the table's own frame scrolled away with its
     content. Generic: any table directly inside a scroll box is framed
     by it; tables outside scrollers keep their own border. The
     "this extends" signal is the scrollbar itself, brightened — native,
     universal, zero markup (the fade experiment is retired: it took a
     mask, two scroll timelines and a repaint kick to almost work). ── */
  :where(.scroll-x, .scroll-y):has(> table) {
    border: 1px solid var(--border); border-radius: var(--cfg-radius);
    scrollbar-width: thin;
    scrollbar-color: color-mix(in oklch, var(--border), var(--_ink-n) 40%) transparent;
    /* THE SCROLLER PAINTS THE SURFACE (v3.2.1, EventOS #855): a table's own
       --_bg is opaque and sat over the roster's scroll fade (below) — the
       root cause of a fade that never showed; inside a scroller the table
       is transparent and the frame's surface shows through its rows */
    & > :where(table) { border: 0; border-radius: 0; background-color: transparent; }
  }

  /* ── table — booktabs: quiet frame, two vibrant rules, rhythm not lines ── */
  :where(table) {
    --bg: 0;
    inline-size: 100%;
    border-collapse: separate; border-spacing: 0;
    border: 1px solid var(--border); border-radius: var(--cfg-radius);
    background-color: var(--_bg); --surf: var(--_bg); --surf-bg: var(--bg);
    & :where(th, td) {
      --type: var(--table-type, -1);
      padding: 0.18lh 0.7em;
      text-align: start; vertical-align: baseline;
    }
    /* the baseline rule above puts a bare checkbox's BOTTOM on the text
       baseline — it read top-shifted in header and rows. Form controls
       center on the line box instead. */
    & :where(input[type="checkbox"]) { vertical-align: middle; }
    & :where(thead th) {
      --fg: -0.75; font-weight: 600;
      border-block-end: 1px solid var(--focus);
      /* the head sticks and paints with the FRAME's surface (feedback
         #119: a bare table's own --_bg let the rows show through) */
      position: sticky; inset-block-start: 0; z-index: 2;
      background-color: var(--surf);
    }
    & :where(thead tr:first-child th:first-child) { border-start-start-radius: var(--cfg-radius); }
    & :where(thead tr:first-child th:last-child)  { border-start-end-radius:   var(--cfg-radius); }
    & :where(tbody) { border-block-end: 1px solid var(--focus); }
    & :where(th.num, td.num) {
      text-align: end; font-variant-numeric: tabular-nums; font-feature-settings: "tnum" 1;
    }
    /* a header whose content is a flex cluster (label · sort · funnel)
       ignores text-align — the cluster must justify to the th's own
       alignment, or a sparse numeric column shows its header far left of
       its right-aligned data. */
    & :where(th.num) > :where(.row) { justify-content: flex-end; }
    & :where(th.center) > :where(.row) { justify-content: center; }
    & :where(caption) { --type: -1; --fg: -0.7; text-align: start; padding: 0.18lh 0.7em; caption-side: top; }
  }

  /* .num-table — a table whose figures line up without tagging every cell.
     All body cells get tabular numerals (so digits align in a column), and
     any cell/header still opts into right-alignment with .num. Use for money
     and metric tables; the per-cell .num rule above governs which columns
     right-align. */
  :where(table.num-table) :where(td, th) {
    font-variant-numeric: tabular-nums; font-feature-settings: "tnum" 1;
  }
  :where(.table-scroll) {
    --bg: 0;
    overflow: auto; overscroll-behavior: contain;
    max-block-size: 24lh;
    border: 1px solid var(--border); border-radius: var(--cfg-radius);
    background-color: var(--_bg); --surf: var(--_bg); --surf-bg: var(--bg);
    & > :where(table) { border: 0; border-radius: 0; }
  }

  /* ── the GUIDE state (gf-262): the one control the set-up walk wants
     clicked next wears a pulsing outline + glow — outline, so nothing
     shifts; the pulse rides --cfg-motion (0 = a still ring). ── */
  :where([data-ui-state="guide"]) {
    outline: 2px solid var(--focus); outline-offset: 2px;
    animation: guide-pulse calc(var(--cfg-motion) * 3s) ease-in-out infinite;
  }
  /* a STILL ring; only the glow breathes, slowly and softly (2026-09-10,
     per Mike: the first cut flickered) */
  @keyframes guide-pulse {
    0%, 100% { box-shadow: 0 0 0 0 transparent; }
    50%      { box-shadow: 0 0 0.5em 0 color-mix(in oklch, var(--focus) 55%, transparent); }
  }

  /* ── crumbs — breadcrumb trail; masked-SVG separators painted --border.
     Minimal by decree (gf-334, feedback #79): a crumb <a> is a BARE LINK
     — ink, underline, transition and hover are base's link defaults (the
     recipe restating --fg from this later layer pinned the ink under
     hover: fighting itself); the current crumb (any truthy aria-current,
     gf-185/gf-328) wears the nav's active ink at rest, no weight change,
     and hovers like its siblings. ── */
  :where(.crumbs) {
    --crumb-sep: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='black' stroke-width='2.5' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='m9 18 6-6-6-6'/%3E%3C/svg%3E");
    --type: -3;
    display: flex; flex-wrap: wrap; align-items: center; gap: 0.45em;
    & > :where(a, span, button) { display: inline-flex; align-items: center; gap: 0.35em; }
    & > :where(a, span, button) > :where(svg) { inline-size: 1em; block-size: 1em; flex: 0 0 auto; }
    & > :where([aria-current]:not([aria-current="false"])):not(:hover) { --fg: -1; }
    /* the root crumb's aggregate count rides inline (gf-407) — the
       corner badge would sit on the label's tail at the trail's -3 step */
    & :where(.badge) { position: static; translate: none; }
    & > :where(a, span) + :where(a, span)::before {
      content: "";
      inline-size: 1lh; block-size: 1lh;
      background-color: var(--border);
      mask: var(--crumb-sep) center / contain no-repeat;
      -webkit-mask: var(--crumb-sep) center / contain no-repeat;
    }
  }


  /* ── tree — a hierarchy of links (gf-335: the Settings map in one
     glance). Nested lists indent under a hairline; a row is a link with
     its glyph, no underline (a nav, not prose). ── */
  :where(.tree) {
    list-style: none; margin: 0; padding: 0;
    display: flex; flex-direction: column; gap: 0.25lh;
    & :where(ul) {
      list-style: none; margin: 0; padding-inline-start: 1em;
      border-inline-start: 1px solid var(--border);
      display: flex; flex-direction: column; gap: 0.25lh;
    }
    & :where(li) { display: flex; flex-direction: column; gap: 0.25lh; }
    & :where(a) { display: inline-flex; align-items: center; gap: 0.35em; text-decoration: none; }
    & :where(a) > :where(svg) { inline-size: 1em; block-size: 1em; flex: 0 0 auto; }
  }

  /* (the TITLE BLOCK recipe — .title-block, .title-sub — was CUT 2026-09-24,
     gf-409 step 4: the one-row header it dressed is retired; the stacked
     header's rows are the engine's .spread cells) */

  /* ── disclosure — bare <details>: card frame, CSS chevron, animated body ── */
  :where(details) {
    --bg: 0;
    border: 1px solid var(--border); border-radius: var(--cfg-radius);
    background-color: var(--_bg); --surf: var(--_bg); --surf-bg: var(--bg);
    & > :where(summary) {
      display: flex; align-items: center; gap: 0.5em;
      padding: 0.3lh 0.7em;
      border-radius: var(--cfg-radius);
      transition: border-radius calc(var(--cfg-motion) * 0.2s) ease;
      font-weight: 600; list-style: none;
      user-select: none; -webkit-user-select: none;
      background-color: var(--_bg); --surf: var(--_bg); --surf-bg: var(--bg);
      &::-webkit-details-marker { display: none; }
      &::after {
        content: "";
        inline-size: 0.5em; block-size: 0.5em;
        margin-inline-start: auto;
        border: 0.12em solid currentColor;
        border-block-start: 0; border-inline-start: 0;
        rotate: -45deg; translate: 0 -0.1em;
        transition: rotate calc(var(--cfg-motion) * 0.18s) ease;
        opacity: 0.6;
      }
    }
    &[open] > :where(summary) {
      border-end-start-radius: 0; border-end-end-radius: 0;
      border-block-end: 1px solid var(--border);
    }
    &[open] > :where(summary)::after { rotate: 45deg; }
    &::details-content {
      overflow: hidden; block-size: 0; padding: 0 0.7em;
      transition:
        block-size    calc(var(--cfg-motion) * 0.2s) ease,
        padding-block calc(var(--cfg-motion) * 0.2s) ease,
        content-visibility calc(var(--cfg-motion) * 0.2s) allow-discrete;
    }
    &[open]::details-content { block-size: auto; padding-block: 0.3lh; }
  }

  /* ── .otable — THE OUTLINE TABLE (gf-386): one grid for a tree with
     columns. The name column takes the room, the fact columns size to
     content (--cols is the count); the head, every row and every
     details node (and its content box) lay onto the SAME subgrid, so
     a cell can never wrap under a name however narrow the page. Depth
     is indentation on the name cell (--depth per row). A node's fold
     is a bare details — no card, no summary band, no CSS chevron (the
     row carries its own fold glyph, turned when open). A column that
     is out of band (its .c-* gate) leaves an empty track behind.
     Class specificity on purpose: the bare-details card recipe above
     shares the layer at zero specificity. ── */
  .otable { display: grid; grid-template-columns: minmax(0, 1fr) repeat(var(--cols, 3), max-content); column-gap: 1em; align-items: center; }
  .otable > .otable-head, .otable-row, details.otable-node, details.otable-node::details-content {
    display: grid; grid-template-columns: subgrid; grid-column: 1 / -1; align-items: center; column-gap: inherit;
  }
  details.otable-node { border: 0; border-radius: 0; background: none; }
  details.otable-node > summary { padding: 0; border: 0; border-radius: 0; background: none; font-weight: inherit; cursor: pointer; }
  details.otable-node > summary::after { content: none; }
  details.otable-node::details-content, details.otable-node[open]::details-content { padding: 0; }
  .otable > .otable-head { min-block-size: 1.4lh; border-block-end: 1px solid var(--border); }
  /* STICKY CONTEXT (feedback #116): inside a scrolling frame the head
     and every top-level node's row hold their place — a house stays in
     view while its brands scroll under it. Painted with the frame's own
     surface so nothing shows through. */
  .otable > .otable-head { position: sticky; inset-block-start: 0; z-index: 2; background-color: var(--surf); }
  .otable > details.otable-node > summary.otable-row { position: sticky; inset-block-start: 1.4lh; z-index: 1; background-color: var(--surf); }
  .otable-row { min-block-size: 1.7lh; border-radius: max(0px, calc(var(--cfg-radius) - 3px)); }
  /* THE TYPE LADDER BY DEPTH (feedback 2026-09-21): the parent a step
     up, the child at body, the grandchild a step down — --type is a
     number, so depth computes it. It is set on the cells' CONTENT, not
     the row: the name cell's indent is measured in the row's em, and a
     custom property's em resolves where it is used, so a typed row would
     also creep left. */
  .otable-row > .otable-name > *, .otable-row > .otable-cell > * { --type: calc(1 - var(--depth, 0)); }
  /* a tag in a cell keeps a tag's step below its row's text (the ladder
     above would otherwise lift it to the row's own size) */
  .otable-row > .otable-cell > .tag { --type: calc(-1 - var(--depth, 0)); }
  .otable-row:hover { --bg: 0.08; background-color: var(--_bg); }
  .otable-name { display: flex; align-items: center; gap: 0.4em; min-inline-size: 0; padding-inline-start: calc(var(--depth, 0) * 1.5em + 0.4em); }
  .otable-name > svg { inline-size: 1.05em; block-size: 1.05em; flex: 0 0 auto; --fg: -0.5; }
  .otable-fold { display: inline-flex; inline-size: 1em; block-size: 1em; flex: 0 0 auto; --fg: -0.45; }
  .otable-fold > svg { inline-size: 1em; block-size: 1em; transition: rotate calc(var(--cfg-motion) * 0.15s) ease; }
  details.otable-node[open] > summary .otable-fold > svg { rotate: 90deg; }
  .otable-cell { display: flex; align-items: center; min-inline-size: 0; padding-inline-end: 0.4em; }
  /* a fact as TEXT with the affordance on hover: a dotted underline
     that appears under the pointer (or focus), nothing else on the row */
  .otable-text { display: inline-flex; align-items: center; gap: 0.3em; padding: 0.05em 0.2em; border: 0; background: none; box-shadow: none; min-block-size: 0; --lift: initial; --bg: initial; text-decoration: underline dotted transparent; text-underline-offset: 0.2em; }
  .otable-text:where(:hover, :focus-visible) { text-decoration-color: currentColor; }
  .otable-text > svg { inline-size: 1em; block-size: 1em; }

  /* ── checkbox / radio field — horizontal, same no-shift feedback slot ── */
  :where(form, [role="form"]) :where(label:has(> input:is([type="checkbox"], [type="radio"])):not(.tabs *)) {
    display: grid;
    grid-template-columns: auto 1fr; column-gap: 0.5em; align-items: start;
    & > :where(input) { grid-row: 1; align-self: center; }
    & > :where(span)  { --fg: -0.75; grid-column: 2; }
    & > :where(small) {
      --type: -1; --fg: -0.8; grid-column: 2;
      block-size: 1lh; display: inline-flex; align-items: center; line-height: 1;
    }
    &:has([aria-invalid="true"]) {
      & > :where(small) { --hue-lock: var(--hue-dgr); }
      & > :where([aria-invalid="true"]) { --hue-lock: var(--hue-dgr); }
    }
    &:is(.suc, .inf, .wrn, .dgr) > :where(small),
    &:has([aria-invalid="true"]) > :where(small) { --fg: 1; }
  }

  /* ── field group — <fieldset> lays child fields on one row (--row-width) ── */
  @scope (fieldset) {
    :scope { display: flex; flex-wrap: wrap; gap: 0.25lh; align-items: start; }
    :scope > * { flex: var(--row-width); min-inline-size: 0; }
    /* EVERYTHING FILLS THE WIDTH (gf-173, per Mike — canon change,
       mirror upstream): labels flex by --row-width in EVERY fieldset,
       legended or not. The old legended special case (flex: 0 0 auto,
       content-sized labels) was why "full-width field under a legend"
       had no knob — retired. Buttons hug in EVERY fieldset (gf-183):
       a submit is not a field — which is exactly what makes the
       SINGLE-ROW form work: bare controls share the row, the button
       takes only its own width. */
    :scope > :where(button) { flex: 0 0 auto; align-self: end; } /* on the controls' line, under the labels' row (EventOS feedback #382) */
    :scope > :where(legend) {
      display: flex; align-items: baseline; gap: 0.5em;
      flex: 1 0 100%;
      --type: 0; --fg: -0.75;
      & > small {
        --type: -1; --fg: -0.8;
        block-size: 1lh; display: flex; align-items: center; line-height: 1;
      }
    }
  }
  :where(fieldset:is(.suc, .inf, .wrn, .dgr), fieldset:has([aria-invalid="true"])) > :where(legend) > :where(small) { --fg: 1; }
  /* the danger hue for the LEGEND's state line only (gf-191, per Mike):
     locking the whole fieldset bled the hue into every sibling field —
     invisible in one-field fieldsets, glaring in a two-up row. */
  :where(fieldset:has([aria-invalid="true"])) > :where(legend) { --hue-lock: var(--hue-dgr); }

  /* ── segmented radio (.tabs) — real radios in a recessed groove.
     A DIV role=group, NOT fieldset (its anonymous box breaks tile stretch). ── */
  :where(.tabs) {
    display: flex; flex-wrap: wrap;
    align-items: stretch; gap: 0.1lh;
    --bg: 0.1; background-color: var(--_bg); --surf: var(--_bg); --surf-bg: var(--bg);
    border-radius: var(--cfg-radius);
    padding: 2px;
    min-block-size: var(--_control-h);
    box-sizing: border-box;
    position: relative;
    &:where(:has(> .legend)) { margin-block-start: 1lh; }
    & > :where(.legend) {
      position: absolute; inset-block-end: 100%; inset-inline-start: 0; inline-size: 100%;
      display: flex; align-items: baseline; gap: 0.5em;
      padding: 0; background: none;
      --type: inherit; --fg: -0.75;
      & > :where(small) {
        --type: -1; --fg: -0.8;
        block-size: 1lh; display: flex; align-items: center; line-height: 1;
      }
    }
    & > :where(label) {
      --type: inherit;
      /* EVEN SPLIT (gf-175, per Mike): the chips share the groove
         equally — no slack at the end. Be prudent: don't wear the
         groove where the longest option can't fit its share. */
      flex: 1; align-self: stretch;
      display: flex; align-items: center; justify-content: center; gap: 0.4em;
      padding-inline: 0.8em;
      border-radius: calc(var(--cfg-radius) - 0.15lh);
      user-select: none; -webkit-user-select: none;
      transition: background-color calc(var(--cfg-motion) * 0.12s) ease;
      & > :where(span) { --type: inherit; --fg: -0.6; }
      /* THE DOT IS HIDDEN (v3.1.17, EventOS #846): the radio is the state
         and the keyboard's target, the chip is the label — the engine
         hides it, no .vh needed */
      & > :where(input:is([type="radio"], [type="checkbox"])) { position: absolute; inline-size: 1px; block-size: 1px; overflow: hidden; clip-path: inset(50%); white-space: nowrap; margin: 0; outline: none; }
      &:has(:checked) {
        /* the checked chip wears THE ACTIVE STATE (gf-175, per Mike —
           same paint as [data-ui-state="active"]): full-blast accent,
           inverted ink. */
        --bg: 1;
        background-color: var(--_bg); --surf: var(--_bg); --surf-bg: var(--bg);
        border-radius: var(--cfg-radius);
        & > :where(span) { --fg: -1; }
      }
      &:has(:focus-visible) { outline: 2px solid var(--focus); outline-offset: -2px; }
    }
  }

  /* ── underline tabs — sit ON a line; the active tab rides it in --focus ── */
  /* ── underline tabs — the GitHub treatment: a quiet strip on a line.
     Labels are calm ink; hover tints a soft rounded pad; the ACTIVE tab
     is full ink + weight with a 2px --focus underline riding the 1px
     border (margin-block-end: -1px makes them continuous). Children are
     bare: every button-recipe default is explicitly neutralized here,
     and the button paint above is 0-specificity so this actually wins. */
  :where(.tabs-underline) {
    display: flex; align-items: stretch; gap: 0.1em;
    border-block-end: 1px solid var(--border);
    /* optical: first label aligns flush with the content edge */
    & > :where(:first-child) { margin-inline-start: -0.8em; }
    & > :where(a, button, [role="tab"]) {
      appearance: none; border: 0; background: none; box-shadow: none;
      margin: 0; min-block-size: 0;
      font: inherit; font-weight: 400; text-decoration: none; cursor: pointer;
      display: inline-flex; align-items: center; gap: 0.45em;
      padding: 0.35lh 0.8em;
      --lift: initial; --fg: -0.55;
      border-block-end: 2px solid transparent;
      margin-block-end: -1px;
      border-radius: calc(var(--cfg-radius) - 0.1lh) calc(var(--cfg-radius) - 0.1lh) 0 0;
      transition: color            calc(var(--cfg-motion) * 0.12s) ease,
                  border-color     calc(var(--cfg-motion) * 0.12s) ease,
                  background-color calc(var(--cfg-motion) * 0.12s) ease;
      & > svg { flex: 0 0 auto; inline-size: 1.05em; block-size: 1.05em; opacity: 0.85; }
      &:hover {
        --fg: -0.9; --lift: 0.1;
        background-color: var(--_bg); --surf: var(--_bg);
      }
      &:where([aria-selected="true"], [aria-current]:not([aria-current="false"])) {
        --fg: -1; font-weight: 600;
        border-block-end-color: var(--focus);
      }
      &:focus-visible { outline: 2px solid var(--focus); outline-offset: -3px; }
    }
    & :where(.count) {
      --type: -1; --lift: 0.14; background-color: var(--_bg); --surf: var(--_bg);
      --fg: -0.65; font-weight: 400; padding: 0 0.5em; border-radius: 999px; align-self: center;
      min-inline-size: 1.5em; text-align: center;
    }
  }

  /* icon-only tabs-underline (labels gated away): the item collapses to its
     icon; center it and let the row distribute evenly. The underline still
     rides the item, so it sits under the icon. */
  :where(.tabs-underline) > :where(a, button):where(:not(:has(> span:not([hidden])))) {
    justify-content: center;
  }

  /* tabs-underline as the MOBILE bottom bar: when it is the tier-2 nav docked
     into pg-main-footer, spread items across the width and drop the top line
     onto the bar edge. One component, two placements — the header strip and
     this bar are the same tabs-underline. */
  :where(.pg-main-footer) > :where(.tabs-underline) {
    border-block-end: 0;
    justify-content: space-around;
    & > :where(a, button) { flex: 1 1 0; justify-content: center; padding-block: 0.5lh; }
  }

  /* ── button group — connected toggles; pressed = full-blast + committed ink ── */
  /* Members are :where(button, .btn) — a views-control member is an ANCHOR
     (real navigation, middle-click works) wearing .btn, the engine's own
     "button recipe on any element"; without it a bare <a> member has no
     button padding/height and the segments collapse. */
  :where(.button-group) {
    display: inline-flex;
    /* A MEMBER IS A BUTTON OR A RADIO (v3.1.17, EventOS #846): the
       segmented radio's recipe — `<label class="btn"><input type="radio">
       word</label>`, the dot hidden, the checked member pressed — so a
       group can be a form control or a signal's switch alike */
    & > :where(label.btn) { position: relative;
      & > :where(input:is([type="radio"], [type="checkbox"])) { position: absolute; inline-size: 1px; block-size: 1px; overflow: hidden; clip-path: inset(50%); white-space: nowrap; margin: 0; outline: none; }
      &:has(:focus-visible) { outline: 2px solid var(--focus); outline-offset: -2px; z-index: 1; } }
    & > :where(button, .btn) {
      --lift: 0.2; --fg: -0.4;
      border-radius: 0;
      margin-inline-start: -1px;
      &:is([aria-pressed="true"], :has(> input:checked)) {
        --lift: initial; --bg: 1; --fg: var(--_sem-ink, -1);
        background-color: var(--_bg); --surf: var(--_bg); --surf-bg: var(--bg);
        position: relative; z-index: 1;
      }
    }
    & > :where(button, .btn):first-child { border-start-start-radius: var(--cfg-radius); border-end-start-radius: var(--cfg-radius); margin-inline-start: 0; }
    & > :where(button, .btn):last-child  { border-start-end-radius: var(--cfg-radius);  border-end-end-radius: var(--cfg-radius); }
  }

  /* ── dock button — svg + <small>: icon over a tiny caption, capped box
     (an .app-icon shares the anatomy and keeps its own box, gf-407) ── */
  :where(button:has(> svg):has(> small), .btn:has(> svg):has(> small)):not(.app-icon) {
    flex-direction: column; justify-content: flex-start; gap: 0.1em;
    inline-size: 6.8em; block-size: 3.4em;
    padding: 0.4em 0.3em 0.3em; overflow: hidden;
    & > svg   { inline-size: 1em; block-size: 1em; }
    & > small {
      --type: -2; font-weight: 600; line-height: 1.1; text-align: center;
      max-inline-size: 100%; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
    }
  }
  @media (pointer: coarse) {
    :where(button, .btn):not(.app-icon) > svg { inline-size: 1.25em; block-size: 1.25em; }
  }

  /* ── track + fill family — REWORKED onto the engine (was the rogue
     formula: oklch(50%+fill·20% … var(--hue)), blind to hue-lock/shift
     and the chroma knob). Fill is now an anchor mix — it re-hues with
     --_h, obeys --chroma, and a .suc/.dgr lock recolours it. The knob is
     --fill (0–100%, how far the fill sits from zero toward the pole). ── */
  :where(progress, meter) {
    --bg: 0.15;
    --_fill-color: color-mix(in oklch, var(--_a-zero), var(--_a-pos) var(--fill, 65%));
    appearance: none; -webkit-appearance: none;
    inline-size: 100%; block-size: 0.5lh;
    border: 0; border-radius: 999px;
    background-color: var(--_bg); --surf: var(--_bg); --surf-bg: var(--bg);
    overflow: hidden;
  }
  :where(progress)::-webkit-progress-bar   { background: var(--_bg); border-radius: 999px; }
  :where(progress)::-webkit-progress-value { background: var(--_fill-color); border-radius: 999px; }
  :where(progress)::-moz-progress-bar      { background: var(--_fill-color); border-radius: 999px; }
  /* indeterminate: ONE solid segment sweeping the track (feedback #44,
     per Mike: "a new more simple effect") — no gradient fade, no glow;
     a third of the track, one sweep every 1.4s, eased so it reads as
     motion rather than a marquee. background-position runs in track
     minus segment units: −43% parks it off the left edge, 143% off the
     right (0.3 / 0.7). */
  :where(progress:indeterminate) {
    background:
      linear-gradient(var(--_fill-color), var(--_fill-color)) no-repeat,
      var(--_bg);
    background-size: 30% 100%;
    animation: progress-slide calc(var(--cfg-motion) * 1.4s) ease-in-out infinite;
  }
  :where(progress:indeterminate)::-webkit-progress-bar { background: transparent; }

  :where(meter)::-webkit-meter-bar {
    background: var(--_bg); border-radius: 999px; block-size: 0.5lh;
  }
  :where(meter)::-webkit-meter-optimum-value,
  :where(meter)::-webkit-meter-suboptimum-value,
  :where(meter)::-webkit-meter-even-less-good-value {
    background: var(--_fill-color); border-radius: 999px; block-size: 100%;
  }
  :where(meter)::-moz-meter-bar { background: var(--_fill-color); border-radius: 999px; }
  /* .smart — the meter's own bands drive the hue: the ENGINE pole re-hued
     per semantic (was three hardcoded L/C literals) */
  :where(meter.smart) {
    --_c-opt: oklch(from var(--_a-pos) l c var(--hue-suc));
    --_c-sub: oklch(from var(--_a-pos) l c var(--hue-wrn));
    --_c-bad: oklch(from var(--_a-pos) l c var(--hue-dgr));
  }
  :where(meter.smart)::-webkit-meter-optimum-value        { background: var(--_c-opt); }
  :where(meter.smart)::-webkit-meter-suboptimum-value     { background: var(--_c-sub); }
  :where(meter.smart)::-webkit-meter-even-less-good-value { background: var(--_c-bad); }
  :where(meter.smart):-moz-meter-optimum::-moz-meter-bar         { background: var(--_c-opt); }
  :where(meter.smart):-moz-meter-sub-optimum::-moz-meter-bar     { background: var(--_c-sub); }
  :where(meter.smart):-moz-meter-sub-sub-optimum::-moz-meter-bar { background: var(--_c-bad); }

  :where(input[type="range"]) {
    --bg: 0.15;
    --_fill-color: color-mix(in oklch, var(--_a-zero), var(--_a-pos) var(--fill, 65%));
    appearance: none; -webkit-appearance: none;
    inline-size: 100%; block-size: 0.5lh;
    background: transparent; cursor: pointer; margin-block: 0.4lh;
    /* the focus ring follows the theme's radius, as every control's does
       (feedback #108): the generic outline traces the border-radius */
    border-radius: var(--cfg-radius);
  }
  :where(input[type="range"])::-webkit-slider-runnable-track { block-size: 0.5lh; border-radius: 999px; background-color: var(--_bg); }
  :where(input[type="range"])::-moz-range-track    { block-size: 0.5lh; border-radius: 999px; background-color: var(--_bg); }
  :where(input[type="range"])::-moz-range-progress { block-size: 0.5lh; border-radius: 999px; background-color: var(--_fill-color); }
  :where(input[type="range"])::-webkit-slider-thumb {
    appearance: none; -webkit-appearance: none;
    inline-size: 1.1lh; block-size: 1.1lh; border-radius: 50%;
    background-color: var(--_fill-color);
    border: 2px solid var(--_bg);
    margin-block-start: calc((0.5lh - 1.1lh) / 2);
    cursor: grab;
  }
  :where(input[type="range"])::-moz-range-thumb {
    inline-size: 1.1lh; block-size: 1.1lh; border-radius: 50%;
    background-color: var(--_fill-color); border: 2px solid var(--_bg); cursor: grab;
  }
  :where(input[type="range"]):focus-visible::-webkit-slider-thumb { outline: 2px solid var(--focus); outline-offset: 2px; }
  :where(input[type="range"]):focus-visible::-moz-range-thumb     { outline: 2px solid var(--focus); outline-offset: 2px; }
  :where(input[type="range"]):active::-webkit-slider-thumb { cursor: grabbing; }
  :where(input[type="range"]) { @media (pointer: coarse) { block-size: 0.6lh; } }

  /* ── select (customizable, base-select) — engine picker + options.
     The old --surf-l-on-pseudo wart is GONE: unregistered engine values
     inherit onto ::picker naturally, so options ink against the select's
     real surface with no workaround. ── */
  @supports (appearance: base-select) {
    :where(select), :where(select)::picker(select) { appearance: base-select; }
    :where(select) {
      display: inline-flex; align-items: center;
      /* the chosen option is ONE line (EventOS feedback #271, 2026-10-05): a
         base-select lays its selected content out as flex text and a long
         option wrapped the control to two lines; the control keeps its row
         and the words clip, the picker shows them whole */
      min-inline-size: 0; white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
      &::picker-icon {
        --fg: -0.5; margin-inline-start: auto;
        transition: rotate calc(var(--cfg-motion) * 0.15s) ease;
      }
      &:open::picker-icon { rotate: 180deg; }
      &:open { border-color: var(--focus); }
      &:focus-visible { outline-offset: -2px; }
    }
    ::picker(select) {
      background-color: var(--_bg);
      border: 1px solid var(--border);
      border-radius: var(--cfg-radius);
      margin-block-start: 0.25lh;
      padding: 0.2lh;
      --_dur: calc(var(--cfg-motion) * 0.14s);
      transition: opacity var(--_dur) ease-out, translate var(--_dur) ease-out,
                  display var(--_dur) allow-discrete, overlay var(--_dur) allow-discrete;
      opacity: 1; translate: 0 0;
    }
    :where(select):not(:open)::picker(select) { opacity: 0; translate: 0 -0.3lh; }
    @starting-style { :where(select):open::picker(select) { opacity: 0; translate: 0 -0.3lh; } }
    :where(select) option {
      display: flex; align-items: center; gap: 0.5em;
      padding: 0.2lh 0.6em;
      border-radius: max(0px, calc(var(--cfg-radius) - 0.2lh));
      --fg: -0.6;
      &:hover, &:focus { --bg: 0.28; background-color: var(--_bg); }
      &:focus-visible { outline-offset: -2px; }
      &:checked { --fg: -1; font-weight: 600; }
      & svg { inline-size: 1.1em; block-size: 1.1em; flex: 0 0 auto; }
    }
  }

  /* ── floating action buttons — glass; channel formulas → RCS off the
     engine's own resolved values. Shadows stay literal black (sanctioned:
     shadows are achromatic by design); the fab-3 glow is the POLE. ── */
  :where(.fab-1, .fab-2, .fab-3) {
    --lift: initial; --bg: 0.4; --fg: var(--_sem-ink, 0.7);
    border-radius: 999px; font-weight: 650;
    /* NO self-positioning: the hud slot classes (.tl … .br) own placement;
       the hud default centers a slotless child. (The dock is retired —
       2026-08 — so .br is the standard home for the trigger stack.) */
    margin: 1.1lh;
    position: relative;
    transition:
      translate        calc(var(--cfg-motion) * 0.12s) ease,
      background-color calc(var(--cfg-motion) * 0.12s) ease,
      box-shadow       calc(var(--cfg-motion) * 0.12s) ease,
      border-color     calc(var(--cfg-motion) * 0.12s) ease,
      backdrop-filter  calc(var(--cfg-motion) * 0.12s) ease;
    & > svg { inline-size: 1.2em; block-size: 1.2em; }
    &::before { content: ""; position: absolute; inset: -4px; }   /* static hit-pad */
    @media (pointer: fine) { &:hover { translate: 0 -2px; } }   /* the lift is a mouse's (EventOS #884) */
    &:active { translate: 0 0; }
  }
  :where(.fab-1) {
    --fg: var(--_sem-ink, 1);
    background-color: oklch(from var(--_bg) l c h / calc(var(--state) * 0.5));
    border: 1px solid oklch(from var(--surf) calc(l + (var(--cfg-dark) * 2 - 1) * 0.16) 0.12 var(--_h) / calc(var(--state) * 0.75));
    -webkit-backdrop-filter: blur(calc(var(--state) * 0.7lh));
    backdrop-filter: blur(calc(var(--state) * 0.7lh));
  }
  :where(.fab-2) {
    background-color: oklch(from var(--_bg) l c h / clamp(0.24, calc(0.32 + var(--state) * 0.5), 0.62));
    border: 1px solid oklch(from var(--surf) calc(l + (var(--cfg-dark) * 2 - 1) * 0.16) 0.12 var(--_h) / clamp(0.4, calc(0.5 + var(--state) * 0.4), 0.9));
    box-shadow: 0 0.3lh 1lh oklch(0 0 0 / calc(0.26 * (1 - var(--cfg-dark)))),
                0 0.3lh 1.2lh oklch(0 0 0 / calc(0.3 * var(--cfg-dark))),
                inset 0 1px 0 oklch(1 0 0 / calc(0.16 * var(--cfg-dark)));
    -webkit-backdrop-filter: blur(0.7lh) saturate(1.5);
    backdrop-filter: blur(0.7lh) saturate(1.5);
  }
  :where(.fab-3) {
    background-color: oklch(from var(--_bg) l c h / clamp(0.3, calc(0.38 + var(--state) * 0.45), 0.64));
    border: 1px solid oklch(from var(--_a-pos) l 0.14 var(--_h) / clamp(0.5, calc(0.6 + var(--state) * 0.35), 0.95));
    box-shadow: 0 0.2lh 1.3lh oklch(from var(--_a-pos) l c var(--_h) / 0.42),
                0 0.25lh 0.5lh oklch(0 0 0 / 0.2);
    -webkit-backdrop-filter: blur(0.7lh) saturate(1.6);
    backdrop-filter: blur(0.7lh) saturate(1.6);
  }
  /* A LINK WRAPPING A FAB wears the pill's own shape (v3.2.3, EventOS #878):
     the focus ring is the anchor's, and a square anchor drew a square ring
     around a round button — the anchor is a pill, so its ring curves too */
  :where(a:has(> :where(.fab-1, .fab-2, .fab-3))) { display: inline-block; border-radius: 999px; }
  /* inside the hud, fabs are uniform circles; the corner-slot padding owns
     the viewport offset, so the fab's own free-floating margin dies. */
  :where(.hud) :where(.fab-1, .fab-2, .fab-3) { inline-size: 2.4lh; block-size: 2.4lh; margin: 0; }
  /* inside the navigation rail the same: the rail's own rows space the
     controls — the fab's free-floating 1.1lh margin overflowed the
     narrow rail and opened a gap above the theme row (feedback #95) */
  :where(.pg-navigation) :where(.fab-1, .fab-2, .fab-3) { margin: 0; }

  /* ── .hud-pop — a bare anchored surface rising from a hud corner fab: the
     popover box is a transparent positioning frame (the card inside paints).
     anchor-name / position-anchor ride inline (per-instance identity). ── */
  :where(.hud-pop) {
    position-area: top span-left; margin: 0.5lh;
    border: 0; padding: 0; background: transparent;
    inline-size: min(24rem, 90vw);
  }

  /* ── overlays — components, not layout (they paint + animate) ── */
  :where(.drawer) {
    --_dur: calc(var(--cfg-motion) * 0.25s);
    position: fixed; inset: auto; margin: 0; padding: 0.75lh 1em; border: 0;
    max-block-size: none; max-inline-size: none;
    --bg: 0.08; background-color: var(--_bg); --surf: var(--_bg); --surf-bg: var(--bg);
    transition: translate var(--_dur) ease-out, opacity var(--_dur) ease-out,
                display var(--_dur) allow-discrete, overlay var(--_dur) allow-discrete;
    translate: 0 0; opacity: 1;
    &:not(:is([open], :popover-open)) { opacity: 0; translate: var(--_translate-closed); }
    @starting-style { &:is([open], :popover-open) { opacity: 0; translate: var(--_translate-closed); } }
    &:where(.left)   { --_translate-closed: -100% 0; inset: 0 auto 0 0; inline-size: min(85vw,320px); block-size: 100svh; border-radius: 0 var(--cfg-radius) var(--cfg-radius) 0; }
    &:where(.right)  { --_translate-closed: 100% 0;  inset: 0 0 0 auto; inline-size: min(85vw,320px); block-size: 100svh; border-radius: var(--cfg-radius) 0 0 var(--cfg-radius); }
    &:where(.top)    { --_translate-closed: 0 -100%; inset: 0 0 auto 0; inline-size: 100vw; block-size: min(85svh,240px); border-radius: 0 0 var(--cfg-radius) var(--cfg-radius); }
    &:where(.bottom) { --_translate-closed: 0 100%;  inset: auto 0 0 0; inline-size: 100vw; block-size: min(85svh,240px); border-radius: var(--cfg-radius) var(--cfg-radius) 0 0; }
  }
  /* THE DIALOG, TOP-PINNED (gf-71): its width is a rung of THE CONTENT
     LADDER — reading (.content-2) by default, .content-1 the focused
     confirm, .content-3 the workbench (the import wizard); the utility
     classes set --content, the geometry is the same three declarations.
     Top-pinning means a growing dialog only grows downward, and a phone
     keeps its lower half free for the soft keyboard. */
  :where(.modal) {
    --_dur: calc(var(--cfg-motion) * 0.2s);
    position: fixed; inset: 0; margin: 4vh auto auto;
    inline-size: 100%; max-inline-size: var(--content, var(--content-2)); max-block-size: min(85svh, 44rem);
    overflow: auto; overscroll-behavior: contain;
    padding: 0.5lh; border: 0; border-radius: var(--cfg-radius);
    --bg: 0.1; background-color: var(--_bg); --surf: var(--_bg); --surf-bg: var(--bg);
    transition: opacity var(--_dur) ease-out, scale var(--_dur) ease-out,
                display var(--_dur) allow-discrete, overlay var(--_dur) allow-discrete;
    opacity: 1; scale: 1;
    &:not(:is([open], :popover-open)) { opacity: 0; scale: 0.96; }
    @starting-style { &:is([open], :popover-open) { opacity: 0; scale: 0.96; } }
  }
  /* ── .modal.anchored — the small dialog PINNED TO ITS SUBJECT (gf-178,
     per Mike): a modal edit that opens beside the thing it edits. THE
     LAYOUT-SHIFT DOCTRINE: whenever possible, editing must not reflow
     the page — the top layer gives zero shift, the platform gives the
     focus trap + an inert page (so exactly ONE edit can be open), and
     Escape/light-dismiss give cancel for free. The subject wears
     anchor-name, the dialog wears position-anchor (the sanctioned
     inline pair — Chrome gives DIALOGS no implicit invoker anchor,
     verified live gf-183: the trigger wears anchor-name and the click
     stamps position-anchor before commandfor opens). Opens below the
     anchor growing toward inline-START (edit triggers live at row
     ends, where inline-end has no room), flipping when cramped;
     min-inline-size keeps auto width from silently SHRINKING to the
     cramped region instead of overflowing it — fallbacks only fire on
     overflow. Without a resolvable anchor it degrades to the normal
     top-pinned modal. */
  :where(.modal.anchored) {
    inset: auto; margin: 0.2lh 0 0;
    position-area: block-end span-inline-start;
    position-try-fallbacks: flip-block, flip-inline;
    min-inline-size: min(16rem, 90vi);
  }
  /* backdrops — literal black scrim: SANCTIONED (scrims/shadows are
     achromatic by design, like the fab shadows) */
  :where(dialog.drawer, dialog.modal) {
    --_dur: calc(var(--cfg-motion) * 0.2s);
    &::backdrop { background: oklch(0% 0 0 / 0.5);
      transition: background-color var(--_dur) ease-out, display var(--_dur) allow-discrete, overlay var(--_dur) allow-discrete; }
    &:not([open])::backdrop { background: oklch(0% 0 0 / 0); }
    @starting-style { &[open]::backdrop { background: oklch(0% 0 0 / 0); } }
  }
  /* glass dialogs: the FROST lives on ::backdrop — backdrop-filter on a
     top-layer panel does not reliably sample the page beneath it, but on
     ::backdrop it does. The panel keeps .glass's translucent engine
     surface; the backdrop supplies the blur and a lighter scrim. */

  :where(dialog.glass)::backdrop {
    /* NO page blur (gf-13 — a blurred background reads as the app breaking,
       not as modality). A plain light scrim separates; the PANEL keeps its
       own frost (that blur samples only what's directly behind the panel). */
    background: oklch(0% 0 0 / 0.35);
  }
  :where(.menu) {
    --_dur: calc(var(--cfg-motion) * 0.14s);
    position: fixed; inset: auto; margin: 0;
    position-visibility: always; /* an open menu never follows its anchor into hiding (feedback #100) */
    display: flex; flex-direction: column;
    min-inline-size: 11rem; padding: 0.25lh; list-style: none;
    border-radius: var(--cfg-radius);
    --bg: 0.12; background-color: var(--_bg); --surf: var(--_bg); --surf-bg: var(--bg);
    border: 1px solid var(--border);
    transition: opacity var(--_dur) ease-out, translate var(--_dur) ease-out,
                display var(--_dur) allow-discrete, overlay var(--_dur) allow-discrete;
    opacity: 1; translate: 0 0;
    /* display: none is LOAD-BEARING (gf-79): the unconditional flex above
       out-ranks the UA's closed-popover display:none, and opacity alone
       leaves the hidden menu's buttons in the TAB ORDER. allow-discrete
       (already in the transition) keeps the exit animation whole. */
    &:not(:popover-open) { opacity: 0; translate: 0 -0.3lh; display: none; }
    @starting-style { &:popover-open { opacity: 0; translate: 0 -0.3lh; } }
  }
  @supports (top: anchor(bottom)) {
    :where(.menu) {
      top: anchor(bottom); left: anchor(left);
      margin-block: 0.3lh;
      min-inline-size: max(11rem, anchor-size(width));
      position-try-fallbacks: flip-block, flip-inline, flip-block flip-inline;
    }
  }

  /* ── toast — THE NOTIFICATION CARD, ONE SHAPE (the toast contract,
     EventOS gf-535, 2026-10-05; canonical since gf-42, rev 3 gf-470).
     The markup is the whole vocabulary and this rule lays it out by
     nesting:
       <toast-card class="toast card glass {tone}" data-ui-state="unread|read|leaving">
         <header> glyph · <strong>title · <menu>verbs </header>   one line at rest
         <progress>                                                 optional, the row's bar
         <section> the words · the verb · the age </section>       the fold
       </toast-card>
     The header's [aria-expanded] (a button in the menu, the SERVER's
     word) opens the fold; the host's ONE state word, stamped by the
     server, paints the dot (unread), quiets (read) or plays the exit
     (leaving). No other class, no other hook. The preview is small: one
     type step down, 20rem. The choreography of gf-470 stands: IN — the
     box grows, then the slide; OUT — the slide, then the collapse, then
     display none (allow-discrete); the server renders a put-away card
     as leaving for a grace window so the morph never cuts the exit. ── */
  :where(.toast) {
    --_dur: calc(var(--cfg-motion) * 0.2s);
    --type: -1;
    --glass-alpha: 0.75; /* a toast shows what is behind it, a little (Mike 2026-10-05: 0.88 read opaque over a dark map; 0.65 too thin, gf-519) */
    display: flex; flex-direction: column; gap: var(--gap, 0.25lh);
    inline-size: 20rem; max-inline-size: calc(100vw - 2lh);
    overflow: clip;
    opacity: 1; translate: 0 0;
    transition: block-size var(--_dur) ease-out, margin-block var(--_dur) ease-out,
                padding-block var(--_dur) ease-out, border-block-width var(--_dur) ease-out,
                opacity var(--_dur) ease-out var(--_dur), translate var(--_dur) ease-out var(--_dur),
                display calc(2 * var(--_dur)) allow-discrete;
    & + :where(.toast) { margin-block-start: 0.25lh; } /* the pile's spacing rides the cards, so a leaving card's whole footprint collapses */
    & > :where(header) {
      display: flex; align-items: center; gap: 0.5em; min-inline-size: 0;
      & > :where(svg) { inline-size: 1.25em; block-size: 1.25em; flex: none; }
      & > :where(strong) { flex: 1; min-inline-size: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
      & > :where(menu) { display: flex; flex: none; order: 2; margin: 0; padding: 0; margin-inline-start: auto; }
    }
    & > :where(progress) { inline-size: 100%; }
    /* THE FOLD: closed it is not a flex item; open it grows from 0 */
    & > :where(section) {
      display: none; flex-direction: column; gap: var(--gap, 0.25lh);
      overflow: hidden; block-size: 0; opacity: 0;
      transition: block-size var(--_dur) ease, opacity var(--_dur) ease, display var(--_dur) allow-discrete;
      & > :where(p) { margin: 0; --fg: -0.6; }
      & > :where(small) { --fg: -0.6; }
    }
    &:has(> header [aria-expanded="true"]) {
      & > :where(section) { display: flex; block-size: auto; opacity: 1; }
      & > :where(header) > :where(strong) { white-space: normal; overflow: visible; } /* an opened card shows its whole title (#205) */
      & > header [aria-expanded] svg { rotate: 90deg; }
    }
    & > header [aria-expanded] svg { transition: rotate var(--_dur) ease; }
    /* THE UNREAD DOT (#137, #198): a pseudo of the header, the state word its only switch */
    &[data-ui-state="unread"] > :where(header)::after {
      content: ""; order: 1; flex: none;
      inline-size: 0.5em; block-size: 0.5em; border-radius: 50%;
      background-color: var(--focus);
    }
    &[data-ui-state="read"] { --fg: -0.6; } /* quiet ink; never opacity on glass */
    /* OUT: the slide first, the collapse second, then gone */
    &[data-ui-state="leaving"] {
      opacity: 0; translate: 100% 0;
      block-size: 0; margin-block: 0; padding-block: 0; border-block-width: 0;
      display: none;
      transition: opacity var(--_dur) ease-in, translate var(--_dur) ease-in,
                  block-size var(--_dur) ease-out var(--_dur), margin-block var(--_dur) ease-out var(--_dur),
                  padding-block var(--_dur) ease-out var(--_dur), border-block-width var(--_dur) ease-out var(--_dur),
                  display calc(2 * var(--_dur)) allow-discrete;
    }
  }
  @starting-style { :where(.toast:has(> header [aria-expanded="true"])) > :where(section) { block-size: 0; opacity: 0; } }
  @starting-style { :where(.toast) {
    opacity: 0; translate: 100% 0;
    block-size: 0; margin-block: 0; padding-block: 0; border-block-width: 0;
  } }
  /* .prelines — multi-line producer text (the pipeline's per-table
     lines) keeps its newlines wherever it renders (drawer, toast). */
  :where(.prelines) { white-space: pre-line; overflow-wrap: anywhere; } /* a person's text: a long token wraps instead of widening its card (EventOS feedback #284) */

  /* copy-button (gf-164): the glyph wrappers dissolve into the icon
     button's own layout; the [hidden] one stays gone (UA default). */
  :where(copy-button) :where(span:not([hidden])) { display: contents; }

  /* ── global search palette — the ⌘K overlay and its nav trigger.
     [moved 2026-08 from an embedded <style> in layout.templ: ONE file.]
     Engine tokens only (--bg/--fg/--type/lh, no literal colours); the
     matched-term <mark> rides --fg:1 + --hue-shift so it tints off the
     shell hue. Overlay chrome (surface, backdrop, focus trap, animation)
     all comes from .modal; the dialog hosting a .palette re-shapes to a
     top-pinned column frame and hands the padding to the palette rows. ── */
  /* a modal that holds a tabpanel is a column: the head (identity, tabs)
     stays put and the panel — .grow.scroll-y in the markup — does the
     scrolling (feedback #158, 2026-09-24) */
  /* the tabbed modal is a column while it CLOSES too (EventOS feedback
     #184): display is held by the discrete transition after [open]
     drops, so only display may depend on [open] — a direction that
     dropped with it flipped the fading dialog to a row and its content
     went skinny mid-fade */
  :where(.modal:has(> .column > [role="tabpanel"])) { flex-direction: column; overflow: hidden; }
  :where(.modal[open]:has(> .column > [role="tabpanel"])) { display: flex; }
  :where(.modal) > :where(.column:has(> [role="tabpanel"])) { flex: 1 1 auto; min-block-size: 0; }
  :where(.modal):has(> .palette) {
    inline-size: min(92vw, 40rem); padding: 0; overflow: hidden;
    margin-block-start: 9vh; margin-block-end: auto;
  }
  :where(.gs-trigger) {
    display: inline-flex; align-items: center; gap: 0.5em;
    inline-size: 100%; max-inline-size: 20rem;
    --type: -1; justify-content: flex-start;
    & > svg { inline-size: 1.1em; block-size: 1.1em; --fg: -0.5; flex: 0 0 auto; }
    & :where(.gs-tlabel) { flex: 1; text-align: start; --fg: -0.55; }
  }
  :where(.palette) { display: flex; flex-direction: column; overflow: hidden; max-block-size: 82vh; }
  :where(.palette) :where(#gsearch-results) { flex: 1; min-block-size: 0; display: flex; flex-direction: column; overflow: hidden; }
  :where(.p-search) {
    display: flex; align-items: center; gap: 0.6em;
    padding: 0.55lh 0.7em; border-block-end: 1px solid var(--border);
    & > svg { inline-size: 1.2em; block-size: 1.2em; --fg: -0.5; flex: 0 0 auto; }
    & :where(input) { flex: 1; border: 0; background: transparent; --type: 1; padding: 0; block-size: auto; }
    & :where(input:focus) { outline: 0; box-shadow: none; }
    & :where(kbd) { cursor: pointer; }
  }
  :where(.p-scopes) { display: flex; gap: 0.4em; flex-wrap: wrap; padding: 0.45lh 0.7em; border-block-end: 1px solid var(--border); }
  :where(.p-results) { overflow-y: auto; overscroll-behavior: contain; padding: 0.35lh 0.4em 0.5lh; display: flex; flex-direction: column; gap: 0.1lh; flex: 1; }
  :where(.p-hint) { padding: 1.3lh 0.9em; text-align: center; --fg: -0.6; }
  :where(.p-group) { --fg: -0.55; --type: -2; letter-spacing: 0.08em; text-transform: uppercase; padding: 0.5lh 0.6em 0.2lh; }
  :where(.p-row) {
    display: flex; align-items: center; gap: 0.7em;
    padding: 0.4lh 0.6em; border-radius: var(--cfg-radius);
    cursor: pointer; text-decoration: none; color: inherit;
    & > svg { inline-size: 1.15em; block-size: 1.15em; --fg: -0.55; flex: 0 0 auto; }
    & :where(.p-body) { flex: 1; min-inline-size: 0; display: flex; flex-direction: column; gap: 0.05lh; }
    & :where(.p-title) { --type: 0.5; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
    & :where(.p-sub) { --fg: -0.6; --type: -1; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
    & :where(.p-ret) { --fg: -0.5; opacity: 0; flex: 0 0 auto; }
    & :where(mark) { background: transparent; --fg: 1; --hue-shift: -22; font-weight: 700; }
  }
  :where(.p-row[aria-selected="true"]) { --lift: 0.14; background-color: var(--_bg); --surf-l: var(--_bg-l); }
  :where(.p-row[aria-selected="true"]) :where(.p-ret) { opacity: 1; }
  :where(.p-ret, .p-foot) :where(kbd) {
    display: inline-flex; align-items: center; justify-content: center;
    --type: -1; line-height: 1; min-block-size: 1.7em; min-inline-size: 1.7em;
    & > svg { inline-size: 0.95em; block-size: 0.95em; display: block; }
  }
  :where(.p-foot) {
    display: flex; gap: 1.1em; flex-wrap: wrap;
    padding: 0.4lh 0.8em; border-block-start: 1px solid var(--border);
    --fg: -0.55; --type: -1;
    & > :where(span) { display: inline-flex; align-items: center; gap: 0.4em; }
  }

  /* ── calendar — the ops surface (v3, 2026-08): SIMPLE. The month
     sizes to content and GROWS with events; the page scrolls like any
     other page. Colour: --bg 0..1 quantitative (server-set CE scale),
     --lift for chrome, outline var(--focus) for today. Lanes computed
     in Go; bars carry placement knobs (--gc/--gr). The year ribbon is
     a server-rendered SVG — it scales at every size by nature. ── */
  :where(.cal-shell) { display: grid; gap: 0.5lh; align-items: start; }
  @container (width >= 768px) {
    :where(.cal-shell) { grid-template-columns: minmax(0, 1fr) 20rem; }
  }
  /* the month FILLS ITS BOX (2026-09-01, per Mike): CalWindow bakes a
     fixed six weeks, so the wrapper is header + six EQUAL tracks — day
     height is stable month to month, and inside a stretched card the
     weeks absorb the slack (each .cal-wk's own trailing fr soaks its
     share). flex: the card column hands the month its free height. */
  :where(.cal-month) { display: grid; grid-template-rows: auto repeat(6, minmax(0, 1fr)); flex: 1 1 auto; min-block-size: 0; }
  :where(.cal-dow) {
    display: grid; grid-template-columns: repeat(7, minmax(0, 1fr));
    & > * { text-align: center; --fg: -0.6; --type: -2; }
  }
  :where(.cal-wk) {
    display: grid; grid-template-columns: repeat(7, minmax(0, 1fr));
    /* EXPLICIT rows (--lanes is the week's server-set knob): the day
       cell's 1 / -1 must span every lane, and -1 only counts explicit
       lines. The trailing fr absorbs min-block-size on quiet weeks. */
    /* a lane is the bar's own line (EventOS feedback #283, 2026-10-05: no
       space above and below the words — more events stack in a week) */
    grid-template-rows: 1lh repeat(var(--lanes, 1), 0.8lh) minmax(0, 1fr);
    min-block-size: 2.8lh; border-block-start: 1px solid var(--border);
  }
  :where(.cal-d) {
    grid-column: var(--gc); grid-row: 1 / -1;
    min-inline-size: 0; margin: 1px; padding: 0.1lh 0.3em;
    display: flex; flex-direction: column;
    border-radius: max(3px, calc(var(--cfg-radius) - 5px));
    --lift: 0.04; background-color: var(--_bg); --surf: var(--_bg);
    & > :where(small) { --type: -3; --fg: -0.55; }
  }
  :where(.cal-d.out) { opacity: 0.4; }
  :where(.cal-d.today) { outline: 2px solid var(--focus); outline-offset: -1px; }
  :where(.cal-assets) { display: flex; gap: 0.25em; --fg: -0.5; margin-block-start: auto; }
  :where(.cal-assets) :where(svg) { inline-size: 0.85em; block-size: 0.85em; }
  :where(.cal-more) { --type: -3; --fg: -0.5; margin-block-start: auto; z-index: 1; }
  :where(.cal-b) {
    grid-column: var(--gc); grid-row: var(--gr); z-index: 1;
    /* no block margin (2026-09-01, per Mike): the bar fills its lane */
    min-inline-size: 0; margin: 0 2px; padding-inline: 0.3em;
    display: flex; align-items: center; overflow: hidden; white-space: nowrap;
    text-decoration: none; border-radius: max(3px, calc(var(--cfg-radius) - 6px));
    background-color: var(--_bg); --surf: var(--_bg); --surf-bg: var(--bg);
    & > :where(span) { --type: -2; overflow: hidden; text-overflow: ellipsis; }
  }
  :where(.cal-b.cont-l) { border-start-start-radius: 0; border-end-start-radius: 0; margin-inline-start: 0; }
  :where(.cal-b.cont-r) { border-start-end-radius: 0; border-end-end-radius: 0; margin-inline-end: 0; }
  @container (width < 560px) {
    :where(.cal-wk) { grid-template-rows: 1lh repeat(var(--lanes, 1), 0.75lh) minmax(0, 1fr); min-block-size: 2.6lh; }
    :where(.cal-b) > :where(span) { --type: -3; }
  }
  :where(.cal-cards) {
    display: grid; align-content: start; gap: 0.4lh;
    grid-template-columns: repeat(auto-fill, minmax(min(100%, 15rem), 1fr));
  }
  :where(.cal-legend) { display: inline-flex; align-items: center; gap: 0.3em; --type: -1; --fg: -0.6; }
  /* chips ride the same currentColor + --fg ink ramp as the SVG ribbon */
  :where(.cal-legend) :where(i) { inline-size: 0.8em; block-size: 0.8em; border-radius: 2px; background: currentColor; }

  /* ── .roster — THE SERVER-DRIVEN TABLE (2026-09-10, per Mike: "a
     simple CSS component… fully server driven, auto-updating"). One
     composition class on the div that holds a roster's head, its form
     and its table; every behaviour below is CSS over server markup —
     no client state, no rocket (the data-table element retired gf-98).
     Sits after the table recipe on purpose: the cell rules win the
     zero-specificity tie by source order. */
  /* AUTO-TRUNCATE (gf-54): a body cell never wraps — it clips on one
     line with an ellipsis so rows stay one line tall. ⚠ max-width on
     a td is IGNORED by auto table layout; the lever is the COLUMN
     width hint on the th, the td rule clips to what the column got. */
  :where(.roster) :where(tbody td) { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; --type: -1; }
  /* THE COLUMN MODEL (gf-159): every column hugs min-content via the
     1% squeeze; the .clip columns absorb the surplus (100% hint) and
     their td ellipsis handles the overflow. */
  :where(.roster) :where(thead th) { inline-size: 1%; white-space: nowrap; --type: 0; padding-block: 0; --lift: 0.08; }
  :where(.roster) :where(thead th.clip) { inline-size: 100%; }
  /* TONE, not lines (gf-24): the sticky head rides a small lift, the
     hovered row washes up one step. Borders stay retired. */
  :where(.roster) :where(tbody tr:hover) :where(td) { --lift: 0.06; background-color: var(--_bg); --surf: var(--_bg); }
  /* a cell that IS a checkbox lines its box up with the head's select-all
     (v2.1.1, EventOS feedback #563): at the start, never centred */
  :where(.roster) :where(td:has(> input[type="checkbox"]:only-child)) { text-align: start; }
  :where(.roster) :where(.center) { vertical-align: middle; }
  :where(.roster) :where(.center) :where(input[type="checkbox"]) { vertical-align: middle; }
  /* the head's controls (gf-24/67): 1lh squares, quiet ink that wakes
     when the header is hovered; an active one (a sort, a filter that
     is on) stays lit. */
  :where(.roster) :where(thead th) > :where(.row) { display: flex; }
  :where(.roster) :where(thead th) > :where(.row) > :where(a, button) { min-block-size: 0; min-inline-size: 0; block-size: 1lh; inline-size: 1lh; padding: 0; --fg: -0.35; }
  :where(.roster) :where(thead th:hover) > :where(.row) > :where(a, button) { --fg: -0.8; }
  :where(.roster) :where(thead th) > :where(.row) > :where(a, button):where([aria-pressed="true"], [data-ui-state="active"]) { --fg: 1; }
  /* the resting (off) sort glyph hides until its header is hovered —
     visibility, not display, so nothing shifts; the glyph holder never
     shrinks. */
  :where(.roster) :where(thead th) :where(span[data-ui-state]) { flex: 0 0 auto; }
  :where(.roster) :where(thead th) :where(span[data-ui-state]) > :where(svg) { flex: 0 0 auto; inline-size: 1.05em; block-size: 1.05em; }
  :where(.roster) :where(thead th) :where([data-ui-state="off"]:has(> svg)) { visibility: hidden; }
  /* a funnel whose menu is open stays visible — it is the menu's ANCHOR,
     and an anchored element whose anchor goes hidden is hidden with it
     (position-visibility: anchors-visible, the default); the pointer
     leaving the th for the menu used to vanish the menu (feedback #100) */
  :where(.roster) :where(thead th) :where([data-ui-state="off"]:has(> svg)):has(+ .menu:popover-open) { visibility: visible; }
  :where(.roster) :where(thead th:hover) :where([data-ui-state="off"]:has(> svg)) { visibility: visible; }
  :where(.roster) :where(thead th) :where(.badge) { position: static; translate: none; }
  /* the roster form is PLUMBING, not a panel (gf-67): it stands down
     like a card-hosted form. */
  :where(.roster) :where(form) { --bg: 0; background: none; padding: 0; border: 0; border-radius: 0; }
  /* the head's structure (gf-66): the tag row holds its line even
     empty; the rule between ⚙ and the actions is a bare hr. */
  :where(.roster) :where(.dt-tags) { min-block-size: 1lh; }
  :where(.roster) :where(.dt-head) :where(hr) { block-size: 1.2em; inline-size: 0; margin: 0; border: 0; border-inline-start: 1px solid var(--border); align-self: center; }
  :where(.roster) :where(.bulk-list) { margin: 0; max-block-size: 8lh; overflow-y: auto; }
  /* the SCROLLER is the painted, rounded box (gf-61): its radius clips
     the table's corners, and the 0.5lh scrolling shadows say "more
     data" exactly while any hides beyond an edge (local for the
     covers, scroll for the shadows — each fade self-erases at its end
     of travel). The translucent black is a SHADOW, not a color. */
  :where(.roster) :where(.scroll-x) {
    --bg: 0; border-radius: var(--cfg-radius);   /* the rows' own surface: the table under it is transparent (v3.2.1) */
    background-color: var(--_bg);
    background-image:
      linear-gradient(to right, var(--_bg) 40%, transparent),
      linear-gradient(to left, var(--_bg) 40%, transparent),
      linear-gradient(to right, oklch(0 0 0 / 0.35), transparent),
      linear-gradient(to left, oklch(0 0 0 / 0.35), transparent);
    background-position: left, right, left, right;
    background-size: 1lh 100%, 1lh 100%, 0.5lh 100%, 0.5lh 100%;
    background-repeat: no-repeat;
    background-attachment: local, local, scroll, scroll;
  }
  /* the table fills its box (gf-159): the .clip absorbers take the
     surplus; when min-contents exceed the box it grows past 100% and
     .scroll-x carries it. Transparent so the shadows read through. */
  :where(.roster) :where(table) { --bg: -0.2; --surf: var(--_bg); --surf-bg: var(--bg); inline-size: 100%; min-inline-size: 100%; }
}


/* ════════════════════════════════════════════════════════════════════
   PRODUCT COMPONENTS — graduated from the retired @layer project
   (2026-09-01, per Mike: single-tenant now — the "downstream consumer"
   layer made no sense, and its templ <style> block was a shadow second
   stylesheet the css metrics never saw). Same layer as every other
   component; this block sits later in the file, so on an equal-
   specificity tie these still win, exactly as their project-layer
   selves did. App rules that must beat a SKIN paint belong in skin.
   ════════════════════════════════════════════════════════════════════ */
@layer block {
	.org-mark svg { block-size: var(--mark-h, 1.4lh); inline-size: auto; display: block; }

	/* THE BUSY DIALOG (gf-390 rev 3): while a dialog's work runs on the
	   server (aria-busy stamped from the record), its form sleeps under
	   the wait cursor and the stacked <progress> speaks; the close stays
	   live. State rides ARIA, never a class. */
	:where(dialog[aria-busy="true"]) { cursor: wait; }
	:where(dialog[aria-busy="true"]) :where([role="form"], .spread) { pointer-events: none; opacity: 0.55; }

	/* (.toast promoted to the engine's components layer, gf-42 —
	   one overlay family, one home.) The STACK's clearance is
	   app composition: the bottom-right corner also hosts the
	   fab cluster, so the toast column floats above it. Gap is
	   zeroed because spacing rides the toasts' own sibling
	   margins (the engine collapses a leaving card's whole
	   footprint, so the stack glides shut). */
	/* scrollbar-gutter stable (EventOS feedback #197, Mike): the pile's
	   scrollbar appearing or leaving must not shift the cards' width */
	.hud > .br.toasts { --gap: 0; max-block-size: 100%; overflow: hidden auto; scrollbar-gutter: stable; }
	/* (overflow-x hidden, not auto: a card sliding past the edge is
	   scrollable overflow, and a scroll container with overflow-y alone
	   grows a horizontal scrollbar for the slide — the pile shifted by
	   its height on every entrance and exit, gf-470) */
	/* (the pile's bottom clearance lives in the utility layer beside the
	   slot padding it must out-rank — gf-409) */
	/* the Dismiss-all row heads the pile at the toasts' own spacing (feedback #153) */
	.hud > .br.toasts > .row { margin-block-end: 0.25lh; }
	/* (the 9lh rail widening is GONE, gf-72: the rail is
	   min-width now and grows for a long email by itself.) */

	/* the UI lab's resizable composition demos (gf-72):
	   drag the corner, the .container inside re-gates. Lab
	   plumbing only — never a product surface. */
	.lab-resize { resize: horizontal; overflow: auto; min-inline-size: 5rem; max-inline-size: 100%; inline-size: 18rem; border: 1px dashed var(--border); border-radius: var(--cfg-radius); padding: 0.25lh 0.5em; }

	/* (.nav-version/.nav-account promoted to the engine's
	   shell-anatomy block beside .nav-org/.nav-search, gf-42.) */

	/* (the anchor-center centering experiment for the config
	   menu is RETIRED — it never held in practice; the engine's
	   own anchor + flip fallbacks place every dt menu, and the
	   open-owner highlight above is the tell for which one.) */

	/* the config menu's COMPOSED rows (gf-31): everything the
	   rocket injects is class-driven — the recipes live HERE,
	   in lh + engine constants, never in JS cssText. Direct
	   button/a rows already carry the engine's .menu recipe;
	   these cover the rows the engine doesn't know: the size
	   stepper, the column cluster, the saved-views list. */
	/* standard step (gf-71): the head speaks type 0 like the
	   rails — no explicit knob. */
	.dt-head { align-items: center; }
	.menu > .row { min-block-size: 1.7lh; padding: 0 0.8em; align-items: center; }
	.menu .row > button { min-block-size: 0; padding: 0.1em 0.4em; }
	.menu > hr { margin-block: 0.15lh; }
	.menu > a > .tag { margin-inline-start: auto; }
	.menu [data-dt-cols] { flex-wrap: wrap; max-inline-size: 20rem; }
	.menu [data-dt-t] { min-inline-size: 2.2em; text-align: center; }
	/* the menu's SECTIONS are fieldsets (gf-32): the engine's
	   own field-group recipe gives each a quiet full-width
	   legend; they just need the menu's side rhythm. */
	.menu > fieldset { margin: 0.15lh 0.8em; max-inline-size: 20rem; }


	/* Metrics chart chrome (gf-69): title top-left, the layer
	   legend as a column top-right — both overlaid in margin
	   space the SVG reserves. (The color plane needs NO rules
	   here — pure engine composition, deliberately.) */
	.chart-host { position: relative; }
	/* the admin usage heatmap (year grid, Sun-Sat rows; moved
	   2026-08-28 from an embedded <style> in admin.templ) */
	/* the cell unit is REM on purpose (gf-184 fix, per Mike: "labels
	   incorrectly placed"): the labels wear --type: -2, so an em-sized
	   grid in the labels computed SMALLER than the em-sized heat grid —
	   months spanned half the graph, day labels drifted off their rows.
	   rem is the same length for every participant. */
	.usage-heat { display: grid; grid-template-rows: repeat(7, var(--hc, 0.72rem)); grid-auto-flow: column; grid-auto-columns: var(--hc, 0.72rem); gap: 2px; inline-size: max-content; }
	.usage-heat-cell { border-radius: 0.15em; }
	/* the GitHub chrome (gf-182, per Mike + the svg-charts skill's
	   quieting doctrine): day labels left (Mon/Wed/Fri), month labels
	   above spanning their weeks, an intensity RAMP legend below —
	   the swatches reuse the cell recipe, so legend and chart cannot
	   disagree. The whole figure is max-content inside the card's own
	   overflow-x scroller: it never squishes, it scrolls. */
	/* THE CELL FITS (feedback #118, Mike: "better to be small and all
	   visible than large and have to scroll"): .heat-wrap is the query
	   container; the cell unit --hc is what 53 weeks and the day labels
	   need to fit its width, capped at the old 0.72rem and floored where
	   a cell stops reading — below the floor the wrapper scrolls. */
	.heat-wrap { container-type: inline-size; overflow-x: auto; padding-block: 0.2lh; }
	.heat-fig { --hc: clamp(0.34rem, calc((100cqi - 2.6rem - 52 * 2px) / 53), 0.72rem); inline-size: max-content; display: grid; grid-template-columns: auto auto; gap: 0.2lh 0.4em; }
	.heat-months { grid-column: 2; display: flex; }
	.heat-month { inline-size: calc(var(--hm-w) * (var(--hc) + 2px)); overflow: hidden; white-space: nowrap; --type: -2; --fg: -0.55; }
	.heat-days { display: grid; grid-template-rows: repeat(7, var(--hc)); gap: 2px; --type: -2; --fg: -0.55; }
	.heat-days > span { grid-row: var(--hd-r); line-height: var(--hc); }
	.heat-legend { grid-column: 2; display: flex; align-items: center; gap: 2px; --type: -2; --fg: -0.55; justify-content: end; }
	.heat-legend .usage-heat-cell { inline-size: var(--hc); block-size: var(--hc); }
	.chart-host .chart-title { position: absolute; inset-block-start: 0.5lh; inset-inline-start: 0.8em; }
	.chart-host .chart-legend { position: absolute; inset-block-start: 0.6lh; inset-inline-end: 0.8em; align-items: end; }


	/* google-maps host geometry (gf-35): the component's
	   sizing contract lives HERE, not inline — the host is a
	   sized block, its one ignore-morphed child fills it for
	   Google's DOM. No key → the templ renders the empty
	   state instead, so a sized empty box can't happen. */
	/* rounded like every nested surface (Mike, 2026-08-31):
	   the same inner-radius formula the calendar's day cells
	   use; overflow clips Google's square canvas to it. */
	/* NO intrinsic size (Mike, 2026-08-31: no magic numbers) —
	   every placement declares its share explicitly (.rise). */
	google-maps { display: block; position: relative; border-radius: max(3px, calc(var(--cfg-radius) - 5px)); overflow: hidden; }
	google-maps > [data-ignore-morph] { position: absolute; inset: 0; }

	/* mini-map host geometry (gf-220): the static thumbnail's
	   box — same inner radius as its live sibling; the host
	   declares its own shape (a wide strip) because the card
	   flow it sits in has no explicit share to give it. The
	   rocket only maintains the img inside. */
	mini-map { display: block; aspect-ratio: 8 / 3; border-radius: max(3px, calc(var(--cfg-radius) - 5px)); overflow: hidden; }
	mini-map > img { display: block; inline-size: 100%; block-size: 100%; object-fit: cover; }

	/* the map OVERLAY recipes (2026-08-31): the rocket writes
	   classes + knobs only — every marker/popup style lives
	   here. Dots ride .bg + a SEMANTIC lock (kind = meaning);
	   the anti-tile ring/shadow are contrast against GOOGLE'S
	   render, a foreign surface, and are this layer's one
	   sanctioned non-token pair — say so before "fixing" them. */
	/* composable scopes sit right in the tab row, centered on
	   the underline band */
	.tabs-underline .tab-toggles { margin-inline-start: auto; align-self: center; }
	/* rail mini section header (Mike, 2026-08-31): small caps,
	   recessed ink, breathing room above — groups the folded
	   Wholesaler rail without competing with its links. */
	.rail-head { --type: -2; --fg: -0.55; text-transform: uppercase; letter-spacing: 0.08em; padding: 0.4lh 0.5em 0.1lh; }
	.rail-head:first-child { padding-block-start: 0.1lh; }
	/* .stub-slot — a DESIGNED placeholder (2026-09-01): a card region that
	   names the chart/surface that will live there. Dashed like the data
	   map's empty tables — "planned" reads the same everywhere. */
	.stub-slot { min-block-size: 9lh; border: 1px dashed var(--border); border-radius: var(--cfg-radius); display: grid; place-items: center; --fg: -0.55; --type: -1; }

	/* THE SCHEMA MAP (2026-09-01 as the Review data map; gf-242 the whole
	   schema on Database → Map). Furniture (edges) recessed on the card's
	   ink; boxes are quiet surfaces tinted by tier; an EMPTY table goes
	   dashed + drained. */
	.erd-map { inline-size: 100%; block-size: auto; }
	.erd-map .erd-edges { --fg: -0.5; }
	.erd-map .erd-edges :is(line, path) { fill: none; stroke: currentColor; stroke-opacity: 0.45; stroke-width: 1.2; }
	.erd-map .erd-edges circle { fill: currentColor; fill-opacity: 0.6; }
	.erd-map .erd-node rect { fill: currentColor; fill-opacity: 0.07; stroke: currentColor; stroke-opacity: 0.5; stroke-width: 1; }
	.erd-map .erd-node.empty { --fg: -0.55; }
	.erd-map .erd-node.empty rect { fill-opacity: 0.02; stroke-dasharray: 5 4; }

	/* county territory picker (2026-08-31): chart doctrine —
	   furniture recessed via fill/stroke opacity on the card's
	   own ink, selection is a MARK at --fg: 1 (each path re-runs
	   the engine resolver, so currentColor answers the knob). */
	.county-map { inline-size: 100%; max-inline-size: 28rem; block-size: auto; }
	/* non-scaling-stroke (gf-188, per Mike): the outlines are HAIRLINES at
	   any rendered size — without it the 0.7 SVG-unit stroke scaled with
	   the map and went unusably wide (the .il-county recipe knew this). */
	.county-map path { fill: currentColor; fill-opacity: 0.06; stroke: currentColor; stroke-opacity: 0.35; stroke-width: 0.7; vector-effect: non-scaling-stroke; cursor: pointer; }
	.county-map path:hover { fill-opacity: 0.22; }
	.county-map path.on { --fg: 1; fill-opacity: 0.6; stroke-opacity: 0.9; }
	/* THE GLANCE (EventOS feedback #279, 2026-10-05): the same recipe read
	   only, at a fixed share of the rhythm — 10 lines tall, as wide as
	   its cell, the state letterboxed inside — so a settings split sizes
	   by the theme, not by the map's own ratio; no pointer, no hover */
	.county-map[data-ui-state="glance"] { block-size: 10lh; max-inline-size: none; }
	.county-map[data-ui-state="glance"] path { cursor: default; }
	.county-map[data-ui-state="glance"] path:hover { fill-opacity: 0.06; }
	.county-map[data-ui-state="glance"] path.on:hover { fill-opacity: 0.6; }

	/* HOME full-bleed canvas + the timeline (2026-08-31, per
	   Mike: the letterbox dies — a dashboard fills its screen) */

	.gmap-dot { inline-size: 0.75em; block-size: 0.75em; border-radius: 50%; --bg: 0.8;
	  border: 2px solid rgb(255 255 255 / 0.9); box-shadow: 0 1px 3px rgb(0 0 0 / 0.35); }
	.gmap-chip { --bg: 0.12; border: 1px solid var(--border); border-radius: 999px;
	  padding: 0.15em 0.75em; font: 600 0.75rem var(--font-ui, system-ui, sans-serif);
	  white-space: nowrap; box-shadow: 0 1px 3px rgb(0 0 0 / 0.25); }
	.gmap-pop { min-inline-size: 14rem; display: flex; }
	.gmap-pop-title { text-decoration: none; font-weight: 700; }
	.gmap-pop-close { flex: 0 0 auto; --fg: -0.6; }
	/* the map's OWN control cluster (2026-09-01, per Mike): engine
	   buttons over Google's canvas — stock widgets are disabled. Painted
	   surfaces (the anti-tile ring doctrine above applies). */
	.gmap-ctl { position: absolute; inset-block-start: 0.5em; inset-inline-end: 0.5em; }
	.gmap-ctl > button { --bg: 0.25; background-color: var(--_bg); border: 1px solid var(--border); box-shadow: 0 1px 3px rgb(0 0 0 / 0.3); }

	.gmap-err { position: absolute; inset: 0; align-items: center; justify-content: center;
	  --gap: 0.3lh; --fg: -0.6; }


	/* (the column popover menus are GONE, gf-62 — with them
	   the label-trigger, open-owner and menu-width recipes.)
	   The ⚙ still tells when its panel is open: */

	/* the notification drawer GROWS with its feed to 80% of the
	   viewport, then scrolls — overriding the engine drawer's
	   fixed top-drawer height for this one surface. */
	/* the grown drawer is a column whose HEAD stays put and whose .grow.scroll-y
	   child does the scrolling (feedback #155) */
	.drawer-grow { block-size: auto; max-block-size: 80svh; overflow: hidden; }
	.drawer-grow[open] { display: flex; flex-direction: column; } /* the UA keeps the closed dialog display: none */
}


@layer block {
  /* the dormant home-map SVG unit (from components.templ, 2026-09-01) */
  /* layered 2026-08-31 (Mike's unlayered-css sweep): project is the
     LAST layer, so these keep beating every engine rule exactly as
     their unlayered selves did — only their cascade home changed. */
  .il-map-box { position: relative; inline-size: 100cqi; block-size: 100cqb; overflow: hidden; border: 1px solid var(--border); border-radius: var(--cfg-radius); }
  /* Context (neighbor) states — faint neutral silhouette for geographic
     context, no county strokes. (Adjacent neighbors share the fill so their
     shared borders merge into one surround; true per-state neighbor lines
     would need dissolved state-outline geometry, which isn't baked yet.) */
  .ctx-state { --surface-chroma: 0; --bg: 0.09; fill: var(--_bg); stroke: none; pointer-events: none; }
  /* State border — the home state's OUTER boundary, in the --focus accent.
     Drawn under the county fills (see maprender) so only the outer rim shows;
     the internal county-to-county segments are covered by the opaque fills. */
  .il-outline { --hue: 250; fill: none; stroke: var(--focus); stroke-width: 1.75; vector-effect: non-scaling-stroke; stroke-linejoin: round; stroke-linecap: round; pointer-events: none; }
  /* Home state — a little chromatic --bg goes a long way against the neutral
     neighbors. County borders are --border. Read-only: data-sel shades the
     counties in the configured territory (brighter fill). */
  .il-county path { --hue: 250; --surface-chroma: 0.06; --bg: 0.15; fill: var(--_bg); stroke: var(--border); stroke-width: 0.6; vector-effect: non-scaling-stroke; stroke-linejoin: round; stroke-linecap: round; }
  .il-county[data-sel] path { --surface-chroma: 0.09; --bg: 0.34; }
  /* Solid disc behind each marker glyph at --fg:0 — neutral surface, zero
     chroma, so the marker's chromatic --fg:1 glyph has maximum chroma contrast
     against it. */
  .mark-glass { --fg: 0; fill: currentColor; stroke: var(--border); stroke-width: 0.75; }
  .wh-mark { --fg: 1; color: var(--_fg); overflow: visible; }
  /* Event/activation markers — small chromatic dots. The group sets --fg
     (chromatic) + a relative --hue-shift (event vs activation); the core
     fills with that ink. A --_bg ring keeps it legible on any state fill. */
  .map-dot { cursor: pointer; }
  .map-dot:focus { outline: none; }
  .map-dot .dot-core { fill: currentColor; }
  .map-dot:hover .dot-core, .map-dot:focus-visible .dot-core { --fg: 0.95; }
  /* Orientation layers — inked by the engine (--fg) with a --_bg halo
     (paint-order:stroke) so names read over any fill. Non-scaling stroke
     keeps the halo a hairline at every zoom. */
  .place-dot { --fg: 1; fill: currentColor; pointer-events: none; }
  /* foreignObject HTML labels — laid out by the HTML engine (real kerning),
     colored by --fg (the engine sets `color` on every element). font-size
     (px = user units) is set inline so it scales with the camera. No halo —
     places run full-chroma (--fg 1) to pop, counties recede (--fg -0.75). */
  .map-label { font-family: var(--font-ui); line-height: 1; white-space: nowrap; display: flex; height: 100%; align-items: center; pointer-events: none; }
  .place-label { --fg: 1; font-weight: 600; justify-content: flex-start; }
  .county-label { --fg: -0.75; font-weight: 600; justify-content: center; text-align: center; letter-spacing: 0.04em; text-transform: uppercase; }
  /* Pin name labels — the tightest zoom tier: quieter than place names
     (the dot's chroma already says "event"), so the two layers don't
     compete when a town and an event share a block. */
  .pin-label { --fg: -0.35; font-weight: 500; justify-content: flex-start; }
  /* County zoom links — the county IS the control: a cursor + a brighter
     hover fill say "click to move the camera here". */
  a > .il-county path { cursor: zoom-in; }
  a:hover > .il-county path, a:focus-visible > .il-county path { --bg: 0.26; }
	

  /* (the billing calendar — .bill-cal and its cells — was CUT 2026-09-24,
     gf-411 step 4: the card states the renewal date) */
	
}


/* ════════════════════════════════════════════════════════════════════
   utility — the colour-owned utilities (late so a call-site helper
   beats a component's own hue/ink).
   ════════════════════════════════════════════════════════════════════ */
@layer utility {
  /* .active — THE transient-highlight utility (gf-191, per Mike): the
     standard active-row paint for CLIENT-owned, momentary emphasis (a
     combobox's keyboard row, a rocket's "this one"). Global on purpose —
     utilities are the layer for exactly this. Not for real current-ness:
     aria-current keeps pages/steps, aria-pressed keeps toggles. */
  :where(.active) {
    --bg: 0.3; background-color: var(--_bg); --fg: var(--_sem-ink, -1); font-weight: 600;
  }

  /* semantic hue helpers — lock hue + flag components to commit ink */
  :where(.suc) { --hue-lock: var(--hue-suc); --_sem-ink: 1 }
  :where(.inf) { --hue-lock: var(--hue-inf); --_sem-ink: 1 }
  :where(.wrn) { --hue-lock: var(--hue-wrn); --_sem-ink: 1 }
  :where(.dgr) { --hue-lock: var(--hue-dgr); --_sem-ink: 1 }

	/* Kind identity: events and activations are the same base hue, offset
	   ±30 so they read as related-but-distinct everywhere. The engine does
	   the rest; semantic --hue-lock pills (status) still override. */
	.is-event { --hue-shift: -30; }
	.is-activation { --hue-shift: 30; }

  /* black & white — the one mono mechanism */
  :where(.bw) { --chroma: 0; }

  /* frosted surface — the engine's own resolved surface, translucent,
     over a backdrop blur. Publishes like .bg. Knobs: --glass-alpha
     (0.65 — a dialog over its dimmed backdrop) · --glass-blur (0.5lh).
     Needs content BEHIND it to read as glass. TWO RULES (EventOS
     2026-10-05, toasts over a map unreadable): an overlay that must read
     over ANYTHING — a hud card — raises --glass-alpha (0.88; a toast wears
     0.75 and shows a little of what is behind it); and quiet is INK (--fg), never opacity on the card —
     opacity multiplies into the glass and the words go with it. */
  :where(.glass) {
    /* alpha via color-mix-with-transparent: PLAIN substitution of --_bg,
       the one consumption pattern the engine promises (RCS re-parsing of
       the unregistered intermediate is not promised and was the failure). */
    background-color: color-mix(in oklch,
      var(--_bg) calc(var(--glass-alpha, 0.65) * 100%), transparent);
    -webkit-backdrop-filter: blur(var(--glass-blur, 0.5lh));
    backdrop-filter: blur(var(--glass-blur, 0.5lh));
    --surf: var(--_bg); --surf-bg: var(--bg);
  }
}


/* ════════════════════════════════════════════════════════════════════
   utility (geometry slice) — pure-geometry utilities owned by layout.
   [cleanup: the duplicate .scroll-x rule in the old file is deduped]
   ════════════════════════════════════════════════════════════════════ */
@layer utility {
  /* .oneline — a row that must not wrap (header clusters, icon pairs).
     Composition class: replaces inline flex-wrap styles. */
  :where(.oneline) { flex-wrap: nowrap; }

  /* .start — a composition whose items align to the block start (an icon
     beside multi-line text). Replaces inline align-items. */
  :where(.start) { align-items: start; }

  /* hud slots — promoted past components so .cr etc. override a
     component's own place-self (the fab default is end/end). */
  :where(.hud) > :where(.tl) { place-self: start start   }
  :where(.hud) > :where(.tc) { place-self: start center  }
  :where(.hud) > :where(.tr) { place-self: start end     }
  :where(.hud) > :where(.cl) { place-self: center start  }
  :where(.hud) > :where(.cc) { place-self: center center }
  :where(.hud) > :where(.cr) { place-self: center end    }
  :where(.hud) > :where(.bl) { place-self: end start     }
  :where(.hud) > :where(.bc) { place-self: end center    }
  /* THE DOCK'S HANDLE RIDES AT THE PILLS' HEIGHT (v3.1.16, EventOS #820:
     "a little difficult to hit on the bottom of the screen"): the centre
     slot of a mobile hud lifts to the corner pills' middle */
  :where(.hud.mobile) > :where(.bc) { margin-block-end: 1.2lh; }
  :where(.hud) > :where(.br) { place-self: end end       }
  /* a corner slot used as a STACK of hud triggers: breathing room from the
     viewport edge, tight rhythm between members, members hug the corner. */
  :where(.hud) > :where(.tl, .tr, .bl, .br) { padding: 1lh; gap: var(--gap, 0.5lh); }
  /* THE DOCK (v3.1.8, EventOS #738, after iOS Calendar's bar): a GLASS
     corner slot is a PILL — the gutter becomes its margin, the pill wears
     the padding, the full radius and the quiet edge; its buttons are
     hud fabs, bare glyphs at rest on the glass. A hud wearing
     data-ui-state="away" slides its bottom slots down past the edge — a
     translate, nothing reflows — and a .cr fab is the handle that stays. */
  :where(.hud) > :where(.bl, .br):where(.glass) { margin: 1lh; padding: 0.15lh 0.35em; gap: 0.25em; border-radius: 999px; border: 1px solid var(--border); box-shadow: 0 0.3lh 1lh oklch(0 0 0 / 0.18); }
  :where(.hud) > :where(.bl, .br) { transition: translate calc(var(--cfg-motion) * 0.25s) ease; }
  :where(.hud[data-ui-state="away"]) > :where(.bl, .br) { translate: 0 calc(100% + 1.5lh); visibility: hidden; transition: translate calc(var(--cfg-motion) * 0.25s) ease, visibility 0s calc(var(--cfg-motion) * 0.25s); }
  /* THE TOAST PILE'S CLEARANCE (gf-409, 2026-09-24): above the fab
     cluster where a phone hud is on, above the dock where a main footer
     is — HERE, in the slot's own layer: the components-layer copy had
     lost to this padding for as long as it existed */
  :where(.page:has(> .hud.mobile:not([data-ui-state="away"]))) ~ :where(.hud) > :where(.br.toasts) { padding-block-end: 4lh; }
  :where(.page:has(> .pg-main-footer)) ~ :where(.hud) > :where(.br.toasts) { padding-block-end: 9lh; }
  :where(.hud) > :where(.tr, .br):where(.column) { align-items: flex-end; }
  :where(.hud) > :where(.tl, .bl):where(.column) { align-items: flex-start; }

  :where(.fill) { inline-size: 100%; }
  /* .grow — the block-axis complement to .fill: take the leftover space
     in a flex chain AND stay shrinkable (min-block-size: 0 defeats the
     auto-min). Chain it from a region's child down to a .scroll-y and
     the INNER surface becomes the scroller instead of the region. */
  :where(.grow) { flex: 1 1 auto; min-block-size: 0; }
  /* .far — the member pushed to the far end of its row (v3.1.11, EventOS
     #763): the trash tab at the end of a tab row, a verb at the right of
     a head. One declaration, where the toast's menu, the combo-box's
     clear and the list's tag each spelled it on their own. */
  :where(.far) { margin-inline-start: auto; }
  /* a growing box with an aspect (mini-map) takes the share its column
     has left, not its aspect: beside a sibling card that sets the row,
     it absorbs the difference (EventOS local feedback #28); 3lh keeps
     it legible when there is no share */
  :where(mini-map.grow) { aspect-ratio: auto; flex-basis: 0; min-block-size: 3lh; }
  /* .container — the generic self-measuring wrapper: content inside it
     gates on ITS width (cards in a grid, widgets in a flank). */
  :where(.container) { container-type: inline-size; }
  /* scroll viewports — the axis scrolls; pair with a constrained size (a
     flex/grid track, a max-block-size, etc.) since something must bound the
     box for it to scroll. min-*-size:0 defeats the flex/grid auto-min so it
     can shrink-and-scroll inside a parent; overscroll-behavior:contain stops
     scroll-chaining to the page. */
  :where(.scroll-x) { overflow-x: auto; overscroll-behavior: contain; min-inline-size: 0; }
  :where(.scroll-y) { overflow-y: auto; overscroll-behavior: contain; min-block-size: 0; scrollbar-gutter: stable; }
}


/* ════════════════════════════════════════════════════════════════════
   utility — the non-core slice.
   ════════════════════════════════════════════════════════════════════ */
@layer utility {
  /* an hr inside a composition is a LINE, not a spacer — the gap owns
     the rhythm. Kills the app-wide `style="margin: 0"` hr pattern. */
  :where(.row, .column, .spread, .spread-column, .lcr) > :where(hr) { margin-block: 0; }
  :where(.nowrap)   { white-space: nowrap; }
  /* .stress — the lab's resize handle (gf-175, per Mike: "give each
     form a resize so we can stress test"). Drag the corner; the
     specimen reflows at any width — and any height since the blocks
     (gf-608: a block answers its height too). A LAB utility — product
     surfaces size by composition, not by handles. */
  :where(.stress)   { resize: both; overflow: auto; min-inline-size: 14rem; }
  :where(.truncate) { white-space: nowrap; overflow: hidden; text-overflow: ellipsis; min-inline-size: 0; }

  /* .content-1 · .content-2 · .content-3 — a surface centred at a rung of
     THE CONTENT LADDER (the tokens in theme; 2026-10-01, replacing
     .measure + --measure, which said the word twice): the class names the
     rung, the rule is one for all three. It fills anything narrower. The
     dialog wears the same words — .modal reads --content with rung 2 as
     its default, so a confirm is a .modal.content-1 and a workbench a
     .modal.content-3; one ladder, two kinds of surface. Rosters take no
     rung: they fill the canvas. */
  :where(.content-1) { --content: var(--content-1); }
  :where(.content-2) { --content: var(--content-2); }
  :where(.content-3) { --content: var(--content-3); }
  :where(.content-1, .content-2, .content-3) { inline-size: 100%; max-inline-size: var(--content); margin-inline: auto; }

  /* (.type-flow is RETIRED, gf-68 — --type inherits natively now; the
     class was the opt-in workaround and its utility-layer rule squashed
     component-set steps, which the native cascade respects.) */
  :where(.center)   { text-align: center; }
  :where(.center) > :where(svg) { margin-inline: auto; } /* a declared-box glyph centres like the text beside it (EventOS gf-580) */
  :where(.vh)       { position: absolute; inline-size: 1px; block-size: 1px; overflow: hidden; clip-path: inset(50%); white-space: nowrap; }
  @media print { :where(.np) { display: none; } }
  :where([data-ui-state="on"])  { border-color: var(--focus); box-shadow: inset 0 0 0 1px var(--focus); }
  :where([data-ui-state="off"]:not(.tag)) { filter: grayscale(1) opacity(0.5); }
  /* ("active" lives in the skin layer — a standing STATE must outrank the
     shipped variants, and utility sits below skin in the layer order, so
     .bare's --bg: 0 was eating the state's --bg: 1.) */
}


@layer utility {
  /* FRAME-IN-FRAME, table edition (moved from components, gf-24 — it
     lived there as a DEAD rule: the table recipe sits later in that
     layer and out-ordered it at zero specificity, which is exactly the
     "weird borders" a card-hosted table showed): the card owns the
     frame; a table inside one keeps only its two vibrant rules. */
  :where(.card) :where(table) {
    border: 0; border-radius: 0; background: none;
  }

  /* ── data-ui-state, generalized past tags (gf-25, per Mike) ──────────
     The on-ring recipe (border + inset focus ring) needs a BOX; a bare
     icon holder — a span whose whole content is one svg — can't wear it.
     "on" commits full-chroma ink instead (the engine's own emphasis
     grammar); "off" keeps the standard grayscale quiet from above. */
  :where(span[data-ui-state="on"]:has(> svg:only-child)) {
    border: 0; box-shadow: none; --fg: 1;
  }

  /* .menu plumbing — must out-rank the button paint in components.
     ⚠ display/align/padding are set HERE, not assumed: a bare <a> row has no
     button recipe behind it, and without flex its icon and label stack. */
  :where(.menu):not(:popover-open) { pointer-events: none; }
  :where(.menu) > :where(button, a) {
    display: flex; align-items: center;
    /* a menu row is a ROW whatever its anatomy (gf-191): svg + small
       inside would otherwise match the DOCK recipe above (icon-over-
       caption, fixed box) — this later declaration reclaims it. */
    flex-direction: row; inline-size: auto; block-size: auto;
    justify-content: flex-start; gap: 0.5em;
    min-block-size: 1.7lh; padding: 0 0.8em;
    border: 0; --lift: initial; --bg: initial; background: transparent; box-shadow: none;
    text-decoration: none; --fg: var(--_sem-ink, -0.85); font-weight: 500;
    border-radius: max(0px, calc(var(--cfg-radius) - 3px));
  }
  :where(.menu) > :where(button, a) > :where(svg) { inline-size: 1.15em; block-size: 1.15em; flex: 0 0 auto; }
  :where(.menu) > :where(button, a):hover { --bg: 0.16; background-color: var(--_bg); }
  /* current/selected row — same grammar as .nav-item: state rides ARIA,
     and aria-selected joins it (gf-191, per Mike: the correct key for a
     listbox's chosen row — generalized here like .tabs already does).
     Transient client highlights wear the global .active utility instead. */
  :where(.menu) > :where(button, a):where([aria-current="page"], [aria-pressed="true"], [aria-selected="true"]) {
    --bg: 0.3; background-color: var(--_bg); --fg: var(--_sem-ink, -1); font-weight: 600;
  }
  /* match highlight in menu rows (gf-191, per Mike: "subtle professional
     — just --fg: 1"): the matched substring in chromatic ink, nothing
     else — no background, no bold, no hue shift (the palette's louder
     mark stays its own). */
  /* NO color property here: the core pipeline computes ink from --fg —
     an explicit color would override it (the palette's mark knows). */
  :where(.menu) :where(mark) { background: transparent; --fg: 1; }

  /* ── group-pick rows (gf-385): a menu row that is a DIV holding two
     buttons — the pick (grows, reads as the row) and the delete glyph
     at the end. Same height, hover and selected paint as a button row;
     the row's own buttons drop the button recipe. A disabled delete
     (the group is in use) stays visible, dimmed. ── */
  :where(.menu) > :where(div[role="option"]) {
    display: flex; align-items: center; gap: 0.25em;
    min-block-size: 1.7lh; padding-inline: 0.8em 0.3em;
    border-radius: max(0px, calc(var(--cfg-radius) - 3px));
    & > :where(button) { border: 0; --lift: initial; --bg: initial; background: transparent; box-shadow: none; min-block-size: 0; padding: 0; }
    & > :where([data-gp-pick]) { flex: 1 1 auto; display: flex; align-items: center; justify-content: flex-start; gap: 0.5em; text-align: start; --fg: var(--_sem-ink, -0.85); font-weight: 500; }
    & > :where([data-gp-pick]) > :where(svg) { inline-size: 1.15em; block-size: 1.15em; flex: 0 0 auto; }
    & > :where([data-gp-edit], [data-gp-del], [data-gp-save]) { --fg: -0.5; }
    & > :where([data-gp-del]):disabled { --fg: -0.25; }
    /* edit mode (rev 2): the row's own name input, in flow where the
       name was; the save check beside it. The rocket toggles hidden. */
    & > :where([data-gp-rename]) { flex: 1 1 auto; min-inline-size: 0; inline-size: auto; min-block-size: 0; padding-block: 0.1em; margin-block: 0.15lh; }
    &[data-ui-state="editing"] > :where([data-gp-pick], [data-gp-edit], [data-gp-del]) { display: none; }
    & > :where([hidden]) { display: none; }
    &:hover, &:where(.active, [aria-selected="true"]) { --bg: 0.16; background-color: var(--_bg); }
    &:where(.active, [aria-selected="true"]) > :where([data-gp-pick]) { --fg: var(--_sem-ink, -1); font-weight: 600; }
    &[hidden] { display: none; } /* the rocket filters by the hidden attribute; a declared display must not out-rank it */
  }
  :where(.menu) > :where([data-gp-empty]) { display: block; padding: 0.3lh 0.8em; }
  :where(.menu) > :where([data-gp-empty])[hidden] { display: none; }
}


/* ════════════════════════════════════════════════════════════════════
   skin — the shipped variants. NOT the project layer: project is the
   author's, reserved and empty.
   ════════════════════════════════════════════════════════════════════ */
@layer exception {
  /* .pri — the full-blast POSITION on the scale; label is max-contrast
     neutral. Opts out of the semantic ink commit by setting --fg literally. */
  .pri { --lift: initial; --bg: 1; --fg: -1; }

  /* .sec — frosted glass: engine-resolved translucent fill (RCS off the
     element's own resolved surface — no channel intermediates), a
     hue-tinted rim derived from the HOST surface, and NO --surf publish:
     the label must contrast what shows through, not the faint fill. */
  .sec {
    --lift: initial; --bg: 0.4; --fg: var(--_sem-ink, -0.9);
    background-color: oklch(from var(--_bg) l c h / clamp(0.12, calc(0.2 + var(--state) * 0.55), 0.55));
    -webkit-backdrop-filter: blur(0.6lh) saturate(1.5) brightness(calc(1 + var(--state) * 0.1));
    backdrop-filter: blur(0.6lh) saturate(1.5) brightness(calc(1 + var(--state) * 0.1));
    border: 1px solid oklch(from var(--surf)
      calc(l + (var(--cfg-dark) * 2 - 1) * 0.14) 0.12 var(--_h)
      / clamp(0.25, calc(0.45 + var(--state) * 0.45), 0.85));
  }
  /* semantic .sec (.suc/.inf/.wrn/.dgr): the GLYPH keeps the hue-locked
     ink, the LABEL returns to neutral (Mike, 2026-08-31 — hue on both
     reads loud; neutral text is the premium read). The span re-runs the
     resolver at -0.9, which walks the NEUTRAL pole regardless of the
     inherited hue-lock; a no-op on non-semantic .sec (same -0.9). */
  .sec:not(:disabled, [aria-disabled="true"]) > :where(span) { --fg: -0.9; }
  /* a PRESSED .sec is solid (feedback #143): the skin's translucent fill
     was out-cascading the button-group's pressed rule, so a held toggle
     read the same as its neighbours */
  .sec[aria-pressed="true"] { --bg: 1; --fg: var(--_sem-ink, -1); background-color: var(--_bg); --surf: var(--_bg); --surf-bg: var(--bg); }
  /* disabled rolls the label back WITH the glyph (2026-09-01, per Mike:
     the neutral-label rule was overriding the drained ink) — with no
     span-level --fg the button's disabled -0.3 inherits into both. */

  /* .bare — the chrome-less verb: no border, transparent at rest (the glyph
     IS the control); hover still lifts. Back-chevrons, inline dismissals. */
  .bare:where(button, .btn) { border: 0; --bg: 0; }

  /* "active" — full-blast standing state (gf-71): the .pri position as a
     STATE rather than a voice; lens toggles wear it (gf-79). A state must
     beat any variant, .bare included, so it sits AFTER them at matching
     specificity — no :where. For semantic cases prefer the ARIA the shell
     already speaks: aria-current="page" (nav items), aria-pressed
     (toggles), aria-selected (tabs). */
  [data-ui-state="active"] { --lift: initial; --bg: 1; --fg: -1; }

  /* disabled keeps drained ink over the component reads and .pri/.sec literals */
  button:disabled, button[aria-disabled="true"], .btn[aria-disabled="true"] { --fg: -0.3; }

  /* the shipped region paint — ALL pg-* defaults in ONE home now
     (pg-main-header's stray padding moved here from the grid) */
  .pg-banner      { --bg:  0.4; padding: 0.25lh; }
  .pg-header      { --bg: -0.8; padding: 0.25lh; }
  .pg-subheader   { --bg: -0.8; padding: 0.15lh 1em; }
  .pg-navigation  { --bg: -0.8; padding: 0.25lh; border-inline-end: 1px solid var(--border); }
  /* The toolbar is TERTIARY NAVIGATION FOR THE CONTENT BESIDE IT, so it shares
     that content's surface rather than sitting on its own panel. Given its own
     background it reads as a third region competing with the nav drawer, and the
     relationship to the tabs above becomes guesswork. Same --bg as pg-main, one
     hairline to separate them, and the two read as one card. */
  .pg-toolbar     { --bg: -0.3; padding: 0.25lh; border-inline-end: 1px solid var(--border); }
  /* the header's divider is a HAIRLINE (feedback #149, 2026-09-24: back
     at half a pixel once the home lost its search bar; rev 10 had dropped
     the 1px line). Drawn ONCE: when the header carries the tabs-underline,
     that underline IS the divider. */
  .pg-main-header { --bg: -0.3; padding: 0.25lh; border-block-end: 0.5px solid var(--border); }
  .pg-main-header:has(> .tabs-underline) { border-block-end: 0; }
  /* the bottom padding stands down ONLY when the tabs are the header's last
     element (feedback #75, gf-331): the underline needs to sit on the divider
     line; any other header keeps its default margin below. */
  .pg-main-header:has(> .tabs-underline:last-child) { padding-block-end: 0; }
  /* the subheader takes over the divider when present; when IT carries
     the tabs, the underline is the divider */
  .pg-main-header:has(+ .pg-main-subheader) { border-block-end: 0; }
  .pg-main-subheader { --bg: -0.3; padding: 0 0.25lh; border-block-end: 1px solid var(--border); }
  .pg-main-subheader:has(> .tabs-underline) { border-block-end: 0; }
  .pg-main-aside     { --bg: -0.6; padding: 0.25lh; border-inline-start: 1px solid var(--border); }
  /* TITLE ROW HEIGHT IS CONSTANT. Without this the row collapses to the title's
     line box when a screen carries no verbs and no filters, and grows to a
     control's height when it does — so moving between two tabs of the SAME
     section shifted the title and the tabs by ~2px. The row now always reserves
     one control height (the rack convention is --type: -1, which is what
     --_control-h resolves to here), whether or not anything is in it. */
  .pg-main-header > .spread {
    /* the verb rack's scale (the convention is --type: -1 on header
       controls). Since gf-68 the step INHERITS, so the rack's buttons
       pick it up for free; the title is untouched because headings set
       their own step. Reserve the height and the row stops moving. */
    --type: -1;
    min-block-size: var(--_control-h);
  }
  .pg-main        { --bg: -0.3; padding-inline: 0.25lh; }
  /* the dock (gf-407 rev 5): a lh of side room, and the safe-area insets
     where the page runs to the phone's rounded corners in app mode
     (viewport-fit=cover) — the home's account and bell must not sit in
     the corner radius */
  .pg-main-footer { --bg: -0.3; padding: 0.25lh max(1lh, env(safe-area-inset-right)) max(0.25lh, env(safe-area-inset-bottom)) max(1lh, env(safe-area-inset-left)); }
  /* the version tag LIVES IN THE UNSAFE AREA (gf-407 rev 10, Mike: "the
     perfect thing to go in the unsafe area"): a footer that ends in the
     tag drops the inset padding and the tag's box is the inset itself */
  .pg-main-footer:has(> .nav-version:last-child) { padding-block-end: 0.25lh; display: flex; flex-direction: column; gap: 0.25lh; }
  .pg-main-footer > .nav-version {
    display: flex; align-items: center; justify-content: center;
    min-block-size: env(safe-area-inset-bottom, 0px); --type: -3; --fg: -0.55;
  }
  .pg-aside       { --bg: -0.6; padding: 0.25lh; }  /* recessed: quieter than pg-main (-0.3) */
  .pg-footer      { --bg:  0.03; padding: 0.25lh; }

  /* ── the canvas, as PAINT — no spanning element ──────────────────────────
     The toolbar is tertiary navigation FOR the content beside it, so the two
     must read as one rounded card. That used to be a real element (.pg-canvas)
     spanning both areas, with the regions riding transparently on top — which
     inverted the painter contract and restated the inset three times.

     MEASURED (2026-08): two abutting regions painting the SAME tone show no
     seam — .page has no grid gap, so the split-corner composition is
     pixel-identical to the spanning element. The card is therefore now pure
     conditional paint: every canvas region defaults to the full rounded
     inset, and each ADJACENCY zeroes the shared edge. The grid state is the
     conditional; :has() reads it. Add or remove a region and the corners
     re-solve — nothing to opt out of, nothing empty to detect.

     A screen opts in by putting .canvas on the regions themselves (each
     region builds its own part of the page): pg-toolbar, pg-main, and —
     should a screen ever use it — pg-main-footer. */
  .page:has(> .canvas) {
    /* THE TONE STACK, and why this value. Three tiers have to stay legible:
       chrome (nav -0.8, page/header -0.5) < canvas < a .card ON the canvas
       (--bg: 0). Push the canvas to -0.06 and it collides with the cards
       sitting on it; leave it at pg-main's old -0.3 and it barely separates
       from the page. -0.2 keeps roughly even air on both sides. */
    --canvas: -0.2;      /* the pair's shared tone — set once, read everywhere */
    /* 0.25lh is THE spacing unit in this system: the gap of .column/.row/
       .spread and the padding of every pg-* region. The canvas gets the same
       one. A bespoke value here is how a composed surface drifts out of step
       with the compositions sitting inside it. */
    --canvas-gap: 0.25lh;

    /* chrome recedes to the page's own tone so the canvas is the only lit
       surface: the content is a thing ON the page, not another region OF it.
       The crumb bar is NOT in this list — it belongs to the app chrome above
       it and keeps pg-header's and pg-navigation's own -0.8, so the top strip
       reads as one band rather than three tones stacked. */
    & > .pg-main-header { --bg: -0.5; }
  }

  /* every canvas region paints the shared tone itself (the normal painter
     contract) and defaults to the FULL rounded inset — a lone pg-main.canvas
     is the whole card, four corners, no other rule needed. */
  .page > .canvas {
    --bg: var(--canvas);
    border-radius: var(--cfg-radius);
    margin: var(--canvas-gap);
  }

  /* ADJACENCIES — each zeroes the edge it shares, in logical properties.
     toolbar | main(+footer): the vertical seam */
  .page > .pg-toolbar.canvas {
    border-start-end-radius: 0; border-end-end-radius: 0;
    margin-inline-end: 0;
  }
  .page:has(> .pg-toolbar.canvas) > :is(.pg-main.canvas, .pg-main-footer.canvas) {
    border-start-start-radius: 0; border-end-start-radius: 0;
    margin-inline-start: 0;
  }
  /* main over footer: the horizontal seam */
  .page:has(> .pg-main-footer.canvas) > .pg-main.canvas {
    border-end-start-radius: 0; border-end-end-radius: 0;
    margin-block-end: 0;
  }
  .page > .pg-main-footer.canvas {
    border-start-start-radius: 0; border-start-end-radius: 0;
    margin-block-start: 0;
  }

  /* (The old NO-BLOCK-PADDING-ON-pg-main doctrine is RETIRED, gf-39: it
     protected a sticky table head inside a region-level scroller, and
     pg-main no longer scrolls — components own their scrolling, sticky
     heads stick inside their own .scroll-y, and pg-main wears the
     system's uniform 0.25lh padding like every other composition.) */

  /* Card-in-a-card is the defect this invites, and a bare table is where it
     shows: the table recipe is itself a card. Directly on the canvas it
     drops the frame and adopts the canvas tone. Scoped to a DIRECT child so
     a table inside a real .card section keeps its own frame. */
  .page > .pg-main.canvas > table {
    --bg: var(--canvas); border: 0; border-radius: 0; background-color: transparent;

    /* THE STICKY HEAD MUST BE TOLD THE TONE. --bg is registered
       inherits: false — "a surface's position must not leak to children" —
       so the head does NOT pick up the canvas value set on the table above.
       Left alone it falls to the initial 0 and paints the brightest tone in
       the system, which is why it read as a pale band floating over the
       list. It keeps a paint (it has to occlude the rows passing under it);
       it simply paints the surface it is sitting on. */
    & :where(thead th) { --bg: var(--canvas); }
  }

  /* skins — the extremes the system lerps between, re-picked: the surface
     anchors per bank and the chromatic ink's poles (--fg: 1 lands on the
     skin's accent; the formula is the root's, unchanged) */
  [data-ui-skin="carbon"] {
    --lt-neg: oklch(89% 0 0); --lt-zero: oklch(99.5% 0 0); --lt-pos: oklch(55.7% 0.243 0); --lt-flip: 0.60;
    --dk-neg: oklch(11% 0 0); --dk-zero: oklch(20% 0 0);   --dk-pos: oklch(64.7% 0.189 0); --dk-flip: 0.85;
    --fg-chr-d: 0.557; --fg-chr-l: 0.647; --fg-chroma: 0.21;   /* the chromatic ink's extremes: the skin's own accent */
    --cfg-radius: 0px;
  }
  [data-ui-skin="material"] {
    --lt-neg: oklch(87% 0.022 0); --lt-zero: oklch(98.4% 0.013 0); --lt-pos: oklch(49.6% 0.130 0); --lt-flip: 0.62;
    --dk-neg: oklch(9% 0.015 0);  --dk-zero: oklch(18.7% 0.012 0); --dk-pos: oklch(83.5% 0.095 0); --dk-flip: 0.70;
    --fg-chr-d: 0.496; --fg-chr-l: 0.835; --fg-chroma: 0.11;   /* the chromatic ink's extremes: the skin's own accent */
    --cfg-radius: 16px;
  }
  /* AN EXPLICIT CORNERS CHOICE OUTRANKS A SKIN'S DEFAULT (2026-09-29): the
     theme layer's [data-ui-radius] steps restated here, after the skins, so
     colour (the skin, the hue) and shape (the corners) are separate choices
     a person makes apart — a skin only proposes a radius. */
  [data-ui-radius="0"] { --cfg-radius: 0px; }
  [data-ui-radius="1"] { --cfg-radius: 6px; }
  [data-ui-radius="2"] { --cfg-radius: 12px; }
  [data-ui-radius="3"] { --cfg-radius: 22px; }
}


/* ── CROSS-DOCUMENT VIEW TRANSITIONS — their own layer, SIMPLIFIED
   (2026-09-01, per Mike: "ditch the complex view transitions for now
   and just use the cross blur everywhere until product is stable").
   The directional machinery — data-vt axes, the vt-main named group,
   eight push keyframes, the head's direction script — is RETIRED with
   it; git holds the whole figure if it ever returns. What remains is
   ONE uniform transition: every navigation cross-fades through a
   slight blur, ~160ms, riding --cfg-motion so reduced-motion (which
   zeroes the knob) collapses it to an instant swap. ── */
@layer exception {
  /* @view-transition { navigation: auto; }  — KILLED (gf-200, per Mike:
     "more trouble than it's worth"). Measured evidence, 2026-09-04: with
     the opt-in active, EVERY same-origin navigation stalled ~4s — the
     old document was held (its SSE stream stayed open four seconds past
     the new document's GET) waiting for a snapshot that never came,
     then the transition TIMED OUT and was skipped, showing the naked
     white interstitial. Cross-origin navigations (no VT possible): 21ms.
     So the transition delivered all cost and no cross-fade. The baked
     meta color-scheme keeps plain swaps dark-to-dark. The rules below
     stay for any future SAME-document transition experiments; the
     html[data-vt-ran] probe stays armed — re-enabling starts by
     uncommenting this line and watching that probe. */
  /* ⚠ THE BLEND IS LOAD-BEARING (2026-09-01, the FOUC hunt): the UA's
     default cross-fade applies plus-lighter through its OWN animation,
     so overriding animation-name silently wiped it — mid-transition the
     two half-transparent snapshots summed below 100% and the raw canvas
     flashed through on EVERY navigation (read as FOUC). Restoring the
     blend explicitly, with LINEAR timing on both sides so the opacities
     sum to exactly 1 at every instant, makes the cross seamless again;
     the blur rides on top. */
  ::view-transition-image-pair(root) { isolation: isolate; }
  ::view-transition-old(root),
  ::view-transition-new(root) {
    mix-blend-mode: plus-lighter;
    animation-duration: calc(var(--cfg-motion, 1) * 160ms);
    animation-timing-function: linear;
    animation-fill-mode: both;
  }
  ::view-transition-old(root) { animation-name: vt-blur-out; }
  ::view-transition-new(root) { animation-name: vt-blur-in; }

  /* THE SHELL STANDS STILL (2026-09-01, per Mike): chrome that is
     identical on both documents gets its OWN named group — a named
     subtree is lifted out of the root snapshot, so the cross-blur
     covers only what actually changes. An identical pair cross-fades
     invisibly (and when the nav's aria-current mark moves, only that
     region softly swaps). Geometry is safe to name because these
     regions keep one size across pages at a given density. */
  /* ⚠ ONE NAME, ONE RENDERED BOX (gf-172): a view-transition-name
     shared by two RENDERED elements makes Chrome skip the ENTIRE
     transition — every navigation becomes a hard cut that reads as
     page-wide FOUC. The responsive ladder ships literal duplicates
     (.hud.mobile beside .hud.tablet.desktop), saved today only by the
     hidden tier's display:none; name each tier DISTINCTLY so a gate
     tweak can never collide them. */
  :where(.pg-navigation.mobile) { view-transition-name: vt-nav-m; }
  :where(.pg-navigation:not(.mobile)) { view-transition-name: vt-nav; }
  :where(.hud.mobile)           { view-transition-name: vt-hud-m; }
  :where(.hud:not(.mobile))     { view-transition-name: vt-hud; }
  /* the TOASTS hud is a second not-.mobile .hud on desktop — two rendered
     boxes on one name = Chrome skips EVERY transition (the gf-172 law,
     nearly re-tripped gf-188: the collision arms whenever a toast is
     unseen). Toast content differs per page anyway — it rides the root
     cross-blur, never a lifted group. */
  :where(.hud:has(> .toasts))   { view-transition-name: none; }
}


/* ════════════════════════════════════════════════════════════════════
   media — responsive show/hide via revert-layer. LAST meaningful layer:
   the gate's display must out-rank a component's display. (Inline display
   still wins — never set display inline on a gated element.)

   Why display (not visibility/opacity): display:none removes the element
   from the tab order AND the accessibility tree, so the hidden variant
   cannot be tabbed into or announced. Don't add aria-hidden/inert/tabindex
   to gated copies — redundant on the hidden one, a bug on the shown one.
   ════════════════════════════════════════════════════════════════════ */
@layer visibility {
  /* Breakpoints follow the Bootstrap grid boundaries: phones < 576 ·
     576–768 tablet · ≥768 desktop. THIS FILE IS THE SINGLE SOURCE —
     change thresholds here, in this layer; there is no override file.
     (The old @layer project — the reserved downstream-consumer slot —
     retired 2026-09-01, per Mike: single-tenant; app components live in
     this file's components layer like every other component.)
     THE GATES ARE :where()-WRAPPED (v3.1.1, EventOS gf-665): zero
     specificity like every rule, so an element wearing a device gate
     and a container tier is decided by source order (the tiers sit
     later). Still ONE FAMILY PER ELEMENT: two gates cannot AND through
     the cascade — the later family's verdict stands alone; gate the
     second family on a wrapper. */
  :where(.mobile, .tablet, .desktop, .ultrawide) { display: none }
  @media (width < 576px)               { :where(.mobile)  { display: revert-layer } }
  /* ULTRA-WIDE (v3.1.15, EventOS #803): pixels AND aspect — a 21:9 desk
     at 1920 and up, never a tall 4K; a member of the viewport family,
     shown there alone (a control that only matters on such a screen) */
  @media (width >= 1920px) and (min-aspect-ratio: 2/1) { :where(.ultrawide) { display: revert-layer } }
  /* THE DOCK TAKES NO CLEARANCE (v3.1.9, EventOS #741 #742): the canvas
     runs to the bottom edge under the pills — the dock's handle is the
     relief for the rare control they cover. (v3.1.8 and before padded
     pg-main, the toolbar and the main footer by the bar's height; the
     gap read as a hole once the dock was away.) */
  /* a gated crumb takes its separator with it (gf-407: the home crumb is
     .mobile; its follower's chevron must not stand alone wider) */
  @media (width >= 576px) { :where(.crumbs) > .mobile + :where(a, span)::before { display: none } }
  @media (576px <= width < 768px)      { :where(.tablet)  { display: revert-layer } }
  @media (width >= 768px)              { :where(.desktop) { display: revert-layer } }

  /* [RETIRED 2026-08] the declared aside width: asides are content-
     sized now (containment removed in core.layout), so the auto track
     hugs whatever one-off widget the flank carries. */
  /* [RETIRED 2026-08] the narrow shell re-flow: ONE topology. Dynamic
     design is gates, not alternate grids — a region that cannot fit at
     a width gates itself (.c-desktop on an aside) and its track
     collapses. Narrow reachability of a gated flank is an app decision
     (dock, drawer), not a shell re-slot. */
  /* DENSITY TIERS (component scale): small = the glyph alone; medium =
     glyph + label; large = the full block. "and-up" is the multi-class
     idiom (class="medium large"). The c-* family below remains the
     LAYOUT band family — two families, two jobs.
     THE 8rem BOUNDARY IS RAIL MATH (gf-23): the tiers are what a rail's
     content gates on, so small/medium must STRADDLE the rail's two
     declared widths at EVERY type size. Icon width (glyph anatomy) tops
     out ≈ 4rem at xl type; the labeled 8lh bottoms out ≈ 9.7rem at sm
     type (rem is fixed, lh rides the type scale — that skew is why
     10rem broke: labeled-at-sm fell into .small and stacked the foot).
     8rem sits between them with margin on both sides. */
  :where(.small, .medium, .large)              { display: none }
  @container (width < 8rem)            { :where(.small)  { display: revert-layer } }
  @container (8rem <= width < 20rem)   { :where(.medium) { display: revert-layer } }
  @container (width >= 20rem)          { :where(.large)  { display: revert-layer } }

  /* the c-* family — the SAME breakpoints as .mobile/.tablet/.desktop,
     measured against the CONTAINER instead of the viewport. Inside a pg
     region the available width is the region's, not the window's (the
     nav column eats 140–260px), so region content declares its ranges
     against the surface it actually lives on. pg-main-header and
     pg-main are the query containers (core.layout). */
  :where(.c-mobile, .c-tablet, .c-desktop)     { display: none }
  @container (width < 576px)           { :where(.c-mobile)  { display: revert-layer } }
  @container (576px <= width < 768px)  { :where(.c-tablet)  { display: revert-layer } }
  @container (width >= 768px)          { :where(.c-desktop) { display: revert-layer } }

  /* ORIENTATION and HEIGHT gates (EventOS gf-608) — answered only inside
     a SIZE container (a .block): a width-only .container cannot answer
     them and the gate stays hidden. Landscape is wider than 3:2, the
     block's own law; tall is 10em and up. */
  .c-portrait, .c-landscape            { display: none }
  @container (aspect-ratio <= 3/2)     { .c-portrait  { display: revert-layer } }
  @container (aspect-ratio > 3/2)      { .c-landscape { display: revert-layer } }
  .c-short, .c-tall                    { display: none }
  @container (height < 10em)           { .c-short { display: revert-layer } }
  @container (height >= 10em)          { .c-tall  { display: revert-layer } }

  :where(.fine, .coarse)                       { display: none }
  @media (pointer: fine)               { :where(.fine)   { display: revert-layer } }
  @media (pointer: coarse)             { :where(.coarse) { display: revert-layer } }

  /* FOCUS MODE chrome-hide — lives here (not in core.layout) so it beats
     component display rules. .crumbs sets display:flex in @layer components,
     which is later than core.layout; only a rule at or after components can
     hide a .pg-subheader.crumbs. The media layer is the sanctioned home for
     display-state switches (the .mobile/.tablet/.print gates above), and focus
     mode is exactly that kind of switch. */
  .page:where([data-ui-state="focus"]) > :where(.pg-banner, .pg-header,
      .pg-subheader, .pg-navigation, .pg-toolbar, .pg-aside,
      .pg-main-aside, .pg-footer) {
    display: none;
  }
  /* focus mode hides the toolbar but :has() still sees it in the DOM — the
     card would keep a squared, flush left edge against a rail that is no
     longer there. Re-solve to the lone-main shape (this layer is later than
     skin, so it wins). */
  .page:where([data-ui-state="focus"]) > .pg-main.canvas {
    border-radius: var(--cfg-radius);
    margin: var(--canvas-gap);
  }

  /* PRINT — the ink-on-paper view. The themed surfaces are a screen medium;
     on paper the system becomes austere. Keep pg-main* (content + its header
     for context), drop every other chrome region, and quiet the surfaces to
     white while KEEPING borders — hairline structure, no fill. This is where
     Tufte's asceticism lives without compromising the screen experience. */
  @media print {
    /* THE PAPER IS LETTER (v3.1.2, EventOS: "optimized for 8½ × 11"): one
       sheet, half-inch margins. THE CANVAS KEEPS A HEIGHT: a block page
       makes pg-main auto-high, and a size container inside it (a board's
       cell) then has no height at all — it printed nothing. The main is
       let free and a main's cell is given the sheet's height less the
       head, so its board arranges on paper as it does on a screen. */
    @page { size: letter; margin: 0.5in; }
    .page {
      display: block; height: auto; overflow: visible;
    }
    .page > :where(.pg-main) { overflow: visible; block-size: auto; min-block-size: 0; }
    .page > :where(.pg-main) > :where(section.cell) { block-size: calc(100vh - 4lh); }
    .page > :where(.pg-banner, .pg-header, .pg-subheader, .pg-navigation,
                   .pg-toolbar, .pg-aside, .pg-main-aside, .pg-footer, .pg-main-subheader),
    :where(.focus-fab) { display: none; }
    /* PAPER TAKES NO ACTION (v3.1.3, EventOS gf-694): no verb, no menu, no
       trail, no tab prints — the main header keeps its title, the canvas
       its facts; the subheader (the tabs) goes with the rails */
    .page :where(button, [popover], .crumbs, .tabs, .tabs-underline, .fab-1, .fab-2, .fab-3, .hud) { display: none; }

    /* surfaces → white; borders stay. Regions and painted components lose
       their fill but keep their outline. */
    :where(.page, .pg-main, .pg-main-header, .pg-main-subheader,
           .pg-main-footer, .pg-main > section, .card, table, thead th, .search-box) {
      background: none;
      --bg: 0; --lift: initial;
    }
    /* ink → black on white, regardless of screen tone */
    :where(.pg-main, .pg-main *) { --fg: -1; }
    /* let content use the full page width on paper */
    :where(.pg-main) { max-inline-size: none; }
  }
}



/* Keyframes are global by name — outside @layer, like the engine's own. */
@keyframes vt-blur-out { to   { opacity: 0; filter: blur(6px); } }

@keyframes vt-blur-in { from { opacity: 0; filter: blur(6px); } }

/* The RECIPE SHELF (gf-181: .vtd-hero · .vtd-slide · .vtd-fade · [data-vtd])
   was cut 2026-09-29 with the lab's View-transitions bench (gf-429): the
   patterns live in git; cross-document VT has been off since gf-200. */
/* keyframes are global by name — outside @layer */
@keyframes progress-slide {
  0%   { background-position: -43% 0; }
  100% { background-position: 143% 0; }
}
