/* ==========================================================================
   8-BIT NES · components.css  —  stateless recipes, class-based
   Cross-framework: plain classes, no runtime. Soft fills via color-mix().
   Every value flows from tokens.css. Uses native CSS nesting.
   ========================================================================== */

/* inherits: true so that BOTH forms work — on the bar, <span class="pbar"
   style="--fill:64%">, and on the piece that draws it, <i style="--fill:64%">.
   It was false, which made `.pbar { --fill: 0% }` unreadable by anything: the
   child resolved this registration's own initial-value instead, so a consumer who
   set it on the container (what that declaration implies) got a bar stuck at 0%
   with no error anywhere. Registered so it can be transitioned. */
@property --fill {
  syntax: "<percentage>";
  inherits: true;
  initial-value: 0%;
}

@layer components {

  /* ----------------------------------------------------------------- BUTTON */
  .btn {
    --accent: var(--primary);
    --accent-d: var(--primary-d);
    display: inline-flex;
    align-items: center;
    justify-content: center;
    gap: var(--sp-2);
    font-family: var(--font-mono);
    font-size: var(--ctrl-fs);
    font-weight: var(--fw-bold);
    text-transform: uppercase;
    letter-spacing: var(--ls-chrome);
    color: var(--ink-on-accent);
    background: var(--accent);
    border: var(--bw) solid var(--accent-d);
    box-shadow: var(--sh-3);
    /* height-anchored to the shared --ctrl-h so a button lines up with an input
       / select at the same size; inline padding + font scale with the rung. */
    min-block-size: var(--ctrl-h);
    padding-block: 0;
    padding-inline: var(--ctrl-pad);
    cursor: pointer;
    /* square 90° corners — hard shadow + press-in do the work, no bevel */
    transition: transform var(--dur-fast) var(--ease),
      box-shadow var(--dur-fast) var(--ease),
      background var(--dur-fast);

    &:hover {
      filter: brightness(1.08);
    }

    &:active {
      transform: translate(2px, 2px);
      box-shadow: var(--sh-1);
    }

    /* SIGNATURE press */
    &:disabled {
      background: var(--panel-2);
      color: var(--dim);
      border-color: var(--line-hi);
      box-shadow: none;
      cursor: not-allowed;
      filter: none;
    }

    /* ghost: quiet control on dark */
    &.ghost {
      background: var(--panel);
      color: var(--muted);
      border-color: var(--line-hi);
      box-shadow: var(--sh-2);

      &:hover {
        color: var(--ink);
        border-color: var(--primary);
        filter: none;
      }
    }

    /* toggle-on: fills with its accent */
    &[aria-pressed="true"] {
      background: var(--accent);
      color: var(--ink-on-accent);
      border-color: var(--accent-d);
    }

    /* ---- variants (solid is the default above; these opt out of the fill) ---- */

    /* outline: transparent, accent edge + accent text */
    &.outline {
      background: none;
      color: var(--accent);
      border-color: var(--accent);
      box-shadow: var(--sh-2);

      &:hover {
        background: color-mix(in srgb, var(--accent) 14%, transparent);
        filter: none;
      }
    }

    /* soft: low-tint accent fill, accent text — quiet but still colored */
    &.soft {
      background: color-mix(in srgb, var(--accent) 18%, transparent);
      color: var(--accent);
      border-color: color-mix(in srgb, var(--accent) 45%, transparent);
      box-shadow: var(--sh-2);

      &:hover {
        background: color-mix(in srgb, var(--accent) 26%, transparent);
        filter: none;
      }
    }

    /* link: text-only action — drops the border, shadow, and press */
    &.link {
      background: none;
      color: var(--accent);
      border-color: transparent;
      box-shadow: none;
      padding-inline: 0;
      min-block-size: 0;
      /* text-only: no control-row height */

      &:hover {
        filter: none;
        text-decoration: underline;
        text-underline-offset: 3px;
      }

      &:active {
        transform: none;
        box-shadow: none;
      }
    }

    /* ---- sizes: remap the shared rung, so a .btn.sm == an input[data-size=sm]
       in height. Same effect as data-size="…", kept as classes for ergonomics
       (and back-compat). xs → dense AI toolbars; xl → hero CTAs. ---- */
    &.xs {
      --ctrl-h: var(--ctrl-h-xs);
      --ctrl-pad: var(--ctrl-px-xs);
      --ctrl-fs: var(--ctrl-fs-xs);
      gap: var(--sp-1);
    }

    &.sm {
      --ctrl-h: var(--ctrl-h-sm);
      --ctrl-pad: var(--ctrl-px-sm);
      --ctrl-fs: var(--ctrl-fs-sm);
    }

    &.lg {
      --ctrl-h: var(--ctrl-h-lg);
      --ctrl-pad: var(--ctrl-px-lg);
      --ctrl-fs: var(--ctrl-fs-lg);
      box-shadow: var(--sh-4);
    }

    &.xl {
      --ctrl-h: var(--ctrl-h-xl);
      --ctrl-pad: var(--ctrl-px-xl);
      --ctrl-fs: var(--ctrl-fs-xl);
      box-shadow: var(--sh-4);
    }

    /* ---- layout modifiers ---- */
    &.block {
      display: flex;
      inline-size: 100%;
    }

    /* icon-only: a perfect square at the current rung — pair with aria-label.
       Width tracks --ctrl-h via aspect-ratio, so it matches every size. */
    &.icon {
      padding-inline: 0;
      aspect-ratio: 1;
    }

    /* ---- loading: spinner ticks in, clicks blocked. Toggle aria-busy. ---- */
    &[aria-busy="true"] {
      cursor: progress;
      pointer-events: none;

      &::before {
        content: "";
        flex: none;
        inline-size: 1em;
        block-size: 1em;
        border: var(--bw-2) solid currentColor;
        border-inline-end-color: transparent;
        border-block-end-color: transparent;
        animation: spinner .7s linear infinite;
      }
    }
  }

  /* ------------------------------------------------------------- BUTTON GROUP
     Joins independent .btn actions into one seamless bar — an action toolbar,
     NOT a single-select (that's .segment). Each child keeps its own variant,
     accent, size and state; the group just fuses their edges + shadow.
     Split button = a .btn-group of [main .btn] + [caret .btn.icon].
     Wrap children in exactly one .btn-group; mix any variants freely. */
  .btn-group {
    display: inline-flex;
    box-shadow: var(--sh-3);

    /* children shed their own elevation — the bar carries one shared shadow */
    &>.btn {
      position: relative;
      box-shadow: none;

      /* the touched button floats up so its full border reads over the seam */
      &:hover,
      &:focus-visible {
        z-index: var(--z-raised);
      }

      /* press in place: translating would tear the seam, so darken instead */
      &:active {
        transform: none;
        box-shadow: none;
        filter: brightness(.9);
      }
    }

    /* collapse the double edge between neighbours into a single seam line */
    &>.btn+.btn {
      margin-inline-start: calc(-1 * var(--bw));
    }

    /* full-width bar: children share the row equally */
    &.block {
      display: flex;

      &>.btn {
        flex: 1;
        justify-content: center;
      }
    }

    /* stacked bar: same fuse, vertical — good for mobile action menus */
    &.stack {
      flex-direction: column;

      &>.btn {
        justify-content: center;
      }

      &>.btn+.btn {
        margin-inline-start: 0;
        margin-block-start: calc(-1 * var(--bw));
      }
    }
  }

  /* ------------------------------------------------------------------- CARD */
  .card {
    --accent: var(--primary);
    --accent-d: var(--primary-d);
    container-type: inline-size;
    background: var(--panel);
    border: var(--bw) solid var(--line-hi);
    border-inline-start: var(--accent-bar) solid var(--accent);
    box-shadow: var(--sh-4);
    padding: var(--sp-4);
    transition: border-color var(--dur-fast);

    /* hover lifts the three non-accent edges to the accent — never touch the
       inline-start accent bar (that's the signature classifier). */
    &:hover {
      border-block-color: var(--accent);
      border-inline-end-color: var(--accent);
    }

    &.lg {
      box-shadow: var(--sh-5);
      padding: var(--sp-5);
    }

    &>.head {
      display: flex;
      align-items: center;
      gap: var(--sp-3);
      margin-block-end: var(--sp-3);
    }

    & .num {
      flex: none;
      display: grid;
      place-items: center;
      inline-size: 1.75rem;
      block-size: 1.75rem;
      font-family: var(--font-mono);
      font-size: var(--fs-chip);
      font-weight: var(--fw-bold);
      color: var(--ink-on-accent);
      background: var(--accent);
      border: var(--bw-2) solid var(--accent-d);
      box-shadow: var(--sh-2);
    }

    & .title {
      font-family: var(--font-mono);
      font-size: var(--fs-h2);
      font-weight: var(--fw-bold);
      color: var(--ink);
      line-height: var(--lh-tight);
    }
  }

  @container (max-width:280px) {
    .card .num {
      display: none;
    }
  }

  /* ---------------------------------------------------------------- CALLOUT */
  .callout {
    --accent: var(--gold);
    --mark: "*";
    position: relative;
    border: var(--bw-2) solid var(--accent);
    background: color-mix(in srgb, var(--accent) 12%, transparent);
    padding: var(--pad-box);
    /* the inline half of --pad-box, plus the marker column */
    padding-inline-start: calc(var(--sp-4) + var(--sp-5));
    font-size: var(--fs-body);

    /* WCAG 2.2 · 1.4.1 Use of Color. Hue was the ONLY thing saying which kind of
       callout this is, and hue is not enough here: warn (#ff9e2c) against gotcha
       (#f23c4e) is the warning/danger pair that matters most, and memo against the
       default was not "two warm yellows" but the same gold — measured identical,
       so those two were indistinguishable at 100%, not merely close. It bites
       hardest where callouts are generated: a consumer rendering `> [!WARNING]`
       has to strip the marker, and then the kind exists nowhere on screen.

       Absolutely positioned in a reserved gutter so it holds for any content
       shape — inline text, one <p>, or a stack of blocks. The glyph is decorative
       to a screen reader (`/ ""`), because the panel's own label carries the kind
       in text; the unslashed declaration above it is the fallback for engines that
       do not parse alt text. Opt out with --mark: none (add
       padding-inline-start: var(--sp-4) to reclaim the gutter). */
    &::before {
      content: var(--mark);
      content: var(--mark) / "";
      position: absolute;
      inset-block-start: var(--sp-3);
      inset-inline-start: var(--sp-4);
      inline-size: var(--sp-5);
      font-family: var(--font-mono);
      font-weight: var(--fw-bold);
      line-height: inherit;
      color: var(--accent);
    }

    & b {
      color: var(--accent);
    }

    &.tip {
      --accent: var(--good);
      --mark: "+";
    }

    &.gotcha {
      --accent: var(--crit);
      --mark: "X";
    }

    &.memo {
      --accent: var(--gold);
      --mark: "#";
    }

    &.quest {
      --accent: var(--purple);
      --mark: "?";
    }

    &.info {
      --accent: var(--blue);
      --mark: "i";
    }

    &.warn {
      --accent: var(--warn);
      --mark: "!";
    }
  }

  /* ------------------------------------------------------------------ BADGE */
  .badge {
    --accent: var(--panel-2);
    --accent-d: var(--line-hi);
    display: inline-block;
    font-family: var(--font-mono);
    font-size: var(--fs-label);
    font-weight: var(--fw-bold);
    text-transform: uppercase;
    letter-spacing: var(--ls-chrome);
    /* The badge is the one accent-filled recipe whose DEFAULT fill is dark
       (--accent is --panel-2 above), so light ink is correct — 13.07:1. The moment
       an accent lands on it, or on an ancestor, the fill turns bright and that same
       ink drops to 1.25:1 on gold. So the ink follows the fill: --ink-on-accent
       clears AA on all twelve accents (worst 5.16:1, asserted by check:contrast). */
    color: var(--text);
    background: var(--accent);
    border: var(--bw-2) solid var(--accent-d);
    box-shadow: var(--sh-2);
    padding: var(--pad-tight);
    line-height: var(--lh-tight);

    /* Default is neutral: light --muted text on the dark --panel-2 (never dark
       --ink-on-accent, which sinks into a same-hue dark ground). Only the bright
       colored fills below flip to dark ink. */
    &.clear,
    &.warn,
    &.crit {
      color: var(--ink-on-accent);
    }

    &.todo {
      color: var(--dim);
    }

    &.clear {
      --accent: var(--good);
      --accent-d: var(--good-d);
    }

    &.warn {
      --accent: var(--warn);
      --accent-d: var(--warn-d);
    }

    &.crit {
      --accent: var(--crit);
      --accent-d: var(--crit-d);
    }
  }

  /* Only when the accent is on the badge ITSELF. An ancestor's data-accent does not
     reach it — .badge declares --accent in @layer components, and a declaration on
     the element beats an inherited value — so keying on a wrapper would paint
     near-black ink on the badge's own dark fill (measured 1.26:1). */
  .badge[data-accent] {
    color: var(--ink-on-accent);
  }

  /* The height cap belongs to the OVERLAY, not the list: a palette dropped into a
     page should grow and let the page scroll, or the reader gets a scroller inside
     a scroller with two thumbs to choose from. */
  :is(dialog, .modal) .palette-list {
    max-block-size: min(50vh, 340px);
  }

  /* ------------------------------------------------------------------- CHIP */
  .chip {
    --accent: var(--primary);
    --accent-d: var(--primary-d);
    display: inline-flex;
    align-items: baseline;
    gap: var(--sp-2);
    font-family: var(--font-mono);
    font-size: var(--fs-chip);
    text-transform: uppercase;
    letter-spacing: var(--ls-chrome);
    color: var(--muted);
    background: var(--panel);
    border: var(--bw-2) solid var(--line-hi);
    box-shadow: var(--sh-2);
    padding: var(--pad-snug);
    cursor: pointer;
    transition: transform var(--dur-fast) var(--ease),
      box-shadow var(--dur-fast) var(--ease);

    & .dot {
      inline-size: .5rem;
      block-size: .5rem;
      background: var(--accent);
      border: var(--bw-1) solid var(--accent-d);
    }

    &:active {
      transform: translate(1px, 1px);
      box-shadow: var(--sh-1);
    }

    &.active,
    &[aria-pressed="true"] {
      background: var(--accent);
      color: var(--ink-on-accent);
      border-color: var(--accent-d);
    }
  }

  /* custom elements are inline by default — make the block-level ones fill
     their container so long code scrolls INSIDE (no page-wide overflow). */
  nes-code,
  nes-tabs,
  nes-tree,
  nes-chat-prompt,
  nes-chat-messages,
  nes-editor,
  nes-code-tree,
  nes-mermaid,
  nes-walkthrough,
  nes-zoom,
  nes-annotate,
  nes-compare,
  nes-graph {
    display: block;
  }

  /* icon element: inline, inherits color, sized by font-size (the svg is 1em) */
  nes-icon {
    display: inline-flex;
    align-items: center;
    vertical-align: -0.125em;
  }

  nes-icon>svg {
    display: block;
  }

  /* ---- icon variants: size (icons are 1em, so a class just sets the box), a
     spin for loaders, and .icon-box — a square tile for feature/nav icons. ---- */
  .i-sm {
    font-size: var(--icon-sm);
  }

  .i-md {
    font-size: var(--icon-md);
  }

  .i-lg {
    font-size: var(--icon-lg);
  }

  .i-xl {
    font-size: var(--icon-xl);
  }

  .i-spin>svg,
  nes-icon.i-spin>svg {
    transform-origin: center;
    animation: spinner 1s linear infinite;
    /* shares the single @keyframes spinner (see BUTTON loading) */
  }

  /* square icon tile: soft accent by default, .solid fills, .lg enlarges */
  .icon-box {
    --accent: var(--primary);
    display: inline-grid;
    place-items: center;
    flex: none;
    inline-size: 2.5rem;
    block-size: 2.5rem;
    font-size: var(--icon-md);
    color: var(--accent);
    background: color-mix(in srgb, var(--accent) 15%, var(--panel));
    border: var(--bw-2) solid var(--accent);
    box-shadow: var(--sh-1);
  }

  .icon-box.solid {
    color: var(--ink-on-accent);
    background: var(--accent);
    border-color: var(--accent-d);
  }

  .icon-box.lg {
    inline-size: 3.5rem;
    block-size: 3.5rem;
    font-size: var(--icon-lg);
  }

  /* --------------------------------------------------------------- CODE BLOCK */
  .codeblock {
    max-inline-size: 100%;
    position: relative;
    background: var(--slot);
    border: var(--bw) solid var(--line-hi);
    box-shadow: var(--sh-3);

    & pre {
      margin: 0;
      padding: var(--sp-4);
      overflow-x: auto;
      font-family: var(--font-mono);
      font-size: var(--fs-chip);
      line-height: var(--lh-body);
      color: var(--text);
    }

    & .cp {
      position: absolute;
      inset-block-start: var(--sp-2);
      inset-inline-end: var(--sp-2);
      font-family: var(--font-mono);
      font-size: var(--fs-label);
      text-transform: uppercase;
      letter-spacing: var(--ls-chrome);
      color: var(--muted);
      background: var(--panel-2);
      border: var(--bw-2) solid var(--line-hi);
      box-shadow: var(--sh-1);
      padding: var(--chip-py) var(--chip-px);
      cursor: pointer;

      &:hover {
        color: var(--ink);
        border-color: var(--primary);
      }

      &:active {
        transform: translate(1px, 1px);
      }
    }

    /* syntax tokens — VSCode Dark+ families mapped onto 8-bit brand hues:
       keyword→blue · control/selector→purple · string→amber · number→lime ·
       function→gold · punctuation/at→cyan · comment→dim. */
    & .t-sel {
      color: var(--purple);
    }

    & .t-key {
      color: var(--blue);
    }

    & .t-str {
      color: var(--warn);
    }

    & .t-num {
      color: var(--lime);
    }

    & .t-com {
      color: var(--dim);
      font-style: italic;
    }

    & .t-at {
      color: var(--cyan);
    }

    & .t-fn {
      color: var(--gold);
    }
  }

  /* --------------------------------------------------------- PROGRESS / XP BAR */
  .pbar {
    /* Also the guard: --fill inherits now, so this stops an ancestor's value from
       leaking in and filling a bar nobody set. */
    --fill: 0%;
    /* A <span> is inline, and an inline box ignores inline-size, block-size and
       overflow — so a standalone .pbar measured 2px wide (its own borders) by
       45.1px tall, and its <i> resolved --fill against the PAGE rather than the
       bar. It only ever worked as a flex item, where the parent blockifies it,
       which is how this library's own <nes-hud> uses it and why the published
       example <span class="pbar"><i style="--fill:64%"></i></span> renders a
       sliver when pasted into a plain div. */
    display: block;
    block-size: 1rem;
    background: var(--slot);
    border: var(--bw-2) solid var(--line-hi);
    overflow: hidden;

    &>i {
      display: block;
      block-size: 100%;
      inline-size: var(--fill);
      background: repeating-linear-gradient(90deg,
          var(--primary) 0 6px, var(--primary-d) 6px 8px);
      transition: --fill var(--dur-slow) var(--ease-fill);
    }
  }

  /* ------------------------------------------------------------------- STAT */
  .stat {
    & .n {
      font-family: var(--font-mono);
      font-weight: var(--fw-bold);
      color: var(--accent, var(--primary));
      font-size: var(--fs-h1);
      font-variant-numeric: tabular-nums;
      line-height: var(--lh-tight);
    }

    & .l {
      font-family: var(--font-mono);
      font-size: var(--fs-label);
      text-transform: uppercase;
      letter-spacing: var(--ls-chrome);
      color: var(--muted);
    }
  }

  /* --------------------------------------------------------- QUIZ OPTION (visual) */
  .opt {
    display: flex;
    align-items: center;
    gap: var(--sp-3);
    inline-size: 100%;
    text-align: start;
    color: var(--text);
    background: var(--slot);
    border: var(--bw-2) solid var(--line-hi);
    box-shadow: var(--sh-2);
    padding: var(--sp-3);
    cursor: pointer;
    font-family: var(--font-body);
    font-size: var(--fs-body);
    transition: transform var(--dur-fast) var(--ease);

    & .key {
      flex: none;
      display: grid;
      place-items: center;
      inline-size: 26px;
      block-size: 26px;
      font-family: var(--font-mono);
      font-weight: var(--fw-bold);
      color: var(--muted);
      background: var(--panel-2);
      border: var(--bw-2) solid var(--line-hi);
    }

    &:hover {
      border-color: var(--primary);
    }

    &:active {
      transform: translate(1px, 1px);
      box-shadow: var(--sh-1);
    }

    &.ok {
      border-color: var(--good);
      background: color-mix(in srgb, var(--good) 12%, transparent);

      & .key {
        background: var(--good);
        border-color: var(--good-d);
        color: var(--ink-on-accent);
      }
    }

    &.bad {
      border-color: var(--crit);
      background: color-mix(in srgb, var(--crit) 12%, transparent);

      & .key {
        background: var(--crit);
        border-color: var(--crit-d);
        color: var(--ink-on-accent);
      }
    }

    &:disabled {
      cursor: default;

      &:hover {
        border-color: var(--line-hi);
      }
    }
  }

  /* ===================================================================== */
  /*  CORE WAVE — added components. Same contract: every value from tokens, */
  /*  notch not radius, hard shadow, smooth motion, one accent per block.  */
  /* ===================================================================== */

  /* ------------------------------------------------------------------ AVATAR */
  /* square by design (NES): keeps the hard shadow crisp, no clip-path.        */
  .avatar {
    --sz: 2.5rem;
    --accent: var(--primary);
    display: inline-grid;
    place-items: center;
    overflow: hidden;
    flex: none;
    inline-size: var(--sz);
    block-size: var(--sz);
    font-family: var(--font-mono);
    font-weight: var(--fw-bold);
    font-size: calc(var(--sz) * .38);
    color: var(--ink-on-accent);
    background: var(--accent);
    border: var(--bw-2) solid var(--line);
    box-shadow: var(--sh-2);

    & img {
      inline-size: 100%;
      block-size: 100%;
      object-fit: cover;
    }

    &.sm {
      --sz: 1.75rem;
    }

    &.lg {
      --sz: 3.5rem;
    }
  }

  .avatar-group {
    display: inline-flex;
  }

  .avatar-group>.avatar {
    margin-inline-start: calc(var(--sp-2) * -1);
    outline: var(--ring-w) solid var(--panel);
  }

  .avatar-group>.avatar:first-child {
    margin-inline-start: 0;
  }

  /* --------------------------------------------------------------------- KBD */
  .kbd {
    display: inline-block;
    font-family: var(--font-mono);
    font-weight: var(--fw-bold);
    font-size: var(--fs-label);
    color: var(--ink);
    line-height: var(--lh-none);
    background: var(--panel-2);
    border: var(--bw-2) solid var(--line);
    box-shadow: var(--sh-1);
    padding: var(--chip-py) var(--chip-px);
  }

  /* --------------------------------------------------------------- SEPARATOR */
  .separator {
    border: 0;
    block-size: var(--bw-2);
    inline-size: 100%;
    background: var(--line-hi);
    margin-block: var(--sp-4);

    &.dashed {
      background: repeating-linear-gradient(90deg,
          var(--line-hi) 0 6px, transparent 6px 12px);
    }

    &[data-orientation="vertical"] {
      inline-size: var(--bw-2);
      block-size: auto;
      align-self: stretch;
      margin: 0 var(--sp-3);
      background: var(--line-hi);
    }
  }

  /* ------------------------------------------------------------------- FIELD */
  /* label + control + hint/error wrapper. .err flips the semantics to crit.   */
  .field {
    display: flex;
    flex-direction: column;
    gap: var(--sp-2);

    &>.label {
      font-family: var(--font-mono);
      font-size: var(--fs-label);
      text-transform: uppercase;
      letter-spacing: var(--ls-chrome);
      color: var(--muted);
    }

    &>.hint {
      font-size: var(--fs-chip);
      color: var(--dim);
    }

    & .req {
      color: var(--crit);
    }

    &.err {

      &>.label,
      &>.hint {
        color: var(--crit);
      }
    }
  }

  /* -------------------------------------------------------- INPUT / TEXTAREA */
  .input,
  .textarea {
    inline-size: 100%;
    font-family: var(--font-body);
    font-size: var(--fs-body);
    color: var(--ink);
    background: var(--slot);
    border: var(--bw-2) solid var(--line-hi);
    block-size: var(--ctrl-h);
    padding-inline: var(--ctrl-pad);
    transition: border-color var(--dur-fast);

    &::placeholder {
      color: var(--dim);
    }

    &:hover {
      border-color: var(--dim);
    }

    &:disabled {
      color: var(--dim);
      background: var(--panel);
      cursor: not-allowed;
    }

    &[aria-invalid="true"] {
      border-color: var(--crit);
    }
  }

  .textarea {
    block-size: auto;
    min-block-size: 5.5rem;
    padding-block: var(--sp-3);
    line-height: var(--lh-body);
    resize: vertical;
  }

  /* ------------------------------------------------------------------ SELECT */
  .select {
    inline-size: 100%;
    font-family: var(--font-body);
    font-size: var(--fs-body);
    color: var(--ink);
    background: var(--slot);
    cursor: pointer;
    border: var(--bw-2) solid var(--line-hi);
    block-size: var(--ctrl-h);
    padding-inline: var(--ctrl-pad);
    padding-inline-end: calc(var(--ctrl-pad) + 1.25rem);
    appearance: none;
    -webkit-appearance: none;
    background-repeat: no-repeat;
    background-position: right var(--ctrl-pad) center;
    background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='10' height='7' viewBox='0 0 10 7'%3E%3Cpath d='M1 1l4 4 4-4' stroke='%2356d364' stroke-width='2' fill='none'/%3E%3C/svg%3E");
    transition: border-color var(--dur-fast);

    &:hover {
      border-color: var(--dim);
    }

    &:disabled {
      color: var(--dim);
      background-color: var(--panel);
      cursor: not-allowed;
    }
  }

  /* ---------------------------------------------------------- CHECKBOX / RADIO */
  .checkbox,
  .radio {
    --accent: var(--primary);
    --accent-d: var(--primary-d);
    appearance: none;
    -webkit-appearance: none;
    flex: none;
    inline-size: 18px;
    block-size: 18px;
    display: inline-grid;
    place-items: center;
    cursor: pointer;
    background: var(--slot);
    border: var(--bw-2) solid var(--line-hi);
    box-shadow: var(--sh-1);
    transition: background var(--dur-fast);

    &:checked {
      background: var(--accent);
      border-color: var(--accent-d);
    }

    &:disabled {
      background: var(--panel);
      cursor: not-allowed;
    }
  }

  .checkbox:checked::after {
    content: "";
    inline-size: 6px;
    block-size: 10px;
    margin-block-start: -2px; /* off-grid: optically centres the tick */
    border: solid var(--ink-on-accent);
    border-width: 0 var(--bw-2) var(--bw-2) 0;
    transform: rotate(45deg);
  }

  .radio:checked::after {
    content: "";
    inline-size: .5rem;
    block-size: .5rem;
    background: var(--ink-on-accent);
  }

  /* label pairing */
  .check {
    display: inline-flex;
    align-items: center;
    gap: var(--sp-2);
    cursor: pointer;
    font-size: var(--fs-body);
    color: var(--text);
  }

  /* ------------------------------------------------------------------ SWITCH */
  .switch {
    --accent: var(--good);
    /* the three numbers the whole switch derives from, so the thumb can never
       drift out of centre when one of them is retuned */
    --sw-w: var(--ctrl-h-lg);
    --sw-h: 22px;
    --sw-thumb: 16px;
    appearance: none;
    -webkit-appearance: none;
    position: relative;
    flex: none;
    cursor: pointer;
    inline-size: var(--sw-w);
    block-size: var(--sw-h);
    background: var(--slot);
    border: var(--bw-2) solid var(--line-hi);
    transition: background var(--dur-fast) var(--ease),
      border-color var(--dur-fast);

    &::after {
      content: "";
      position: absolute;
      inset-block: var(--bw-1);
      inset-inline-start: var(--bw-1);
      inline-size: var(--sw-thumb);
      background: var(--line-hi);
      border: var(--bw-1) solid var(--line);
      transition: transform var(--dur-fast) var(--ease), background var(--dur-fast);
    }

    &:checked {
      background: color-mix(in srgb, var(--accent) 28%, var(--slot));
      border-color: var(--accent);
    }

    &:checked::after {
      /* track inner width − thumb − the inset on both sides: the thumb lands as far
         from the right edge as it started from the left, exactly. */
      transform: translateX(calc(var(--sw-w) - 2 * var(--bw-2) - var(--sw-thumb) - 2 * var(--bw-1)));
      background: var(--accent);
    }

    &:disabled {
      cursor: not-allowed;
      opacity: var(--op-disabled);
    }
  }

  /* ---------------------------------------------------------------- SKELETON */
  .skeleton {
    display: block;
    block-size: 1rem;
    inline-size: 100%;
    background: var(--panel-2);
    background-image: linear-gradient(90deg, transparent 0,
        color-mix(in srgb, var(--line-hi) 45%, transparent) 50%, transparent 100%);
    background-size: 220% 100%;
    animation: skeleton 1.4s linear infinite;
  }

  @keyframes skeleton {
    to {
      background-position: -220% 0;
    }
  }

  /* ----------------------------------------------------------------- TOOLTIP */
  /* pure CSS: any element with [data-tip] shows a mono label on hover/focus.   */
  [data-tip] {
    position: relative;
  }

  [data-tip]:hover::after,
  [data-tip]:focus-visible::after {
    content: attr(data-tip);
    position: absolute;
    z-index: var(--z-overlay);
    inset-block-end: calc(100% + var(--sp-2));
    inset-inline-start: 50%;
    transform: translateX(-50%);
    pointer-events: none;
    white-space: nowrap;
    font-family: var(--font-mono);
    font-size: var(--fs-label);
    text-transform: uppercase;
    letter-spacing: var(--ls-chrome);
    color: var(--ink);
    background: var(--panel-2);
    border: var(--bw-2) solid var(--line-hi);
    box-shadow: var(--sh-2);
    padding: var(--chip-py) var(--chip-px);
  }

  /* -------------------------------------------------------------- BREADCRUMB */
  .breadcrumb {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: var(--sp-2);
    font-family: var(--font-mono);
    font-size: var(--fs-label);
    text-transform: uppercase;
    letter-spacing: var(--ls-chrome);
    color: var(--muted);

    & li {
      display: flex;
      align-items: center;
      gap: var(--sp-2);
    }

    & li+li::before {
      content: "\25B8";
      color: var(--dim);
    }

    & a {
      color: var(--muted);
    }

    & a:hover {
      color: var(--primary);
    }

    & [aria-current] {
      color: var(--ink);
    }
  }

  /* -------------------------------------------------------------- PAGINATION */
  .pagination {
    display: flex;
    flex-wrap: wrap;
    gap: var(--sp-2);
    align-items: center;
  }

  .pg {
    min-inline-size: 2rem;
    block-size: 2rem;
    padding-inline: var(--sp-2);
    display: inline-grid;
    place-items: center;
    font-family: var(--font-mono);
    font-size: var(--fs-chip);
    color: var(--muted);
    background: var(--panel);
    border: var(--bw-2) solid var(--line-hi);
    box-shadow: var(--sh-1);
    cursor: pointer;
    transition: transform var(--dur-fast) var(--ease);

    &:hover {
      border-color: var(--primary);
      color: var(--ink);
    }

    &:active {
      transform: translate(1px, 1px);
      box-shadow: none;
    }

    &[aria-current="page"] {
      background: var(--primary);
      color: var(--ink-on-accent);
      border-color: var(--primary-d);
      cursor: default;
    }

    &:disabled {
      color: var(--dim);
      cursor: not-allowed;

      &:hover {
        border-color: var(--line-hi);
      }
    }
  }

  /* -------------------------------------------------------------------- TABS */
  .tablist {
    display: flex;
    flex-wrap: wrap;
    gap: var(--sp-1);
    border-block-end: var(--bw-2) solid var(--line-hi);
  }

  .tab {
    font-family: var(--font-mono);
    font-size: var(--fs-chip);
    text-transform: uppercase;
    letter-spacing: var(--ls-chrome);
    color: var(--muted);
    background: none;
    border: 0;
    cursor: pointer;
    padding: var(--pad-box);
    border-block-end: var(--accent-bar) solid transparent;
    margin-block-end: calc(var(--bw-2) * -1);
    transition: color var(--dur-fast);

    &:hover {
      color: var(--ink);
    }

    &[aria-selected="true"] {
      color: var(--primary);
      border-block-end-color: var(--primary);
    }
  }

  .tabpanel {
    padding-block-start: var(--sp-4);
  }

  .tabpanel[hidden] {
    display: none;
  }

  /* -------------------------------------------------------------- DROPDOWN */
  /* native <details class="dropdown"> — zero JS, closes on Esc for free.      */
  .dropdown {
    position: relative;
    display: inline-block;
  }

  .dropdown>summary {
    list-style: none;
    cursor: pointer;
  }

  .dropdown>summary::-webkit-details-marker {
    display: none;
  }

  .menu {
    position: absolute;
    z-index: var(--z-overlay);
    inset-block-start: calc(100% + var(--sp-2));
    inset-inline-start: 0;
    min-inline-size: 12rem;
    display: flex;
    flex-direction: column;
    gap: var(--sp-hair);
    background: var(--panel);
    border: var(--bw) solid var(--line-hi);
    box-shadow: var(--sh-4);
    padding: var(--sp-2);
  }

  .menu.end {
    inset-inline-start: auto;
    inset-inline-end: 0;
  }

  .menuitem {
    display: flex;
    align-items: center;
    gap: var(--sp-2);
    text-align: start;
    font-family: var(--font-body);
    font-size: var(--fs-body);
    color: var(--text);
    background: none;
    border: 0;
    padding: var(--pad-snug);
    cursor: pointer;

    &:hover {
      background: var(--panel-2);
      color: var(--ink);
    }

    &.crit {
      color: var(--crit);

      &:hover {
        background: color-mix(in srgb, var(--crit) 15%, transparent);
      }
    }
  }

  /* ------------------------------------------------------------------- MODAL */
  /* native <dialog class="modal"> — open with el.showModal().                 */
  .modal {
    --accent: var(--primary);
    inline-size: min(92vw, 32rem);
    color: var(--text);
    background: var(--panel);
    border: var(--bw) solid var(--line-hi);
    border-inline-start: var(--accent-bar) solid var(--accent);
    box-shadow: var(--sh-5);
    padding: var(--sp-5);

    &::backdrop {
      background: color-mix(in srgb, var(--bg) 72%, transparent);
    }

    & .head {
      display: flex;
      align-items: center;
      gap: var(--sp-3);
      margin-block-end: var(--sp-3);
    }

    & .title {
      font-family: var(--font-mono);
      font-size: var(--fs-h2);
      font-weight: var(--fw-bold);
      color: var(--ink);
      flex: 1;
    }

    & .foot {
      display: flex;
      justify-content: flex-end;
      gap: var(--sp-3);
      margin-block-start: var(--sp-5);
    }
  }

  /* ------------------------------------------------------------------- TABLE */
  .table-wrap {
    overflow-x: auto;
    border: var(--bw) solid var(--line-hi);
    box-shadow: var(--sh-3);
  }

  .table {
    inline-size: 100%;
    border-collapse: collapse;
    font-size: var(--fs-body);
    color: var(--text);
    background: var(--panel);

    & th {
      font-family: var(--font-mono);
      font-size: var(--fs-label);
      text-transform: uppercase;
      letter-spacing: var(--ls-chrome);
      color: var(--muted);
      text-align: start;
      vertical-align: top;
      padding: var(--sp-3);
      background: var(--panel-2);
      border-block-end: var(--bw-2) solid var(--line-hi);
      white-space: nowrap;
    }

    /* A data grid is read ACROSS. The browser's default vertical-align: middle is
       right for a <td> in a prose document and wrong here: the moment one cell in a
       row wraps, every short cell floats to the middle of its own height and no two
       values share a top edge. Measured at 390px on a four-column row whose last
       cell wrapped to 164px, the first-line tops spread 57.8px — the row reads as
       four unrelated fragments instead of one record. Narrow is the common case on
       a phone, so this is the default, not a variant. */
    & td {
      vertical-align: top;
      padding: var(--sp-3);
      border-block-end: var(--bw-2) solid color-mix(in srgb, var(--line-hi) 45%, transparent);
      font-variant-numeric: tabular-nums;
    }

    & tbody tr:last-child td {
      border-block-end: 0;
    }

    & tbody tr:hover {
      background: color-mix(in srgb, var(--line-hi) 12%, transparent);
    }
  }

  /* ------------------------------------------------------------------- TOAST */
  .toast-host {
    position: fixed;
    z-index: var(--z-overlay);
    inset-block-end: var(--sp-5);
    inset-inline: 0;
    margin-inline: auto;
    inline-size: max-content;
    max-inline-size: 92vw;
    display: flex;
    flex-direction: column;
    gap: var(--sp-2);
    pointer-events: none;
  }

  .toast {
    --accent: var(--primary);
    pointer-events: auto;
    display: flex;
    align-items: center;
    gap: var(--sp-3);
    font-size: var(--fs-body);
    color: var(--text);
    background: var(--panel);
    border: var(--bw-2) solid var(--line-hi);
    border-inline-start: var(--accent-bar) solid var(--accent);
    box-shadow: var(--sh-4);
    padding: var(--pad-box);
    animation: toast-in var(--dur-mid) var(--ease);
    /* a swipe writes this; nothing else touches transform */
    touch-action: pan-y;

    & b {
      color: var(--accent);
    }

    /* the message column, so a title can sit above the body */
    & .toast-msg {
      flex: 1;
      min-inline-size: 0;
    }

    & .toast-title {
      display: block;
      font-family: var(--font-mono);
      font-size: var(--fs-label);
      font-weight: var(--fw-bold);
      text-transform: uppercase;
      letter-spacing: var(--ls-chrome);
      color: var(--accent);
    }

    /* action + dismiss sit at the end, and never shrink */
    & .toast-act {
      flex: none;
    }

    & .toast-x {
      flex: none;
      inline-size: var(--pip);
      block-size: var(--pip);
      display: grid;
      place-items: center;
      padding: 0;
      font-family: var(--font-mono);
      font-size: var(--fs-chip);
      line-height: var(--lh-none);
      color: var(--dim);
      background: none;
      border: 0;
      cursor: pointer;

      &:hover {
        color: var(--ink);
      }
    }

    /* paused by a pointer or by focus: the reader gets to finish reading
       (WCAG 2.2.1). The bar is the only motion, so it is also the only thing to
       stop — the toast itself never moves while paused. */
    &[data-paused] .toast-bar {
      animation-play-state: paused;
    }

    /* remaining-time bar: --toast-ms comes from the timeout the caller gave */
    & .toast-bar {
      position: absolute;
      inset-block-end: 0;
      inset-inline-start: 0;
      block-size: var(--sp-hair);
      background: var(--accent);
      animation: toast-time var(--toast-ms, 3200ms) linear forwards;
    }

    &:has(.toast-bar) {
      position: relative;
      overflow: hidden;
    }
  }

  /* the dismiss × is a 12px glyph — on a thumb it needs a real box (--tap) */
  @media (pointer: coarse) {
    .toast-x {
      position: relative;
    }

    .toast-x::after {
      content: "";
      position: absolute;
      inset: calc((var(--tap) - var(--pip)) / -2);
    }
  }

  @keyframes toast-time {
    from {
      inline-size: 100%;
    }

    to {
      inline-size: 0%;
    }
  }

  @keyframes toast-in {
    from {
      opacity: 0;
      transform: translateY(10px);
    }
  }

  /* ===================================================================== */
  /*  WAVE 2 — extended set. Same contract: every value from tokens, notch  */
  /*  only on buttons, hard shadow, smooth motion, one accent per block.   */
  /* ===================================================================== */

  /* ----------------------------------------------------------------- SPINNER */
  /* square by design: a hard-edged block whose lit top/right edges spin
     smoothly (linear) — a loader with no curve. Accent-ready, three sizes.    */
  .spinner {
    --sz: 1.5rem;
    --accent: var(--primary);
    display: inline-block;
    flex: none;
    inline-size: var(--sz);
    block-size: var(--sz);
    background: var(--slot);
    border: var(--bw-2) solid var(--line-hi);
    border-block-start-color: var(--accent);
    border-inline-end-color: var(--accent);
    animation: spinner .7s linear infinite;

    &.sm {
      --sz: 1rem;
    }

    &.lg {
      --sz: 2.5rem;
      border-width: var(--bw);
    }
  }

  @keyframes spinner {
    to {
      transform: rotate(360deg);
    }
  }

  /* ------------------------------------------------------------------- RANGE */
  /* styled <input type="range">. Recessed track + square accent thumb with the
     hard shadow. Accent-ready; focus comes from the global :focus-visible.
     (Thumb centering needs physical margin-top — a known vendor hack.)         */
  .range {
    --accent: var(--primary);
    --accent-d: var(--primary-d);
    --track-h: .5rem;
    --thumb: 1.15rem;
    inline-size: 100%;
    block-size: var(--thumb);
    appearance: none;
    -webkit-appearance: none;
    background: none;
    cursor: pointer;

    &:disabled {
      cursor: not-allowed;
      opacity: var(--op-disabled);
    }

    &::-webkit-slider-runnable-track {
      block-size: var(--track-h);
      background: var(--slot);
      border: var(--bw-2) solid var(--line-hi);
    }

    &::-webkit-slider-thumb {
      -webkit-appearance: none;
      inline-size: var(--thumb);
      block-size: var(--thumb);
      margin-top: calc((var(--track-h) - var(--thumb)) / 2 - var(--bw-2));
      background: var(--accent);
      border: var(--bw-2) solid var(--accent-d);
      box-shadow: var(--sh-2);
    }

    &::-moz-range-track {
      block-size: var(--track-h);
      background: var(--slot);
      border: var(--bw-2) solid var(--line-hi);
    }

    &::-moz-range-thumb {
      inline-size: var(--thumb);
      block-size: var(--thumb);
      background: var(--accent);
      border: var(--bw-2) solid var(--accent-d);
      border-radius: 0;
      box-shadow: var(--sh-2);
    }
  }

  /* ------------------------------------------------------- SEGMENTED CONTROL */
  /* a row of connected buttons; the pressed one fills with the accent. Put
     role="group" on the wrapper and aria-pressed="true" on the active button. */
  .segment {
    --accent: var(--primary);
    --accent-d: var(--primary-d);
    display: inline-flex;
    background: var(--panel);
    border: var(--bw-2) solid var(--line-hi);
    box-shadow: var(--sh-2);

    &>button {
      font-family: var(--font-mono);
      font-size: var(--fs-chip);
      text-transform: uppercase;
      letter-spacing: var(--ls-chrome);
      color: var(--muted);
      background: none;
      border: 0;
      border-inline-start: var(--bw-2) solid var(--line-hi);
      padding: var(--sp-2) var(--sp-4);
      cursor: pointer;
      transition: color var(--dur-fast), background var(--dur-fast);
    }

    &>button:first-child {
      border-inline-start: 0;
    }

    &>button:hover {
      color: var(--ink);
    }

    &>button[aria-pressed="true"] {
      color: var(--ink-on-accent);
      background: var(--accent);
    }

    &>button:disabled {
      color: var(--dim);
      cursor: not-allowed;
    }
  }

  /* ------------------------------------------------------------------- STEPS */
  /* stage progression: <ol class="steps"> of <li>. Mark cleared stages .done
     and the active one [aria-current] — reads as NES "STAGE 1 · 2 · 3".        */
  .steps {
    --accent: var(--primary);
    --accent-d: var(--primary-d);
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: var(--sp-3);
    counter-reset: step;

    &>li {
      counter-increment: step;
      display: inline-flex;
      align-items: center;
      gap: var(--sp-2);
      font-family: var(--font-mono);
      font-size: var(--fs-label);
      text-transform: uppercase;
      letter-spacing: var(--ls-chrome);
      color: var(--dim);
    }

    &>li::before {
      content: counter(step);
      flex: none;
      display: grid;
      place-items: center;
      inline-size: 26px;
      block-size: 26px;
      font-weight: var(--fw-bold);
      color: var(--muted);
      background: var(--slot);
      border: var(--bw-2) solid var(--line-hi);
      box-shadow: var(--sh-1);
    }

    &>li.done {
      color: var(--muted);
    }

    &>li.done::before {
      content: "\2713";
      color: var(--ink-on-accent);
      background: var(--accent);
      border-color: var(--accent-d);
    }

    &>li[aria-current] {
      color: var(--ink);
    }

    &>li[aria-current]::before {
      color: var(--ink-on-accent);
      background: var(--accent);
      border-color: var(--accent-d);
    }
  }

  /* ------------------------------------------------------------------- METER */
  /* discrete segmented gauge (health / energy). A flex row of .cell blocks;
     add .on to filled cells. Accent-ready — good/warn/crit read as HP states. */
  .meter {
    --accent: var(--good);
    --accent-d: var(--good-d);
    display: inline-flex;
    gap: var(--sp-hair);

    &>.cell {
      inline-size: 14px;
      block-size: 18px;
      background: var(--slot);
      border: var(--bw-2) solid var(--line-hi);
    }

    &>.cell.on {
      background: var(--accent);
      border-color: var(--accent-d);
    }
  }

  /* ------------------------------------------------------------- EMPTY STATE */
  /* "no data" / "game over" panel. Square, dashed edge, centered mono copy.    */
  .empty {
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: var(--sp-3);
    text-align: center;
    color: var(--muted);
    background: var(--slot);
    border: var(--bw-2) dashed var(--line-hi);
    padding: var(--sp-6) var(--sp-5);

    & .icon {
      font-size: var(--fs-h1);
      line-height: var(--lh-none);
    }

    & .title {
      font-family: var(--font-mono);
      font-size: var(--fs-h3);
      font-weight: var(--fw-bold);
      text-transform: uppercase;
      letter-spacing: var(--ls-chrome);
      color: var(--ink);
    }

    & p {
      font-size: var(--fs-body);
      color: var(--dim);
      max-inline-size: 42ch;
    }
  }

  /* ------------------------------------------------------------------ RATING */
  /* read-only score: a row of star glyphs filled up to .on. Gold by default;
     give the wrapper an aria-label ("4 of 5") so it isn't colour-only.         */
  .rating {
    /* gold stars by convention (reward), not the green primary */
    --accent: var(--gold);
    display: inline-flex;
    gap: var(--sp-1);
    font-size: var(--fs-h3);
    line-height: var(--lh-none);
    color: var(--line-hi);

    &>.s.on {
      color: var(--accent);
    }

    /* interactive picker (<nes-rating>): stars become bare buttons */
    &>button.s {
      padding: 0;
      background: none;
      border: 0;
      color: inherit;
      font: inherit;
      line-height: var(--lh-none);
      cursor: pointer;
      transition: color var(--dur-fast), transform var(--dur-fast) var(--ease);
    }

    &>button.s:hover {
      transform: translateY(-1px);
    }
  }

  /* larger star row for prominent inputs: <nes-rating size="lg"> or .rating.lg */
  .rating.lg {
    font-size: var(--fs-h1);
  }

  /* ------------------------------------------------------------------ DRAWER */
  /* native <dialog class="drawer"> that slides in from the inline-end edge.
     Open with el.showModal(); add .start to slide from the inline-start.       */
  .drawer {
    --accent: var(--primary);
    position: fixed;
    inset-block: 0;
    /* An open modal <dialog> gets `inset-inline: 0` from the UA sheet, which
       over-constrains the box: with both sides set the start edge wins, so this
       drawer opened on the LEFT (measured x=1 in a 1200px viewport) and .start had
       nothing left to do. Releasing the start edge is what makes `end` mean end. */
    inset-inline-start: auto;
    inset-inline-end: 0;
    margin: 0;
    block-size: 100dvh;
    max-block-size: 100dvh;
    inline-size: min(90vw, 22rem);
    color: var(--text);
    background: var(--panel);
    border: 0;
    border-inline-start: var(--bw) solid var(--line-hi);
    box-shadow: var(--sh-5);
    padding: var(--sp-5);

    &::backdrop {
      background: color-mix(in srgb, var(--bg) 72%, transparent);
    }

    &[open] {
      animation: drawer-in var(--dur-mid) var(--ease);
    }

    &.start {
      inset-inline-end: auto;
      inset-inline-start: 0;
      border-inline-start: 0;
      border-inline-end: var(--bw) solid var(--line-hi);
    }

    &.start[open] {
      animation: drawer-in-start var(--dur-mid) var(--ease);
    }

    & .head {
      display: flex;
      align-items: center;
      gap: var(--sp-3);
      margin-block-end: var(--sp-4);
    }

    & .title {
      flex: 1;
      font-family: var(--font-mono);
      font-size: var(--fs-h2);
      font-weight: var(--fw-bold);
      color: var(--ink);
    }
  }

  @keyframes drawer-in {
    from {
      transform: translateX(100%);
    }
  }

  @keyframes drawer-in-start {
    from {
      transform: translateX(-100%);
    }
  }

  /* ===================================================================== */
  /*  WAVE 3 — content & app-shell set. Same contract: tokens, square, hard  */
  /*  shadow, smooth motion, accent-ready.                                   */
  /* ===================================================================== */

  /* ------------------------------------------------------------------- PROSE */
  /* drop rendered markdown/CMS HTML in — restores semantic rhythm + list marks
     (base.css strips them) with brand styling. Square bullets, accent links.  */
  .prose {
    --accent: var(--primary);
    color: var(--text);
    font-size: var(--fs-body);
    line-height: var(--lh-body);

    /* The measure belongs to the TEXT, not the container. Capping .prose itself also
       capped every child that isn't text: a paragraph rewraps when it runs out of
       room, but a table, a <pre>, a diagram or an image cannot — they get crushed or
       scaled down to a width they never needed to fit. So each child carries the
       measure, and the constructs whose content *is* width opt out. Measured by
       default is the safe direction: something nobody has considered yet reads
       correctly instead of sprawling. Retune with --prose-measure. */
    &>* {
      margin-block: 0;
      max-inline-size: var(--prose-measure);
    }

    &>:is(.table-wrap,
      table,
      pre,
      .codeblock,
      .code-preview,
      .diff,
      .terminal,
      .card-group,
      hr,
      nes-code,
      nes-mermaid,
      nes-graph,
      nes-zoom,
      nes-walkthrough,
      nes-compare,
      nes-annotate,
      nes-preview,
      nes-diff,
      nes-logs,
      nes-code-tree,
      nes-split) {
      max-inline-size: none;
    }

    /* Media is a THIRD case, not a member of the list above. Everything on that
       list has a safe way to be too wide — a <pre> and a .table-wrap scroll inside
       themselves, nes-zoom pans, nes-code scrolls — so `none` costs them nothing.
       An <img> has no such escape: `none` hands it its intrinsic width and it walks
       straight out of the page (a 1400px image in a 390px card measured
       scrollWidth 1422 vs clientWidth 390 — sideways scroll on a phone, from a
       stylesheet-only page). Two different caps were being collapsed into one:
       --prose-measure is a READING limit media should escape, 100% is a CONTAINER
       limit nothing may. It gets its own rule rather than a later override because
       :is() takes the specificity of its most specific argument — .table-wrap makes
       the list (0,2,0), so a (0,1,1) media rule after it would lose and read as a
       fix that does nothing. block-size keeps the aspect ratio once width is capped. */
    &>:is(img, svg, video) {
      max-inline-size: 100%;
      block-size: auto;
    }

    &>*+* {
      margin-block-start: var(--sp-4);
    }

    &>:is(h2, h3) {
      margin-block-start: var(--sp-6);
    }

    /* :not(.cite) — a citation marker IS an <a>, and `.prose a` (0,1,1) outranks
       `.cite` (0,1,0), so prose was repainting the library's own citation chip. */
    & a:not(.cite) {
      color: var(--accent);
      text-decoration: underline;
      text-underline-offset: 3px;
      text-decoration-thickness: var(--bw-2);
    }

    & a:hover {
      color: var(--ink);
    }

    & ul,
    & ol {
      padding-inline-start: var(--sp-5);
    }

    & ul {
      list-style: square;
    }

    & ol {
      list-style: decimal;
    }

    & li {
      margin-block-start: var(--sp-2);
    }

    & li::marker {
      color: var(--accent);
    }

    & blockquote {
      border-inline-start: var(--accent-bar) solid var(--accent);
      padding-inline-start: var(--sp-4);
      color: var(--muted);
      font-style: italic;
    }

    & hr {
      border: 0;
      block-size: var(--bw-2);
      background: var(--line-hi);
      margin-block: var(--sp-5);
    }

    & img {
      border: var(--bw-2) solid var(--line-hi);
      box-shadow: var(--sh-3);
    }
  }

  /* -------------------------------------------------------- DESCRIPTION LIST */
  /* key → value rows for detail panels / settings summaries. <dl>/<dt>/<dd>.   */
  .datalist {
    display: grid;
    grid-template-columns: auto 1fr;
    /* the label is 9px mono and the value 13.5px body, so they only line up if BOTH
       share a baseline group — `align-self` on the dt alone is a group of one and
       degrades to `start`, which is what made a dd containing inline <code> sit
       visibly lower than its key. Same shape as .source, which does this already. */
    align-items: baseline;
    gap: var(--sp-2) var(--sp-4);
    font-size: var(--fs-body);

    &>dt {
      font-family: var(--font-mono);
      font-size: var(--fs-label);
      text-transform: uppercase;
      letter-spacing: var(--ls-chrome);
      color: var(--muted);
    }

    &>dd {
      margin: 0;
      color: var(--ink);
      font-variant-numeric: tabular-nums;
    }
  }

  @container (max-width:320px) {
    .datalist {
      grid-template-columns: 1fr;
      gap: var(--sp-1) 0;
    }
  }

  /* ---------------------------------------------------------------- TIMELINE */
  /* vertical events on a connector line with square markers. <ol>/<li>.        */
  .timeline {
    --accent: var(--primary);
    --accent-d: var(--primary-d);
    display: flex;
    flex-direction: column;

    &>li {
      position: relative;
      margin-inline-start: var(--sp-1);
      padding-inline-start: var(--sp-5);
      padding-block-end: var(--sp-5);
      border-inline-start: var(--bw-2) solid var(--line-hi);
    }

    &>li:last-child {
      border-inline-start-color: transparent;
      padding-block-end: 0;
    }

    &>li::before {
      content: "";
      position: absolute;
      inset-block-start: 0;
      inset-inline-start: calc((var(--dot) + var(--bw-2)) / -2);
      inline-size: var(--dot);
      block-size: var(--dot);
      background: var(--accent);
      border: var(--bw-2) solid var(--accent-d);
      box-shadow: var(--sh-1);
    }

    & .time {
      font-family: var(--font-mono);
      font-size: var(--fs-label);
      text-transform: uppercase;
      letter-spacing: var(--ls-chrome);
      color: var(--muted);
    }

    & .title {
      color: var(--ink);
      font-weight: var(--fw-bold);
    }
  }

  /* ----------------------------------------------------------- NUMBER STEPPER */
  /* − [n] + numeric control. Native <input type=number>; wire the buttons in JS. */
  .stepper {
    display: inline-flex;
    align-items: stretch;
    background: var(--slot);
    border: var(--bw-2) solid var(--line-hi);
    box-shadow: var(--sh-2);

    &>button {
      font-family: var(--font-mono);
      font-weight: var(--fw-bold);
      font-size: var(--fs-body);
      color: var(--muted);
      background: var(--panel-2);
      border: 0;
      inline-size: 2.25rem;
      cursor: pointer;
      transition: color var(--dur-fast), background var(--dur-fast);
    }

    &>button:hover {
      color: var(--ink);
    }

    &>button:first-child {
      border-inline-end: var(--bw-2) solid var(--line-hi);
    }

    &>button:last-child {
      border-inline-start: var(--bw-2) solid var(--line-hi);
    }

    &>button:disabled {
      color: var(--dim);
      cursor: not-allowed;
    }

    &>input {
      inline-size: 3.5rem;
      text-align: center;
      font-family: var(--font-mono);
      font-weight: var(--fw-bold);
      color: var(--ink);
      background: none;
      border: 0;
      padding-block: var(--sp-2);
      appearance: textfield;
      -moz-appearance: textfield;
    }

    &>input::-webkit-outer-spin-button,
    &>input::-webkit-inner-spin-button {
      -webkit-appearance: none;
      margin: 0;
    }
  }

  /* -------------------------------------------------------- OPTION SWITCHER */
  /* <nes-switcher>: cycle a small option set with ◀ / ▶ (arcade settings row).
     Arrow buttons flank a centered label; honours the --ctrl-h size scale and
     one accent (gold by default — the "choice / highlight" cue).               */
  .switcher {
    --accent: var(--gold);
    --accent-d: var(--gold-d);
    display: inline-flex;
    align-items: stretch;
    min-block-size: var(--ctrl-h);
    background: var(--slot);
    border: var(--bw-2) solid var(--line-hi);
    box-shadow: var(--sh-2);
    font-family: var(--font-mono);

    &>button {
      flex: none;
      inline-size: var(--ctrl-h);
      display: grid;
      place-items: center;
      color: var(--accent);
      background: var(--panel-2);
      border: 0;
      font-size: var(--ctrl-fs);
      line-height: var(--lh-none);
      cursor: pointer;
      transition: color var(--dur-fast), background var(--dur-fast),
        transform var(--dur-fast) var(--ease);
    }

    &>button:first-child {
      border-inline-end: var(--bw-2) solid var(--line-hi);
    }

    &>button:last-child {
      border-inline-start: var(--bw-2) solid var(--line-hi);
    }

    &>button:hover {
      color: var(--ink);
      background: color-mix(in srgb, var(--accent) 22%, var(--panel-2));
    }

    &>button:active {
      transform: translate(1px, 1px);
    }

    &>button:disabled {
      color: var(--line-hi);
      background: var(--panel-2);
      cursor: not-allowed;
    }

    &>.switcher-label {
      flex: 1;
      display: grid;
      place-items: center;
      min-inline-size: 6rem;
      padding-inline: var(--ctrl-pad);
      color: var(--ink);
      font-size: var(--ctrl-fs);
      text-transform: uppercase;
      letter-spacing: var(--ls-chrome);
      white-space: nowrap;
    }
  }

  /* ------------------------------------------------------------------ BANNER */
  /* full-width page-level notice (distinct from the inline .callout box).       */
  .banner {
    --accent: var(--primary);
    display: flex;
    align-items: center;
    gap: var(--sp-3);
    inline-size: 100%;
    font-size: var(--fs-body);
    color: var(--ink);
    background: color-mix(in srgb, var(--accent) 16%, var(--panel));
    border-block-end: var(--bw-2) solid var(--accent);
    padding: var(--pad-box);

    & b {
      color: var(--accent);
    }

    & .close {
      margin-inline-start: auto;
      flex: none;
      font-family: var(--font-mono);
      color: var(--muted);
      background: none;
      border: 0;
      cursor: pointer;
    }

    & .close:hover {
      color: var(--ink);
    }
  }

  /* ---------------------------------------------------------------- NAV LIST */
  /* vertical sidebar nav for app shells. Mark the current item [aria-current].  */
  .navlist {
    --accent: var(--primary);
    display: flex;
    flex-direction: column;
    font-family: var(--font-mono);
    font-size: var(--fs-chip);

    & .lab {
      padding: var(--pad-snug);
      margin-block-start: var(--sp-3);
      font-size: var(--fs-label);
      text-transform: uppercase;
      letter-spacing: var(--ls-chrome);
      color: var(--accent);
    }

    & a,
    & button {
      display: flex;
      align-items: center;
      gap: var(--sp-2);
      text-align: start;
      color: var(--muted);
      background: none;
      border: 0;
      border-inline-start: var(--accent-bar) solid transparent;
      padding: var(--pad-snug);
      cursor: pointer;
      transition: color var(--dur-fast), background var(--dur-fast);
    }

    & a:hover,
    & button:hover {
      color: var(--ink);
      background: color-mix(in srgb, var(--line-hi) 14%, transparent);
    }

    & [aria-current] {
      color: var(--accent);
      border-inline-start-color: var(--accent);
      background: color-mix(in srgb, var(--accent) 10%, transparent);
    }
  }

  /* ===================================================================== */
  /*  WAVE 4 — FORM MODULE. Full control set (Nuxt-UI parity). Native where  */
  /*  possible; <nes-*> only where state lives. Same contract: tokens only,  */
  /*  square, hard shadow, smooth --ease, one accent per block.              */
  /* ===================================================================== */

  /* ------------------------------------------------ CONTROL GROUP (fieldset) */
  /* CheckboxGroup / RadioGroup: <fieldset class="control-group"> + <legend>.    */
  .control-group {
    --accent: var(--primary);
    display: flex;
    flex-direction: column;
    gap: var(--sp-3);
    border: 0;
    padding: 0;
    margin: 0;
    min-inline-size: 0;

    &>legend {
      font-family: var(--font-mono);
      font-size: var(--fs-label);
      text-transform: uppercase;
      letter-spacing: var(--ls-chrome);
      color: var(--muted);
      padding: 0;
      margin-block-end: var(--sp-1);
    }

    &.row {
      flex-flow: row wrap;
      column-gap: var(--sp-4);
    }

    &.err>legend {
      color: var(--crit);
    }
  }

  /* ---------------------------------------------------------------- SWATCH */
  /* ColorPicker: native <input type="color" class="swatch">. Square chip.       */
  .swatch {
    --accent: var(--primary);
    appearance: none;
    -webkit-appearance: none;
    inline-size: var(--ctrl-h);
    block-size: var(--ctrl-h);
    flex: none;
    padding: var(--bw);
    cursor: pointer;
    background: var(--slot);
    border: var(--bw-2) solid var(--line-hi);
    box-shadow: var(--sh-1);
    transition: border-color var(--dur-fast);

    &:hover {
      border-color: var(--dim);
    }

    &:focus-visible {
      border-color: var(--accent);
    }

    &:disabled {
      cursor: not-allowed;
      opacity: var(--op-disabled);
    }

    &::-webkit-color-swatch-wrapper {
      padding: 0;
    }

    &::-webkit-color-swatch {
      border: 0;
    }

    &::-moz-color-swatch {
      border: 0;
    }
  }

  /* --------------------------------------------------- DATE / TIME PICKERS */
  /* Native <input type="date|time" class="input">. Tint the OS picker glyph
     from black toward the green primary so it reads on the dark surface.       */
  .input[type="date"],
  .input[type="time"],
  .input[type="datetime-local"],
  .input[type="month"],
  .input[type="week"] {
    cursor: pointer;

    &::-webkit-calendar-picker-indicator {
      cursor: pointer;
      opacity: .8;
      filter: invert(64%) sepia(58%) saturate(560%) hue-rotate(74deg) brightness(96%);
      transition: opacity var(--dur-fast);
    }

    &::-webkit-calendar-picker-indicator:hover {
      opacity: 1;
    }
  }

  /* ------------------------------------------------------------- PIN INPUT */
  /* PinInput: <nes-pin> renders a .pin row of single-char cells. Focus→gold.    */
  .pin {
    display: inline-flex;
    gap: var(--sp-2);

    & input {
      inline-size: var(--ctrl-h);
      block-size: var(--ctrl-h);
      text-align: center;
      font-family: var(--font-mono);
      font-size: var(--fs-h2);
      color: var(--ink);
      background: var(--slot);
      border: var(--bw-2) solid var(--line-hi);
      transition: border-color var(--dur-fast);
      appearance: textfield;
      -moz-appearance: textfield;
    }

    & input::-webkit-outer-spin-button,
    & input::-webkit-inner-spin-button {
      -webkit-appearance: none;
      margin: 0;
    }

    & input:focus-visible {
      border-color: var(--gold);
    }

    & input:disabled {
      opacity: var(--op-disabled);
      cursor: not-allowed;
    }
  }

  /* -------------------------------------------------------------- DROPZONE */
  /* FileUpload: <nes-file> renders a .dropzone; gets .over while dragging.       */
  .dropzone {
    --accent: var(--primary);
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    gap: var(--sp-2);
    text-align: center;
    padding: var(--sp-5) var(--sp-4);
    color: var(--muted);
    background: var(--slot);
    border: var(--bw-2) dashed var(--line-hi);
    cursor: pointer;
    transition: border-color var(--dur-fast), background var(--dur-fast),
      color var(--dur-fast);

    & .big {
      font-family: var(--font-mono);
      font-size: var(--fs-chip);
      color: var(--ink);
    }

    & .sub {
      font-size: var(--fs-chip);
      color: var(--dim);
    }

    &:hover,
    &:focus-within {
      border-color: var(--dim);
      color: var(--ink);
    }

    &.over {
      border-color: var(--accent);
      border-style: solid;
      background: color-mix(in srgb, var(--accent) 10%, var(--slot));
      color: var(--ink);
    }
  }

  .filelist {
    display: flex;
    flex-direction: column;
    gap: var(--sp-2);
    margin-block-start: var(--sp-3);
    padding: 0;
    list-style: none;

    & li {
      display: flex;
      align-items: center;
      gap: var(--sp-2);
      font-family: var(--font-mono);
      font-size: var(--fs-chip);
      color: var(--muted);
      background: var(--panel-2);
      border: var(--bw) solid var(--line);
      padding: var(--pad-snug);
    }

    & .name {
      flex: 1;
      overflow: hidden;
      text-overflow: ellipsis;
      white-space: nowrap;
    }

    & .size {
      color: var(--dim);
    }

    & .x {
      background: none;
      border: 0;
      cursor: pointer;
      color: var(--dim);
      font-family: var(--font-mono);
      line-height: var(--lh-none);

      &:hover {
        color: var(--crit);
      }
    }
  }

  /* ------------------------------------------------------------------ TAGS */
  /* InputTags: <nes-tags> renders a .tags box of .token chips + an inline input.
     (Named .token, not .tag — .tag is the Second-Brain #tag pill.)              */
  .tags {
    --accent: var(--primary);
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: var(--sp-2);
    inline-size: 100%;
    min-block-size: var(--ctrl-h);
    padding: var(--sp-2);
    background: var(--slot);
    border: var(--bw-2) solid var(--line-hi);
    cursor: text;
    transition: border-color var(--dur-fast);

    &:focus-within {
      border-color: var(--gold);
    }

    &>input {
      flex: 1;
      min-inline-size: 6ch;
      border: 0;
      outline: none;
      background: none;
      color: var(--ink);
      font-family: var(--font-body);
      font-size: var(--fs-body);

      &::placeholder {
        color: var(--dim);
      }
    }
  }

  .token {
    display: inline-flex;
    align-items: center;
    gap: var(--sp-1);
    font-family: var(--font-mono);
    font-size: var(--fs-chip);
    color: var(--ink-on-accent);
    background: var(--accent);
    padding: var(--sp-hair) var(--sp-2);
    line-height: var(--lh-heading);

    & .x {
      background: none;
      border: 0;
      cursor: pointer;
      color: inherit;
      font-family: var(--font-mono);
      line-height: var(--lh-none);
      opacity: .8;

      &:hover {
        opacity: 1;
      }
    }
  }

  /* --------------------------------------------------------------- LISTBOX */
  /* Listbox: <nes-listbox> renders [role=listbox] of [role=option] rows.        */
  .listbox {
    --accent: var(--primary);
    display: flex;
    flex-direction: column;
    gap: var(--sp-hair);
    inline-size: 100%;
    max-block-size: 16rem;
    overflow-y: auto;
    padding: var(--sp-2);
    background: var(--slot);
    border: var(--bw-2) solid var(--line-hi);

    & [role="option"] {
      display: flex;
      align-items: center;
      gap: var(--sp-2);
      font-family: var(--font-body);
      font-size: var(--fs-body);
      color: var(--text);
      padding: var(--pad-snug);
      cursor: pointer;
      border: var(--bw) solid transparent;
      transition: background var(--dur-fast), color var(--dur-fast);

      &:hover {
        background: var(--panel-2);
        color: var(--ink);
      }
    }

    & [role="option"].active {
      border-color: var(--line-hi);
    }

    & [role="option"][aria-selected="true"] {
      background: var(--accent);
      color: var(--ink-on-accent);

      &::after {
        content: "✓";
        margin-inline-start: auto;
      }
    }
  }

  /* -------------------------------------------------------------- COMBOBOX */
  /* InputMenu / SelectMenu: <nes-input-menu> / <nes-select-menu>. A field or    */
  /* trigger + a filtered .menu popup (reuses .menu / .menuitem from Dropdown).  */
  .combo {
    --accent: var(--primary);
    position: relative;
    display: block;
    inline-size: 100%;

    &>.menu {
      inline-size: 100%;
      min-inline-size: 0;
      max-block-size: 15rem;
      overflow-y: auto;
    }

    /* .menu is display:flex, which would beat the [hidden] default — restore it */
    &>.menu[hidden] {
      display: none;
    }

    & .menuitem.active {
      background: var(--panel-2);
      color: var(--ink);
    }

    & .menuitem[aria-selected="true"] {
      color: var(--accent);
    }

    & .menu-empty {
      padding: var(--pad-snug);
      font-size: var(--fs-chip);
      color: var(--dim);
    }

    &>.trigger {
      display: flex;
      align-items: center;
      gap: var(--sp-2);
      inline-size: 100%;
      text-align: start;
      font-family: var(--font-body);
      font-size: var(--fs-body);
      color: var(--ink);
      background: var(--slot);
      border: var(--bw-2) solid var(--line-hi);
      block-size: var(--ctrl-h);
      padding-inline: var(--ctrl-pad);
      cursor: pointer;
      transition: border-color var(--dur-fast);

      &:hover {
        border-color: var(--dim);
      }

      &[aria-expanded="true"] {
        border-color: var(--gold);
      }

      & .ph {
        color: var(--dim);
      }

      & .caret {
        margin-inline-start: auto;
        color: var(--accent);
      }
    }
  }

  /* ------------------------------------------------------------------- TREE */
  /* <nes-tree>: hierarchical folder/file list. role=tree with nested role=group;
     indentation via --lvl per row. Same contract: square, tokens, one accent.    */
  .tree {
    --accent: var(--primary);
    display: block;
    inline-size: 100%;
    max-block-size: 24rem;
    overflow: auto;
    font-family: var(--font-body);
    font-size: var(--fs-body);
    color: var(--text);
    background: var(--slot);
    border: var(--bw-2) solid var(--line-hi);
    padding: var(--sp-2);

    & [role="group"][hidden] {
      display: none;
    }

    & .row {
      display: flex;
      align-items: center;
      gap: var(--sp-2);
      padding-block: var(--sp-1);
      padding-inline: var(--sp-2);
      padding-inline-start: calc(var(--sp-2) + var(--lvl, 0) * var(--sp-4));
      cursor: pointer;
      border: var(--bw) solid transparent;
      transition: background var(--dur-fast), color var(--dur-fast);
    }

    & .row:hover {
      background: var(--panel-2);
      color: var(--ink);
    }

    & .chev {
      flex: none;
      inline-size: 1em;
      text-align: center;
      color: var(--muted);
      font-family: var(--font-mono);
    }

    & .ic {
      flex: none;
    }

    & .lab {
      overflow: hidden;
      text-overflow: ellipsis;
      white-space: nowrap;
    }

    & [role="treeitem"].active>.row {
      border-color: var(--line-hi);
    }

    & [role="treeitem"][aria-selected="true"]>.row {
      background: var(--accent);
      color: var(--ink-on-accent);

      & .chev {
        color: var(--ink-on-accent);
      }
    }

    & [role="treeitem"][aria-disabled="true"]>.row {
      color: var(--dim);
      cursor: not-allowed;
    }
  }

  /* ===================================================================== */
  /*  AI CHAT MODULE. Presentational recipes for a chatbot UI (assistant /   */
  /*  user bubbles, prompt box, reasoning, tool calls, streaming shimmer).   */
  /*  State lives in <nes-chat-prompt> / <nes-chat-messages>. Same contract. */
  /* ===================================================================== */

  /* --------------------------------------------------------------- CHAT SHELL */
  /* <div class="chat"> = flex column: scrolling messages + a pinned prompt.     */
  .chat {
    --accent: var(--primary);
    display: flex;
    flex-direction: column;
    gap: var(--sp-4);
    block-size: 100%;
    min-block-size: 0;
  }

  /* messages scroll area (also the class <nes-chat-messages> puts on itself) */
  .chat-messages {
    display: flex;
    flex-direction: column;
    gap: var(--sp-4);
    flex: 1;
    min-block-size: 0;
    overflow-y: auto;
    padding: var(--sp-1);
  }

  /* --------------------------------------------------------------- MESSAGE */
  .msg {
    --accent: var(--primary);
    display: flex;
    gap: var(--sp-3);
    align-items: flex-start;
  }

  .msg>.avatar {
    flex: none;
  }

  .msg .bubble {
    max-inline-size: min(80%, 48ch);
    padding: var(--sp-3);
    color: var(--text);
    font-size: var(--fs-body);
    line-height: var(--lh-body);
    background: var(--panel);
    border: var(--bw-2) solid var(--line-hi);
    box-shadow: var(--sh-1);

    /* tighten stacked paragraphs inside a bubble */
    &>:first-child {
      margin-block-start: 0;
    }

    &>:last-child {
      margin-block-end: 0;
    }
  }

  /* user messages mirror to the inline-end and tint with the accent */
  .msg.user {
    flex-direction: row-reverse;
  }

  .msg.user .bubble {
    background: color-mix(in srgb, var(--accent) 20%, var(--panel));
    border-color: var(--accent);
  }

  /* --------------------------------------------------------------- PROMPT */
  /* <nes-chat-prompt> renders .chat-prompt: an auto-growing textarea + submit. */
  .chat-prompt {
    display: flex;
    align-items: flex-end;
    gap: var(--sp-2);
    padding: var(--sp-2);
    background: var(--slot);
    border: var(--bw-2) solid var(--line-hi);
    transition: border-color var(--dur-fast);

    &:focus-within {
      border-color: var(--gold);
    }

    & textarea {
      flex: 1;
      border: 0;
      outline: none;
      resize: none;
      background: none;
      color: var(--ink);
      font-family: var(--font-body);
      font-size: var(--fs-body);
      line-height: var(--lh-body);
      max-block-size: 40vh;

      &::placeholder {
        color: var(--dim);
      }
    }
  }

  /* ChatPromptSubmit: square accent send button; .busy flips it to a stop. */
  .chat-submit {
    flex: none;
    inline-size: var(--ctrl-h);
    block-size: var(--ctrl-h);
    display: grid;
    place-items: center;
    cursor: pointer;
    color: var(--ink-on-accent);
    background: var(--accent);
    border: var(--bw-2) solid var(--accent-d);
    box-shadow: var(--sh-1);
    font-family: var(--font-mono);
    transition: transform var(--dur-fast) var(--ease), filter var(--dur-fast);

    &:hover {
      filter: brightness(1.08);
    }

    &:active {
      transform: translate(1px, 1px);
      box-shadow: none;
    }

    &.busy {
      color: var(--ink-on-accent);
      background: var(--crit);
      border-color: var(--crit-d);
    }

    &:disabled {
      cursor: not-allowed;
      opacity: .5;
    }
  }

  /* --------------------------------------------------------------- REASONING */
  /* ChatReasoning: <details class="reasoning"> — collapsed "thinking" tokens.   */
  .reasoning {
    border: var(--bw-2) dashed var(--line-hi);
    background: color-mix(in srgb, var(--panel) 55%, transparent);

    &>summary {
      list-style: none;
      cursor: pointer;
      padding: var(--pad-snug);
      font-family: var(--font-mono);
      font-size: var(--fs-label);
      text-transform: uppercase;
      letter-spacing: var(--ls-chrome);
      color: var(--muted);
    }

    &>summary::-webkit-details-marker {
      display: none;
    }

    &[open]>summary {
      color: var(--gold);
    }

    & .body {
      padding: 0 var(--sp-3) var(--sp-3);
      color: var(--dim);
      font-family: var(--font-mono);
      font-size: var(--fs-chip);
      line-height: var(--lh-body);
      white-space: pre-wrap;
    }
  }

  /* --------------------------------------------------------------- TOOL CALL */
  /* ChatTool: <details class="tool"> — a tool invocation with a status badge.   */
  .tool {
    background: var(--slot);
    border: var(--bw-2) solid var(--line-hi);
    font-family: var(--font-mono);
    font-size: var(--fs-chip);

    &>summary {
      display: flex;
      align-items: center;
      gap: var(--sp-2);
      list-style: none;
      cursor: pointer;
      padding: var(--pad-snug);
      color: var(--muted);
    }

    &>summary::-webkit-details-marker {
      display: none;
    }

    & .tname {
      color: var(--cyan);
    }

    &>summary .badge {
      margin-inline-start: auto;
    }

    & .body {
      padding: var(--sp-3);
      border-block-start: var(--bw) solid var(--line);
      color: var(--dim);
      white-space: pre-wrap;
      overflow-x: auto;
    }
  }

  /* --------------------------------------------------------------- SHIMMER */
  /* ChatShimmer: streaming placeholder — reuses the skeleton sweep keyframe.    */
  .shimmer {
    display: flex;
    flex-direction: column;
    gap: var(--sp-2);

    &>span {
      display: block;
      block-size: var(--pip);
      background: var(--panel-2);
      background-image: linear-gradient(90deg, transparent 0,
          color-mix(in srgb, var(--line-hi) 45%, transparent) 50%, transparent 100%);
      background-size: 220% 100%;
      animation: skeleton 1.4s linear infinite;
    }

    &>span:nth-child(2) {
      inline-size: 90%;
    }

    &>span:last-child {
      inline-size: 60%;
    }
  }

  /* --------------------------------------------------------------- PALETTE */
  /* ChatPalette: a framed chat panel (modal / side-panel assistant). Wraps a    */
  /* .chat-messages + .chat-prompt; the prompt sits flush on the bottom edge.    */
  .chat-palette {
    display: flex;
    flex-direction: column;
    block-size: 100%;
    min-block-size: 0;
    background: var(--panel);
    border: var(--bw) solid var(--line-hi);
    box-shadow: var(--sh-4);

    & .chat-messages {
      padding: var(--sp-4);
    }

    & .chat-prompt {
      border: 0;
      border-block-start: var(--bw-2) solid var(--line-hi);
    }
  }

  /* --------------------------------------------------------------- COMPOSER
     The full prompt box — ChatGPT / Claude-Code style: an optional attachment
     row on top, the textarea in the middle, and a toolbar footer (icon actions
     + model picker on the left, char hint + send on the right). Reuses
     .chat-submit for send and .btn for the actions — pure layout, wire in JS. */
  .composer {
    --accent: var(--primary);
    display: flex;
    flex-direction: column;
    background: var(--slot);
    border: var(--bw-2) solid var(--line-hi);
    transition: border-color var(--dur-fast);

    &:focus-within {
      border-color: var(--gold);
    }

    /* attachment chips sit above the input */
    & .composer-attach {
      display: flex;
      flex-wrap: wrap;
      gap: var(--sp-2);
      padding: var(--sp-2) var(--sp-2) 0;
    }

    & textarea {
      border: 0;
      outline: none;
      resize: none;
      background: none;
      color: var(--ink);
      font-family: var(--font-body);
      font-size: var(--fs-body);
      line-height: var(--lh-body);
      padding: var(--sp-3);
      max-block-size: 40vh;

      &::placeholder {
        color: var(--dim);
      }
    }

    /* footer toolbar: actions/model on the left, hint + send pushed right */
    & .composer-bar {
      display: flex;
      align-items: center;
      gap: var(--sp-2);
      padding: var(--sp-2);
      border-block-start: var(--bw-2) solid var(--line);
    }

    & .composer-hint {
      margin-inline-start: auto;
      font-family: var(--font-mono);
      font-size: var(--fs-label);
      color: var(--dim);
    }
  }

  /* attachment pill — a file/image on a prompt (or inside a sent message).
     Ellipsizes a long name; the ✕ removes it. Wire the remove in JS. */
  .attach {
    display: inline-flex;
    align-items: center;
    gap: var(--sp-2);
    max-inline-size: 14rem;
    padding: var(--pad-tight);
    font-family: var(--font-mono);
    font-size: var(--fs-label);
    color: var(--muted);
    background: var(--panel-2);
    border: var(--bw-2) solid var(--line-hi);
    box-shadow: var(--sh-1);

    & .attach-name {
      overflow: hidden;
      text-overflow: ellipsis;
      white-space: nowrap;
    }

    & .attach-size {
      flex: none;
      color: var(--dim);
    }

    & .attach-x {
      flex: none;
      display: grid;
      place-items: center;
      padding: 0;
      color: var(--dim);
      background: none;
      border: 0;
      cursor: pointer;

      &:hover {
        color: var(--crit);
      }
    }
  }

  /* ------------------------------------------------------------- SUGGESTIONS
     Prompt starters (empty state) or follow-ups (after an answer): a wrap of
     tap-to-send pills. Real <button>s — hook each to fill/submit the prompt. */
  .suggest {
    display: flex;
    flex-wrap: wrap;
    gap: var(--sp-2);
  }

  .suggest-item {
    display: inline-flex;
    align-items: center;
    gap: var(--sp-2);
    text-align: start;
    color: var(--muted);
    background: var(--panel);
    border: var(--bw-2) solid var(--line-hi);
    box-shadow: var(--sh-1);
    padding: var(--pad-snug);
    font-family: var(--font-body);
    font-size: var(--fs-body);
    cursor: pointer;
    transition: color var(--dur-fast), border-color var(--dur-fast),
      transform var(--dur-fast) var(--ease);

    & nes-icon {
      color: var(--dim);
    }

    &:hover {
      color: var(--ink);
      border-color: var(--primary);

      & nes-icon {
        color: var(--primary);
      }
    }

    &:active {
      transform: translate(1px, 1px);
      box-shadow: none;
    }
  }

  /* --------------------------------------------------------------- CITATIONS
     Grounded answers: an inline [n] marker in the prose that links down to a
     numbered .sources list under the message. Cyan = "reference" by convention. */
  .cite {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    min-inline-size: 18px;
    padding: 0 var(--sp-1);
    font-family: var(--font-mono);
    font-size: .7em;
    font-weight: var(--fw-bold);
    vertical-align: super;
    line-height: var(--lh-heading);
    color: var(--cyan);
    background: var(--slot);
    border: var(--bw-1) solid var(--cyan-d);
    text-decoration: none;
    cursor: pointer;

    &:hover {
      background: color-mix(in srgb, var(--cyan) 22%, transparent);
    }
  }

  /* A citation marker is a single digit in a box, and flex centres the LINE BOX,
     not the ink: the font's ascent and descent are asymmetric, so the glyph sits
     0.85px high — a whole device pixel at 2x, on the one element whose job is to
     look like a printed superscript.

     `text-box-trim` fixes it, but only on a BLOCK container (it does nothing on the
     inline-flex above — measured, still -0.85px), and trimming removes the
     half-leading, so the box collapses to cap height and the padding has to come
     back explicitly. That means the rule cannot be written unguarded: a browser
     without support would apply the display change and the padding but not the
     trim, and the chip would render 23.7px tall instead of 15.7px. Inside
     @supports, an old browser keeps today's construction and a new one gets a
     glyph centred to 0.38px. */
  @supports (text-box-trim: trim-both) {
    .cite {
      display: inline-block;
      text-align: center;
      text-box-trim: trim-both;
      text-box-edge: cap alphabetic;
      padding-block: var(--sp-1);
    }
  }

  .sources {
    display: flex;
    flex-direction: column;
    gap: var(--sp-2);
    margin-block-start: var(--sp-3);
  }

  .source {
    display: flex;
    align-items: baseline;
    gap: var(--sp-2);
    font-size: var(--fs-body);
    color: var(--muted);

    & .source-n {
      flex: none;
      inline-size: 20px;
      block-size: 20px;
      display: grid;
      place-items: center;
      font-family: var(--font-mono);
      font-size: var(--fs-label);
      font-weight: var(--fw-bold);
      color: var(--cyan);
      background: var(--slot);
      border: var(--bw-1) solid var(--cyan-d);
    }

    & .source-title {
      color: var(--ink);
      text-decoration: none;

      &:hover {
        text-decoration: underline;
        text-underline-offset: 3px;
      }
    }

    & .source-host {
      color: var(--dim);
      font-family: var(--font-mono);
      font-size: var(--fs-label);
    }
  }

  /* ===================================================================== */
  /*  EDITOR MODULE. Lightweight rich-text on native contenteditable (zero   */
  /*  dep): toolbar + prose content + slash/mention/emoji menus + drag grip. */
  /*  Mobile-first: the toolbar scrolls horizontally on narrow screens.      */
  /* ===================================================================== */
  .editor {
    --accent: var(--primary);
    position: relative;
    display: flex;
    flex-direction: column;
    inline-size: 100%;
    background: var(--panel);
    border: var(--bw-2) solid var(--line-hi);
    transition: border-color var(--dur-fast);
  }

  .editor:focus-within {
    border-color: var(--gold);
  }

  /* EditorToolbar */
  .editor-toolbar {
    display: flex;
    align-items: center;
    gap: var(--sp-hair);
    overflow-x: auto;
    padding: var(--sp-2);
    background: var(--panel-2);
    border-block-end: var(--bw-2) solid var(--line-hi);
    scrollbar-width: none;
  }

  .editor-toolbar::-webkit-scrollbar {
    display: none;
  }

  .editor-toolbar button {
    flex: none;
    min-inline-size: 30px;
    block-size: 30px;
    padding-inline: var(--sp-2);
    display: inline-grid;
    place-items: center;
    font-family: var(--font-mono);
    font-size: var(--fs-chip);
    color: var(--muted);
    background: none;
    border: var(--bw) solid transparent;
    cursor: pointer;
    transition: background var(--dur-fast), color var(--dur-fast);
  }

  .editor-toolbar button:hover {
    background: var(--slot);
    color: var(--ink);
  }

  .editor-toolbar button.on {
    color: var(--ink-on-accent);
    background: var(--accent);
    border-color: var(--accent-d);
  }

  .editor-toolbar .editor-sep {
    flex: none;
    inline-size: var(--bw);
    align-self: stretch;
    margin-inline: var(--sp-1);
    background: var(--line-hi);
  }

  /* bilingual ghost chip: SRC ▸ TGT + mode glyph (toggles suggest mode) */
  .editor-lang {
    flex: none;
    display: inline-flex;
    align-items: center;
    gap: var(--sp-2);
    margin-inline-start: auto;
    padding-inline: var(--sp-2);
    font-family: var(--font-mono);
    font-size: var(--fs-label);
    color: var(--muted);

    & .pair {
      display: inline-flex;
      align-items: center;
      gap: 0.25em;
    }

    & .arw {
      color: var(--primary);
    }

    & .m {
      min-inline-size: 1.2em;
      text-align: center;
      color: var(--ink-on-accent);
      background: var(--primary);
      border: var(--bw) solid var(--primary);
    }
  }

  /* blog-writing stats footer (words · chars · reading time) */
  .editor-stats {
    padding: var(--sp-2) var(--sp-4);
    border-block-start: var(--bw-2) solid var(--line-hi);
    background: var(--panel-2);
    font-family: var(--font-mono);
    font-size: var(--fs-label);
    letter-spacing: var(--ls-chrome);
    text-transform: uppercase;
    color: var(--dim);
  }

  /* Editor content (contenteditable prose) */
  .editor-content {
    min-block-size: 8rem;
    max-block-size: 60vh;
    overflow-y: auto;
    padding: var(--sp-4);
    padding-inline-start: var(--sp-5);
    color: var(--text);
    font-family: var(--font-body);
    font-size: var(--fs-body);
    line-height: var(--lh-body);
    outline: none;

    &[data-empty]::before {
      content: attr(data-placeholder);
      color: var(--dim);
      pointer-events: none;
    }

    &>* {
      margin-block: 0 var(--sp-3);
    }

    &>:last-child {
      margin-block-end: 0;
    }

    & h1 {
      font-size: var(--fs-h2);
      font-weight: var(--fw-bold);
    }

    & h2 {
      font-size: var(--fs-lead);
      font-weight: var(--fw-bold);
      text-transform: none;
      letter-spacing: 0;
    }

    & h3 {
      font-size: var(--fs-body);
      font-weight: var(--fw-bold);
      text-transform: uppercase;
      letter-spacing: var(--ls-chrome);
      color: var(--muted);
    }

    & ul,
    & ol {
      padding-inline-start: var(--sp-5);
    }

    & ul {
      list-style: square;
    }

    & ol {
      list-style: decimal;
    }

    & li::marker {
      color: var(--accent);
    }

    & blockquote {
      margin-inline: 0;
      padding-inline-start: var(--sp-3);
      border-inline-start: var(--accent-bar) solid var(--accent);
      color: var(--muted);
    }

    & :not(pre)>code {
      font-family: var(--font-mono);
      font-size: var(--fs-code);
      color: var(--cyan);
      background: var(--slot);
      border: var(--bw) solid var(--line);
      padding: var(--atom-py) var(--atom-px);
    }

    & pre {
      font-family: var(--font-mono);
      font-size: var(--fs-chip);
      color: var(--ink);
      background: var(--slot);
      border: var(--bw-2) solid var(--line-hi);
      padding: var(--sp-3);
      overflow-x: auto;
    }

    & a {
      color: var(--accent);
      text-decoration: underline;
    }

    & hr {
      border: 0;
      border-block-start: var(--bw-2) solid var(--line-hi);
    }

    /* @mention chip (contenteditable=false atom) */
    & .mention {
      color: var(--accent);
      background: color-mix(in srgb, var(--accent) 16%, transparent);
      border: var(--bw) solid var(--accent);
      padding: var(--atom-py) var(--atom-px);
      font-family: var(--font-mono);
      font-size: var(--fs-code);
      white-space: nowrap;
    }

    /* VSCode-style inline AI suggestion — dim ghost text, Tab to accept */
    & .ghost {
      color: var(--dim);
      opacity: .75;
      user-select: none;
    }

    & .ghost::after {
      content: " ⇥";
      font-family: var(--font-mono);
      font-size: .8em;
      color: var(--accent);
    }
  }

  /* slash / mention / emoji popup — floats at the caret, reuses .menu look */
  .editor-menu[hidden] {
    display: none;
  }

  .editor-menu {
    position: absolute;
    z-index: var(--z-overlay);
    min-inline-size: 12rem;
    max-block-size: 15rem;
    overflow-y: auto;
    display: flex;
    flex-direction: column;
    gap: var(--sp-hair);
    padding: var(--sp-2);
    background: var(--panel);
    border: var(--bw) solid var(--line-hi);
    box-shadow: var(--sh-4);

    /* items reuse the base .menuitem (Dropdown) — only the editor extras here */
    & .menuitem .k {
      flex: none;
      inline-size: 1.4em;
      text-align: center;
      color: var(--muted);
      font-family: var(--font-mono);
    }

    & .menuitem .sub {
      margin-inline-start: auto;
      color: var(--dim);
      font-size: var(--fs-label);
    }

    /* keyboard-selected row (base already styles :hover) */
    & .menuitem.active {
      background: var(--panel-2);
      color: var(--ink);
    }
  }

  /* EditorDragHandle — grip shown at the inline-start of the hovered block */
  .editor-drag {
    position: absolute;
    z-index: var(--z-pop);
    inline-size: 1rem;
    text-align: center;
    color: var(--dim);
    cursor: grab;
    user-select: none;
    font-family: var(--font-mono);
    opacity: 0;
    transition: opacity var(--dur-fast), color var(--dur-fast);
  }

  .editor-drag.show {
    opacity: 1;
  }

  .editor-drag:hover {
    color: var(--accent);
  }

  .editor-drag:active {
    cursor: grabbing;
  }

  /* drop indicator while dragging a block */
  .editor-content .drop-before {
    box-shadow: 0 -2px 0 var(--accent);
  }

  .editor-content .drop-after {
    box-shadow: 0 2px 0 var(--accent);
  }

  /* ===================================================================== */
  /*  TYPOGRAPHY / MDC MODULE. Render AI/markdown output as rich components   */
  /*  (drop-in prose embeds). Reuses .codeblock / .tablist / .tree; adds the  */
  /*  code-preview, code-group, code-collapse, card-group, field-group,       */
  /*  prompt recipes + <nes-code-tree>. Same contract.                        */
  /* ===================================================================== */

  /* CardGroup — responsive grid of cards */
  .card-group {
    display: grid;
    gap: var(--sp-4);
    grid-template-columns: repeat(auto-fill, minmax(min(100%, 240px), 1fr));
  }

  /* CodePreview — rendered result on top, source below (::code-preview) */
  .code-preview {
    --accent: var(--primary);
    inline-size: 100%;
    background: var(--panel);
    border: var(--bw-2) solid var(--line-hi);

    &>.preview {
      display: flex;
      flex-wrap: wrap;
      align-items: center;
      gap: var(--sp-3);
      padding: var(--sp-5) var(--sp-4);
    }

    &>nes-code,
    &>.codeblock {
      display: block;
      border-block-start: var(--bw-2) solid var(--line-hi);
    }

    /* the embedded code sheds its own frame — the wrapper is the frame */
    &>nes-code .codeblock,
    &>.codeblock {
      border: 0;
      box-shadow: none;
    }
  }

  /* CodeGroup — tabs over multiple code blocks: <nes-tabs class="code-group"> */
  .code-group {
    border: var(--bw-2) solid var(--line-hi);
    background: var(--slot);

    &>.tablist {
      background: var(--panel-2);
      border-block-end: var(--bw-2) solid var(--line-hi);
      padding-inline: var(--sp-2);
    }

    & nes-code .codeblock,
    & .codeblock {
      border: 0;
      box-shadow: none;
    }
  }

  /* CodeCollapse — <details class="code-collapse"> around a code block */
  .code-collapse {
    border: var(--bw-2) solid var(--line-hi);
    background: var(--slot);

    &>summary {
      list-style: none;
      cursor: pointer;
      padding: var(--pad-snug);
      font-family: var(--font-mono);
      font-size: var(--fs-label);
      text-transform: uppercase;
      letter-spacing: var(--ls-chrome);
      color: var(--muted);
      border-block-end: var(--bw-2) solid transparent;
    }

    &>summary::-webkit-details-marker {
      display: none;
    }

    &[open]>summary {
      color: var(--accent);
      border-block-end-color: var(--line-hi);
    }

    & .codeblock {
      border: 0;
      box-shadow: none;
    }
  }

  /* FieldGroup — a bordered list of prop/param rows (API docs, MDC ::field) */
  .field-group {
    --accent: var(--primary);
    inline-size: 100%;
    border: var(--bw-2) solid var(--line-hi);
    background: var(--slot);

    &>.field-row {
      padding: var(--pad-box);
    }

    &>.field-row+.field-row {
      border-block-start: var(--bw) solid var(--line);
    }
  }

  .field-row {
    & .name {
      font-family: var(--font-mono);
      font-weight: var(--fw-bold);
      color: var(--ink);
    }

    & .type {
      font-family: var(--font-mono);
      font-size: var(--fs-chip);
      color: var(--cyan);
      margin-inline-start: var(--sp-2);
    }

    & .req {
      color: var(--crit);
      margin-inline-start: var(--sp-1);
    }

    & p {
      margin: var(--sp-1) 0 0;
      color: var(--muted);
      font-size: var(--fs-body);
    }
  }

  /* Prompt — a terminal / AI command line (accent caret + mono text) */
  .prompt {
    display: flex;
    align-items: center;
    gap: var(--sp-2);
    inline-size: 100%;
    padding: var(--pad-box);
    background: var(--slot);
    border: var(--bw-2) solid var(--line-hi);
    font-family: var(--font-mono);
    font-size: var(--fs-body);
    color: var(--ink);

    &::before {
      content: "❯";
      color: var(--accent);
      font-weight: var(--fw-bold);
    }
  }

  /* Terminal — a multi-line shell session: command lines (❯) + output. */
  .terminal {
    --accent: var(--primary);
    inline-size: 100%;
    overflow-x: auto;
    padding: var(--sp-4);
    background: var(--slot);
    border: var(--bw-2) solid var(--line-hi);
    font-family: var(--font-mono);
    font-size: var(--fs-body);
    line-height: var(--lh-body);
    color: var(--muted);
    white-space: pre;

    & .cmd {
      display: block;
      color: var(--ink);
    }

    & .cmd::before {
      content: "❯ ";
      color: var(--accent);
      font-weight: var(--fw-bold);
    }

    & .out {
      display: block;
      color: var(--muted);
    }

    & .ok {
      color: var(--good);
    }

    & .err {
      color: var(--crit);
    }
  }

  /* Diff — a code diff block: added / removed / context lines with a gutter. */
  .diff {
    inline-size: 100%;
    overflow-x: auto;
    background: var(--slot);
    border: var(--bw-2) solid var(--line-hi);
    font-family: var(--font-mono);
    font-size: var(--fs-body);
    line-height: var(--lh-body);
    white-space: pre;

    & .add,
    & .del,
    & .ctx {
      display: block;
      padding-inline: var(--sp-6) var(--sp-3);
      color: var(--text);
    }

    & .add {
      background: color-mix(in srgb, var(--good) 14%, transparent);
    }

    & .del {
      background: color-mix(in srgb, var(--crit) 14%, transparent);
    }

    & .add::before,
    & .del::before,
    & .ctx::before {
      display: inline-block;
      inline-size: 1.2em;
      margin-inline-start: -1.6em;
      color: var(--dim);
      content: " ";
    }

    & .add::before {
      content: "+";
      color: var(--good);
    }

    & .del::before {
      content: "-";
      color: var(--crit);
    }

    /* diff chrome, emitted by <nes-diff> when it parses a unified diff:
       .file = the ---/+++ pair, .meta = an @@ hunk range. Unmarked lines. */
    & .file,
    & .meta {
      display: block;
      padding-inline: var(--sp-6) var(--sp-3);
      color: var(--dim);
    }

    & .file {
      background: var(--panel-2);
      color: var(--muted);
      font-weight: var(--fw-bold);
    }

    & .meta {
      color: var(--cyan);
    }
  }

  /* Tasklist — a GitHub-style checklist (AI plans / todos): done + open items. */
  .tasklist {
    list-style: none;
    margin: 0;
    padding: 0;
    inline-size: 100%;

    & li {
      display: flex;
      align-items: baseline;
      gap: var(--sp-2);
      padding-block: var(--sp-1);
      color: var(--text);
    }

    & li::before {
      content: "";
      flex: none;
      inline-size: var(--pip);
      block-size: var(--pip);
      translate: 0 0.15em;
      background: var(--panel-2);
      border: var(--bw-2) solid var(--line-hi);
    }

    & li.done {
      color: var(--muted);
      text-decoration: line-through;
      text-decoration-color: var(--line-hi);
    }

    & li.done::before {
      content: "✓";
      display: flex;
      align-items: center;
      justify-content: center;
      font-size: var(--fs-chip);
      color: var(--ink-on-accent);
      background: var(--good);
      border-color: var(--good);
    }
  }

  /* Code block filename header (opt-in via <nes-code file="…">) */
  .cb-file {
    padding: var(--pad-snug);
    background: var(--panel-2);
    border: var(--bw-2) solid var(--line-hi);
    border-block-end: 0;
    font-family: var(--font-mono);
    font-size: var(--fs-label);
    letter-spacing: var(--ls-chrome);
    color: var(--muted);
  }

  /* CodeTree — <nes-code-tree>: file tree (left) + code viewer (right) */
  /* mobile-first: one column, the tree stacked above the viewer. The side-by-side
     shape is added at --bp-sm, not taken away below it. */
  .code-tree {
    display: grid;
    grid-template-columns: 1fr;
    inline-size: 100%;
    max-block-size: 26rem;
    background: var(--slot);
    border: var(--bw-2) solid var(--line-hi);
  }

  .code-tree>.tree {
    border: 0;
    border-block-end: var(--bw-2) solid var(--line-hi);
    max-block-size: 10rem;
    overflow: auto;
  }

  .code-tree>.ct-view {
    overflow: auto;
    min-inline-size: 0;

    & .codeblock {
      border: 0;
      box-shadow: none;
    }

    & .ct-empty {
      padding: var(--sp-4);
      color: var(--dim);
      font-family: var(--font-mono);
      font-size: var(--fs-chip);
    }
  }

  @media (min-width: 36rem) {
    .code-tree {
      grid-template-columns: minmax(9rem, 15rem) minmax(0, 1fr);
    }

    .code-tree>.tree {
      border-block-end: 0;
      border-inline-end: var(--bw-2) solid var(--line-hi);
      max-block-size: none;
    }
  }

  /* ===================================================================== */
  /*  VISUALIZE MODULE. On-brand Mermaid diagrams (AI/LLM output) + a       */
  /*  step-through "How it works" walkthrough. Mermaid is NEVER bundled     */
  /*  (bring-your-own / lazy-load); we ship the frame, theme, and focus.    */
  /* ===================================================================== */

  nes-mermaid {
    --accent: var(--primary);
  }

  .mermaid-view {
    inline-size: 100%;
    overflow: auto;
    padding: var(--sp-4);
    background: var(--panel);
    border: var(--bw-2) solid var(--line-hi);
  }

  .mermaid-view svg {
    display: block;
    max-inline-size: 100%;
    block-size: auto;
    margin-inline: auto;
  }

  /* enforce the brand — square corners on every generated diagram shape */
  .mermaid-view :is(.node, .cluster) rect,
  .mermaid-view rect.actor,
  .mermaid-view .label-container {
    rx: 0;
    ry: 0;
  }

  /* raw fallback: no mermaid yet, mid-stream, or invalid syntax */
  .mermaid-view .mmd-raw {
    margin: 0;
    font-family: var(--font-mono);
    font-size: var(--fs-body);
    color: var(--muted);
    white-space: pre-wrap;
    word-break: break-word;
  }

  /* focus mode: dim the rest, spotlight the current step's nodes */
  .mermaid-view.has-focus :is(.node, .actor, .cluster):not(.nes-focus) {
    opacity: var(--op-dim);
    transition: opacity var(--dur-fast);
  }

  .mermaid-view .nes-focus :is(rect, polygon, circle, path) {
    stroke: var(--accent);
    stroke-width: 3px;
  }

  /* Walkthrough — step-by-step "How it works" */
  .walkthrough {
    --accent: var(--primary);
    display: flex;
    flex-direction: column;
    gap: var(--sp-4);
    inline-size: 100%;
    padding: var(--sp-4);
    background: var(--slot);
    border: var(--bw-2) solid var(--line-hi);
    outline: none;

    &:focus-visible {
      box-shadow: inset 0 0 0 var(--bw-2) var(--accent);
    }
  }

  .wt-head {
    display: flex;
    align-items: baseline;
    justify-content: space-between;
    gap: var(--sp-3);
  }

  .wt-title {
    font-family: var(--font-mono);
    font-weight: var(--fw-bold);
    font-size: var(--fs-h3);
    color: var(--ink);
  }

  .wt-count {
    flex: none;
    font-family: var(--font-mono);
    font-size: var(--fs-chip);
    color: var(--muted);
  }

  .wt-body {
    min-block-size: 3.5rem;
    color: var(--text);

    &> :first-child {
      margin-block-start: 0;
    }

    &> :last-child {
      margin-block-end: 0;
    }
  }

  .wt-foot {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: var(--sp-3);
  }

  .wt-dots {
    display: flex;
    flex: 1;
    flex-wrap: wrap;
    justify-content: center;
    gap: var(--sp-2);
  }

  .wt-dot {
    inline-size: 0.75rem;
    block-size: 0.75rem;
    padding: 0;
    cursor: pointer;
    background: var(--panel-2);
    border: var(--bw) solid var(--line-hi);
    transition: background var(--dur-fast);

    &[aria-current="true"] {
      background: var(--accent);
      border-color: var(--accent);
    }
  }

  /* Lens — one concept, many angles: <nes-tabs class="lens"> (framed tabs) */
  nes-tabs.lens {
    border: var(--bw-2) solid var(--line-hi);
    background: var(--slot);

    &>.tablist {
      background: var(--panel-2);
      border-block-end: var(--bw-2) solid var(--line-hi);
      padding-inline: var(--sp-2);
    }

    & .tabpanel {
      padding: var(--sp-4);
    }

    & .mermaid-view {
      border: 0;
      padding: 0;
    }
  }

  /* autoplay explainer: a subtle animated accent bar under the head */
  .walkthrough.is-auto .wt-head {
    border-block-end: var(--bw-2) solid var(--accent);
    padding-block-end: var(--sp-2);
  }

  /* Zoom — pan/zoom viewport for big diagrams & images.
     Every internal is scoped to the element that builds it. These rules only make
     sense together with the transform <nes-zoom> applies to .zoom-stage: on its own
     .zoom-view is a box with a grab cursor that cannot be grabbed, and its
     `overflow: hidden` silently beats a scroll container declared earlier at the
     same specificity (e.g. .mermaid-view), so a tall child is just clipped. */
  nes-zoom {
    position: relative;
  }

  nes-zoom .zoom-view {
    inline-size: 100%;
    block-size: 100%;
    min-block-size: 12rem;
    overflow: hidden;
    background: var(--panel);
    border: var(--bw-2) solid var(--line-hi);
    cursor: grab;
    touch-action: none;

    &.grabbing {
      cursor: grabbing;
    }
  }

  nes-zoom .zoom-stage {
    inline-size: 100%;
    transform-origin: center center;
    /* No static `will-change: transform`. Declared in CSS it keeps a compositing
       layer alive for every zoom stage on the page, for the page's whole life —
       what MDN warns against — and vector content rasterised into that layer only
       re-sharpens once the gesture settles. <nes-zoom> sets the hint on
       pointerdown/wheel and drops it ~200ms after the gesture ends: present while
       it is needed, absent otherwise. */

    & svg,
    & img {
      display: block;
      max-inline-size: 100%;
      block-size: auto;
      margin-inline: auto;
      pointer-events: none;
    }
  }

  nes-zoom .zoom-bar {
    position: absolute;
    inset-block-start: var(--sp-2);
    inset-inline-end: var(--sp-2);
    display: flex;
    gap: var(--sp-2);
  }

  /* Annotate — numbered hotspots + popover over any content */
  nes-annotate {
    position: relative;
  }

  .anno-canvas {
    position: relative;
    inline-size: 100%;

    & img {
      display: block;
      max-inline-size: 100%;
      block-size: auto;
    }
  }

  .anno-pin {
    position: absolute;
    translate: -50% -50%;
    inline-size: 26px;
    block-size: 26px;
    padding: 0;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    font-family: var(--font-mono);
    font-size: var(--fs-label);
    font-weight: var(--fw-bold);
    color: var(--ink-on-accent);
    background: var(--accent, var(--primary));
    border: var(--bw-2) solid var(--ink);
    box-shadow: var(--sh-2);
    cursor: pointer;
    z-index: var(--z-raised);

    &:hover,
    &.active {
      background: var(--ink);
      color: var(--accent, var(--primary));
    }
  }

  .anno-pop {
    position: absolute;
    z-index: var(--z-pop);
    inline-size: min(16rem, 80%);
    margin-block-start: var(--sp-4);
    margin-inline-start: var(--sp-2);
    padding: var(--pad-box);
    background: var(--panel-2);
    border: var(--bw-2) solid var(--accent, var(--primary));
    box-shadow: var(--sh-3);
    font-size: var(--fs-body);
    color: var(--text);

    &[hidden] {
      display: none;
    }
  }

  .anno-title {
    margin-block-end: var(--sp-1);
    font-family: var(--font-mono);
    font-weight: var(--fw-bold);
    color: var(--accent, var(--primary));
  }

  /* Compare — A/B before-after slider */
  .compare {
    position: relative;
    display: grid;
    inline-size: 100%;
    overflow: hidden;
    border: var(--bw-2) solid var(--line-hi);
    user-select: none;
    touch-action: pan-y;

    &>.cmp-a,
    &>.cmp-b {
      grid-area: 1 / 1;
      inline-size: 100%;
    }

    & img {
      display: block;
      inline-size: 100%;
      block-size: auto;
      pointer-events: none;
    }

    &>.cmp-a {
      z-index: var(--z-raised);
    }
  }

  .cmp-handle {
    position: absolute;
    z-index: var(--z-pop);
    inset-block: 0;
    inline-size: var(--bw-2);
    translate: -50% 0;
    background: var(--accent, var(--primary));
    cursor: ew-resize;

    &::before {
      content: "◀▶";
      position: absolute;
      inset-block-start: 50%;
      inset-inline-start: 50%;
      translate: -50% -50%;
      padding: var(--chip-py) var(--chip-px);
      font-family: var(--font-mono);
      font-size: var(--fs-label);
      color: var(--ink-on-accent);
      background: var(--accent, var(--primary));
      white-space: nowrap;
    }

    &:focus-visible {
      outline: var(--ring-w) solid var(--ink);
      outline-offset: 2px;
    }
  }

  /* Legend — a color / shape key for a diagram */
  .legend {
    display: flex;
    flex-wrap: wrap;
    gap: var(--sp-2) var(--sp-4);
  }

  .legend-item {
    display: inline-flex;
    align-items: center;
    gap: var(--sp-2);
    font-family: var(--font-mono);
    font-size: var(--fs-label);
    color: var(--muted);

    &::before {
      content: "";
      inline-size: var(--pip);
      block-size: var(--pip);
      flex: none;
      background: var(--accent, var(--primary));
      border: var(--bw) solid var(--line-hi);
    }
  }

  /* ======================================================================
     AGENTS · AI-First WebUI primitives — stateless recipes (0 JS, 0 dep).
     Built to render straight from an LLM/agent's HTML output: a live agent
     roster, a context-budget bar, an orchestration/reasoning trace, and a
     response feedback bar. Every state is a data-attr an agent can emit;
     colours flow from the shared run-state vocabulary below.
     ====================================================================== */

  /* ONE run-state vocabulary → one colour, everywhere. Drives the status light
     on .agent, the step mark on .trace-step / .plan-step, and every OpenCode
     surface that reports progress (.statusline .sandbox .check-item .runbar
     .deploy). "running" therefore looks the same whether it's an agent, a
     trace step, a CI check or a deploy. Unknown/unset → dim (idle).
     Five names, deliberately: queued (waiting) · thinking (starting up /
     reasoning) · running (live) · done (finished ok) · error (failed). */
  :is(.agent,
    .trace-step,
    .plan-step,
    .statusline,
    .sandbox,
    .check-item,
    .runbar,
    .deploy)[data-state="queued"] {
    --state: var(--steel);
  }

  :is(.agent,
    .trace-step,
    .plan-step,
    .statusline,
    .sandbox,
    .check-item,
    .runbar,
    .deploy)[data-state="thinking"] {
    --state: var(--gold);
  }

  :is(.agent,
    .trace-step,
    .plan-step,
    .statusline,
    .sandbox,
    .check-item,
    .runbar,
    .deploy)[data-state="running"] {
    --state: var(--primary);
  }

  :is(.agent,
    .trace-step,
    .plan-step,
    .statusline,
    .sandbox,
    .check-item,
    .runbar,
    .deploy)[data-state="done"] {
    --state: var(--good);
  }

  :is(.agent,
    .trace-step,
    .plan-step,
    .statusline,
    .sandbox,
    .check-item,
    .runbar,
    .deploy)[data-state="error"] {
    --state: var(--crit);
  }

  /* -------------------------------------------------------------- AGENT
     One agent in a multi-agent roster: status light + name/model + current
     task. Stack several (grid-cards or a plain column) for the whole crew.
     data-state colours the left bar + light; thinking/running blink live. */
  .agent {
    display: grid;
    grid-template-columns: auto 1fr;
    column-gap: var(--sp-3);
    align-items: start;
    background: var(--panel);
    border: var(--bw-2) solid var(--line-hi);
    border-inline-start: var(--accent-bar) solid var(--state, var(--line-hi));
    box-shadow: var(--sh-3);
    padding: var(--pad-box);

    /* the status light: steady dot, blinks while the agent is working */
    &::before {
      content: "";
      grid-row: 1 / span 2;
      align-self: start;
      margin-block-start: var(--sp-1);
      inline-size: var(--dot);
      block-size: var(--dot);
      background: var(--state, var(--dim));
      border: var(--bw-1) solid var(--line);
      box-shadow: var(--sh-1);
    }

    &[data-state="thinking"]::before,
    &[data-state="running"]::before {
      animation: blink 1.1s steps(2) infinite;
    }

    & .agent-head {
      grid-column: 2;
      display: flex;
      align-items: baseline;
      flex-wrap: wrap;
      gap: var(--sp-2);
    }

    & .agent-name {
      font-family: var(--font-mono);
      font-weight: var(--fw-bold);
      font-size: var(--fs-chip);
      text-transform: uppercase;
      letter-spacing: var(--ls-chrome);
      color: var(--ink);
    }

    & .agent-role {
      font-family: var(--font-mono);
      font-size: var(--fs-label);
      color: var(--dim);
    }

    & .agent-task {
      grid-column: 2;
      min-inline-size: 0;
      margin-block-start: var(--sp-1);
      font-size: var(--fs-body);
      color: var(--muted);
    }
  }

  /* ----------------------------------------------------- CONTEXT / USAGE
     A stacked token-budget bar: each <span> is one slice (system / history /
     tools / response) sized by --seg and tinted by data-accent; the gap left
     over is the free window. Pair with .legend for labels + .usage-meta for
     the readout. Answers "how full is my context window" at a glance. */
  .usage {
    display: flex;
    flex-direction: column;
    gap: var(--sp-2);
  }

  .usage-bar {
    display: flex;
    block-size: 18px;
    background: var(--slot);
    border: var(--bw-2) solid var(--line-hi);
    box-shadow: var(--sh-2);
    overflow: hidden;

    &>span {
      flex: 0 0 var(--seg, 0%);
      background: var(--accent, var(--primary));
      border-inline-end: var(--bw-2) solid var(--line);
    }
  }

  .usage-meta {
    display: flex;
    justify-content: space-between;
    font-family: var(--font-mono);
    font-size: var(--fs-label);
    color: var(--muted);
  }

  /* --------------------------------------------------- TRACE / REASONING
     A vertical run trace: what the agent planned, called, and concluded.
     Marks share the run-state colours; a step can be a static row OR wrap a
     native <details> for zero-JS expand of the tool I/O — no script at all. */
  .trace {
    display: flex;
    flex-direction: column;
    font-family: var(--font-mono);

    &>.trace-step {
      position: relative;
      margin-inline-start: var(--sp-1);
      padding-inline-start: var(--sp-5);
      padding-block-end: var(--sp-4);
      border-inline-start: var(--bw-2) solid var(--line-hi);
    }

    &>.trace-step:last-child {
      border-inline-start-color: transparent;
      padding-block-end: 0;
    }

    /* the step mark — square, coloured by data-state, blinks while active */
    &>.trace-step::before {
      content: "";
      position: absolute;
      inset-block-start: var(--sp-hair);
      inset-inline-start: calc((var(--pip) + var(--bw-2)) / -2);
      inline-size: var(--pip);
      block-size: var(--pip);
      background: var(--state, var(--dim));
      border: var(--bw-2) solid var(--line);
      box-shadow: var(--sh-1);
    }

    &>.trace-step[data-state="thinking"]::before,
    &>.trace-step[data-state="running"]::before {
      animation: blink 1.1s steps(2) infinite;
    }
  }

  .trace-label {
    color: var(--ink);
    font-weight: var(--fw-bold);
    font-size: var(--fs-chip);
  }

  .trace-meta {
    color: var(--dim);
    font-size: var(--fs-label);
    margin-inline-start: var(--sp-2);
  }

  .trace-detail {
    margin-block-start: var(--sp-2);
    font-family: var(--font-body);
    font-size: var(--fs-body);
    color: var(--muted);
  }

  /* collapsible step: <details> whose <summary> is the label row. Kill the UA
     triangle; a .trace-caret icon (optional) rotates open. */
  .trace-step details>summary {
    display: flex;
    align-items: baseline;
    gap: var(--sp-2);
    cursor: pointer;
    list-style: none;
  }

  .trace-step details>summary::-webkit-details-marker {
    display: none;
  }

  .trace-caret {
    transition: transform var(--dur-fast) var(--ease);
  }

  .trace-step details[open]>summary .trace-caret {
    transform: rotate(90deg);
  }

  /* ---------------------------------------------------- FEEDBACK / HITL
     The bar under an AI response: rate / copy / regenerate on the left, run
     metrics (model · tokens · latency) on the right. Actions are plain .btn —
     use aria-pressed on the chosen thumb to lock the rating in. */
  .feedback {
    display: flex;
    align-items: center;
    flex-wrap: wrap;
    gap: var(--sp-3);
    margin-block-start: var(--sp-3);
    padding-block-start: var(--sp-3);
    border-block-start: var(--bw-2) solid var(--line);

    & .feedback-actions {
      display: flex;
      gap: var(--sp-2);
    }

    & .feedback-meta {
      margin-inline-start: auto;
      display: flex;
      align-items: center;
      gap: var(--sp-3);
      font-family: var(--font-mono);
      font-size: var(--fs-label);
      color: var(--dim);
    }
  }

  /* ======================================================================
     SECOND BRAIN · note / knowledge management (Obsidian-style) — stateless
     recipes + the <nes-graph> knowledge graph. Note cards, wiki-links, tags
     and backlinks are 0-JS; the graph is the one interactive piece.
     ====================================================================== */

  /* -------------------------------------------------------------- NOTE CARD
     One note in a vault / list: title + timestamp, a 2-line excerpt, tag pills
     and a link/backlink readout. data-accent classifies it by folder/domain. */
  .note {
    --accent: var(--primary);
    display: flex;
    flex-direction: column;
    gap: var(--sp-2);
    background: var(--panel);
    border: var(--bw-2) solid var(--line-hi);
    border-inline-start: var(--accent-bar) solid var(--accent);
    box-shadow: var(--sh-3);
    padding: var(--pad-box);
    cursor: pointer;
    transition: border-color var(--dur-fast), transform var(--dur-fast) var(--ease);

    &:hover {
      border-inline-end-color: var(--accent);
      border-block-color: var(--accent);
    }

    &:active {
      transform: translate(1px, 1px);
      box-shadow: var(--sh-1);
    }

    & .note-head {
      display: flex;
      align-items: baseline;
      gap: var(--sp-3);
    }

    & .note-title {
      margin: 0;
      font-family: var(--font-mono);
      font-size: var(--fs-h3);
      font-weight: var(--fw-bold);
      color: var(--ink);
    }

    & .note-time {
      flex: none;
      margin-inline-start: auto;
      font-family: var(--font-mono);
      font-size: var(--fs-label);
      color: var(--dim);
    }

    & .note-excerpt {
      margin: 0;
      font-size: var(--fs-body);
      color: var(--muted);
      display: -webkit-box;
      -webkit-line-clamp: 2;
      line-clamp: 2;
      -webkit-box-orient: vertical;
      overflow: hidden;
    }

    & .note-tags {
      display: flex;
      flex-wrap: wrap;
      gap: var(--sp-2);
    }

    & .note-meta {
      display: flex;
      align-items: center;
      gap: var(--sp-3);
      font-family: var(--font-mono);
      font-size: var(--fs-label);
      color: var(--dim);
    }
  }

  /* tag pill — a #tag. Cheap, mono, tap-to-filter. */
  .tag {
    display: inline-flex;
    align-items: center;
    font-family: var(--font-mono);
    font-size: var(--fs-label);
    color: var(--dim);
    background: var(--slot);
    border: var(--bw-1) solid var(--line-hi);
    padding: var(--sp-hair) var(--sp-2);
    cursor: pointer;
    transition: color var(--dur-fast), border-color var(--dur-fast);

    &:hover {
      color: var(--primary);
      border-color: var(--primary);
    }
  }

  /* wiki-link — an inline [[internal link]]. Dashed underline; .new = a note
     that doesn't exist yet (dim), like Obsidian's unresolved links. */
  .wikilink {
    color: var(--indigo);
    text-decoration: none;
    border-block-end: var(--bw-1) dashed color-mix(in srgb, var(--indigo) 60%, transparent);
    transition: color var(--dur-fast), background var(--dur-fast);

    &:hover {
      color: var(--ink);
      background: color-mix(in srgb, var(--indigo) 14%, transparent);
      border-block-end-style: solid;
    }

    &.new {
      color: var(--dim);
      border-block-end-color: var(--line-hi);
    }
  }

  /* ------------------------------------------------------------- BACKLINKS
     "Linked references": the notes that point at this one, each with the
     sentence it was mentioned in. <mark> highlights the match. */
  .backlinks {
    display: flex;
    flex-direction: column;
    gap: var(--sp-2);
  }

  .backlinks-head {
    display: flex;
    align-items: center;
    gap: var(--sp-2);
    margin: 0;
    font-family: var(--font-mono);
    font-size: var(--fs-label);
    text-transform: uppercase;
    letter-spacing: var(--ls-chrome);
    color: var(--muted);
  }

  .backlink {
    display: flex;
    flex-direction: column;
    gap: var(--sp-hair);
    padding: var(--pad-snug);
    background: var(--slot);
    border: var(--bw-2) solid var(--line-hi);
    border-inline-start: var(--accent-bar) solid var(--line-hi);
    box-shadow: var(--sh-1);
    text-decoration: none;
    transition: border-color var(--dur-fast), transform var(--dur-fast) var(--ease);

    &:hover {
      border-inline-start-color: var(--primary);
    }

    &:active {
      transform: translate(1px, 1px);
    }

    & .backlink-title {
      font-family: var(--font-mono);
      font-size: var(--fs-chip);
      color: var(--ink);
    }

    & .backlink-ctx {
      font-size: var(--fs-body);
      color: var(--dim);

      & mark {
        color: var(--ink);
        background: color-mix(in srgb, var(--gold) 32%, transparent);
      }
    }
  }

  /* ---------------------------------------------------------------- GRAPH
     <nes-graph> renders this SVG: square nodes (group = data-accent), hard
     edges, and a focus mode that lights the selected node's neighbourhood. */
  .graph-svg {
    inline-size: 100%;
    block-size: auto;
    background: var(--slot);
    border: var(--bw-2) solid var(--line-hi);
    box-shadow: var(--sh-2);
    touch-action: none;
  }

  .graph-edge {
    stroke: var(--line-hi);
    stroke-width: 2;
  }

  .graph-edge.on {
    stroke: var(--primary);
    stroke-width: 3;
  }

  .graph-node {
    cursor: pointer;

    & .graph-dot {
      fill: var(--accent, var(--primary));
      stroke: var(--line);
      stroke-width: 2;
    }

    & .graph-label {
      fill: var(--muted);
      font-family: var(--font-mono);
      font-size: var(--fs-h3);
      text-anchor: middle;
    }

    &:hover .graph-dot,
    &:focus-visible .graph-dot {
      fill: var(--ink);
    }

    &:focus-visible {
      outline: none;
    }

    &:focus-visible .graph-dot {
      stroke: var(--gold);
      stroke-width: 3;
    }

    &.focus .graph-dot {
      stroke: var(--gold);
      stroke-width: 3;
    }

    &.focus .graph-label {
      fill: var(--ink);
    }

    &.dim {
      opacity: var(--op-dim);
    }
  }

  /* ------------------------------------------------------- PROPERTIES
     Frontmatter / metadata as a key→value grid (Obsidian Properties). Values
     can hold .tag / .wikilink / plain text. Use a <dl> for real semantics. */
  .props {
    display: grid;
    grid-template-columns: auto 1fr;
    gap: var(--sp-1) var(--sp-4);
    font-size: var(--fs-body);

    &>dt {
      font-family: var(--font-mono);
      font-size: var(--fs-label);
      text-transform: uppercase;
      letter-spacing: var(--ls-chrome);
      color: var(--dim);
      align-self: center;
    }

    &>dd {
      margin: 0;
      display: flex;
      flex-wrap: wrap;
      align-items: center;
      gap: var(--sp-2);
      color: var(--text);
    }
  }

  /* ---------------------------------------------------------- OUTLINE
     The heading tree of the current note; indent by .lvl-N, light .active. */
  .outline {
    display: flex;
    flex-direction: column;
    font-family: var(--font-mono);
    font-size: var(--fs-label);

    &>a {
      display: block;
      padding: var(--pad-tight);
      color: var(--muted);
      border-inline-start: var(--bw-2) solid var(--line-hi);
      transition: color var(--dur-fast), border-color var(--dur-fast);
    }

    &>a:hover {
      color: var(--ink);
      border-inline-start-color: var(--primary);
    }

    &>a.active {
      color: var(--primary);
      border-inline-start-color: var(--primary);
    }

    &>a.lvl-2 {
      padding-inline-start: var(--sp-4);
    }

    &>a.lvl-3 {
      padding-inline-start: var(--sp-6);
      color: var(--dim);
    }
  }

  /* -------------------------------------------------------- HEATMAP
     Activity / contribution grid (notes per day). 7 rows = weekdays, columns
     flow as weeks; <i data-level="0–4"> sets the ink. Recolour via data-accent.
     Wrap in an overflow-x:auto div when there are many weeks. */
  .heatmap {
    --accent: var(--primary);
    --accent-d: var(--primary-d);
    display: grid;
    grid-auto-flow: column;
    grid-template-rows: repeat(7, 1fr);
    gap: var(--sp-hair);
    inline-size: max-content;

    &>i {
      inline-size: 12px;
      block-size: 12px;
      background: var(--slot);
      border: var(--bw-1) solid var(--line-hi);
    }

    &>i[data-level="1"] {
      background: color-mix(in srgb, var(--accent) 30%, var(--slot));
    }

    &>i[data-level="2"] {
      background: color-mix(in srgb, var(--accent) 55%, var(--slot));
    }

    &>i[data-level="3"] {
      background: color-mix(in srgb, var(--accent) 78%, var(--slot));
    }

    &>i[data-level="4"] {
      background: var(--accent);
      border-color: var(--accent-d);
    }
  }

  /* ----------------------------------------------------------- BOARD
     A Kanban board: horizontally-scrolling columns of cards. Give each column
     a data-accent for its top bar. Cards are draggable-looking (wire DnD in JS). */
  .board {
    display: flex;
    align-items: flex-start;
    gap: var(--sp-3);
    overflow-x: auto;
    padding-block-end: var(--sp-2);
  }

  .board-col {
    flex: 0 0 min(260px, 78vw);
    display: flex;
    flex-direction: column;
    gap: var(--sp-2);
    background: var(--slot);
    border: var(--bw-2) solid var(--line-hi);
    border-block-start: var(--accent-bar) solid var(--accent, var(--line-hi));
    padding: var(--sp-2);

    &>.board-head {
      display: flex;
      align-items: center;
      gap: var(--sp-2);
      padding: var(--pad-tight);
      font-family: var(--font-mono);
      font-size: var(--fs-label);
      text-transform: uppercase;
      letter-spacing: var(--ls-chrome);
      color: var(--muted);

      & .count {
        margin-inline-start: auto;
        color: var(--dim);
      }
    }
  }

  .board-card {
    background: var(--panel);
    border: var(--bw-2) solid var(--line-hi);
    box-shadow: var(--sh-2);
    padding: var(--sp-3);
    font-size: var(--fs-body);
    color: var(--text);
    cursor: grab;
    transition: border-color var(--dur-fast), transform var(--dur-fast) var(--ease);

    &:hover {
      border-color: var(--primary);
    }

    &:active {
      cursor: grabbing;
      transform: translate(1px, 1px);
      box-shadow: var(--sh-1);
    }
  }

  /* --------------------------------------------------------- PALETTE
     Command palette / quick-switcher: a search box over a .result list. Drop it
     in a <dialog class="modal"> for Cmd-K; wire the filtering in JS. */
  .palette {
    display: flex;
    flex-direction: column;
    background: var(--panel);
    border: var(--bw) solid var(--line-hi);
    box-shadow: var(--sh-5);

    & .palette-input {
      display: flex;
      align-items: center;
      gap: var(--sp-2);
      padding: var(--sp-3);
      border-block-end: var(--bw-2) solid var(--line-hi);
      color: var(--dim);

      & input {
        flex: 1;
        border: 0;
        outline: none;
        background: none;
        color: var(--ink);
        font-family: var(--font-body);
        font-size: var(--fs-h3);

        &::placeholder {
          color: var(--dim);
        }
      }
    }

    & .palette-list {
      display: flex;
      flex-direction: column;
      gap: var(--sp-hair);
      overflow-y: auto;
      padding: var(--sp-2);
    }

    & .palette-empty {
      padding: var(--sp-4);
      text-align: center;
      font-family: var(--font-mono);
      font-size: var(--fs-label);
      color: var(--dim);
    }
  }

  /* --------------------------------------------------------- RESULT
     One search hit / palette row: icon + title (with <mark>) over a dim path,
     an optional trailing hint. .active is the keyboard-selected row. */
  .result {
    display: flex;
    align-items: center;
    gap: var(--sp-3);
    padding: var(--pad-snug);
    background: none;
    border: var(--bw-2) solid transparent;
    text-align: start;
    text-decoration: none;
    color: var(--text);
    cursor: pointer;

    &:hover,
    &.active {
      background: var(--slot);
      border-color: var(--line-hi);
    }

    &.active {
      border-color: var(--primary);
    }

    & .result-icon {
      flex: none;
      color: var(--dim);
    }

    & .result-body {
      min-inline-size: 0;
      display: flex;
      flex-direction: column;
    }

    & .result-title {
      color: var(--ink);
      font-size: var(--fs-body);
      overflow: hidden;
      text-overflow: ellipsis;
      white-space: nowrap;

      & mark {
        color: var(--ink);
        background: color-mix(in srgb, var(--gold) 32%, transparent);
      }
    }

    & .result-path {
      font-family: var(--font-mono);
      font-size: var(--fs-label);
      color: var(--dim);
      overflow: hidden;
      text-overflow: ellipsis;
      white-space: nowrap;
    }

    & .result-hint {
      margin-inline-start: auto;
      flex: none;
    }
  }

  /* ---------------------------------------------------------- EMBED
     A transcluded note (Obsidian ![[note]]): a titled, accent-barred quote of
     another note's content inside this one. */
  .embed {
    --accent: var(--primary);
    background: color-mix(in srgb, var(--panel) 55%, transparent);
    border: var(--bw-2) solid var(--line-hi);
    border-inline-start: var(--accent-bar) solid var(--accent);
    padding: var(--pad-box);

    &>.embed-head {
      display: flex;
      align-items: center;
      gap: var(--sp-2);
      margin-block-end: var(--sp-2);
      font-family: var(--font-mono);
      font-size: var(--fs-label);
      text-transform: uppercase;
      letter-spacing: var(--ls-chrome);
      color: var(--dim);

      & a {
        color: var(--indigo);
        text-decoration: none;
      }
    }

    &>:last-child {
      margin-block-end: 0;
    }
  }

  /* -------------------------------------------------------- MATURITY
     Digital-garden growth stage: 🌱 seedling · 🌿 budding · 🌳 evergreen. */
  .maturity {
    display: inline-flex;
    align-items: center;
    gap: var(--sp-2);
    font-family: var(--font-mono);
    font-size: var(--fs-label);
    text-transform: uppercase;
    letter-spacing: var(--ls-chrome);
    color: var(--muted);
    background: var(--slot);
    border: var(--bw-2) solid var(--line-hi);
    padding: var(--sp-hair) var(--sp-2);

    &::before {
      content: "🌱";
    }

    &.budding {
      color: var(--lime);
      border-color: var(--lime-d);

      &::before {
        content: "🌿";
      }
    }

    &.evergreen {
      color: var(--good);
      border-color: var(--good-d);

      &::before {
        content: "🌳";
      }
    }
  }

  /* ------------------------------------------------------- TAG CLOUD
     A weighted panel of .tag pills; data-weight scales the hottest tags up. */
  .tag-cloud {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: var(--sp-2);

    & .tag .tag-n {
      margin-inline-start: var(--sp-1);
      color: var(--dim);
    }

    & .tag[data-weight="2"] {
      font-size: var(--fs-chip);
      color: var(--muted);
    }

    & .tag[data-weight="3"] {
      font-size: var(--fs-body);
      color: var(--primary);
      border-color: var(--primary);
    }
  }

  /* --------------------------------------------------------- CONCEPT
     A glossary / concept card: term + definition + "see also" links. Cyan bar
     by convention (a reference), recolour with data-accent. */
  .concept {
    --accent: var(--cyan);
    background: var(--panel);
    border: var(--bw-2) solid var(--line-hi);
    border-inline-start: var(--accent-bar) solid var(--accent);
    box-shadow: var(--sh-3);
    padding: var(--pad-box);

    &>.concept-term {
      margin: 0 0 var(--sp-2);
      font-family: var(--font-mono);
      font-size: var(--fs-h3);
      font-weight: var(--fw-bold);
      text-transform: uppercase;
      letter-spacing: var(--ls-chrome);
      color: var(--ink);
      font-style: normal;
    }

    &>.concept-def {
      margin: 0;
      color: var(--muted);
      font-size: var(--fs-body);
    }

    &>.concept-see {
      display: flex;
      flex-wrap: wrap;
      align-items: center;
      gap: var(--sp-2);
      margin-block-start: var(--sp-2);
      font-family: var(--font-mono);
      font-size: var(--fs-label);
      color: var(--dim);
    }
  }

  /* ------------------------------------------------------ NOTE STATS
     A meta bar for a note: words · read time · created · modified. */
  .note-stats {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: var(--sp-3);
    font-family: var(--font-mono);
    font-size: var(--fs-label);
    color: var(--dim);

    &>span {
      display: inline-flex;
      align-items: center;
      gap: var(--sp-1);
    }

    & nes-icon {
      color: var(--muted);
    }
  }

  /* ======================================================================
     OPENCODE · the vibe-coding workspace (cloud IDE / coding-agent WebUI).
     The shell around an agent that writes code: where am I (workbench,
     repobar, filetabs) · is my machine alive (sandbox, statusline) · what
     will it do (plan) · may it (perm) · what changed (diffstat, filechange,
     hunk, <nes-diff>) · does it work (checks, runbar, <nes-logs>,
     stacktrace, <nes-preview>) · can I undo / ship it (ckpt, deploy).
     Lego rule: each recipe owns ONE responsibility and composes with what
     already exists — .diff, .terminal, .tasklist, .tree, .trace, .msg,
     .btn, .input, .segment — instead of re-implementing it. Three pieces
     hold state (<nes-diff>, <nes-logs>, <nes-preview>); the rest is CSS an
     agent can emit straight as HTML.
     ====================================================================== */

  /* --------------------------------------------------------------- WORKBENCH
     The 3-pane workspace shell: rail (files) · main (chat/editor) · side
     (preview/logs). Layout ONLY — it styles no content, so any module drops
     in. Mobile-first: panes stack and cap their height; from 56rem they sit
     side by side. Drop .wb-rail or .wb-side and the grid re-flows itself. */
  .workbench {
    --wb-rail: var(--rail-w);
    --wb-side: 22rem;
    --wb-h: 30rem;
    display: grid;
    grid-template-columns: minmax(0, 1fr);
    inline-size: 100%;
    background: var(--screen);
    border: var(--bw-2) solid var(--line-hi);
    box-shadow: var(--sh-4);
    overflow: hidden;

    &>* {
      min-inline-size: 0;
      overflow: auto;
    }

    &>.wb-rail,
    &>.wb-side {
      max-block-size: 12rem;
      background: var(--slot);
      border-block-end: var(--bw-2) solid var(--line-hi);
    }

    &>.wb-main {
      display: flex;
      flex-direction: column;
      min-block-size: 0;
    }

    @media (min-width: 56rem) {
      grid-template-columns: var(--wb-rail) minmax(0, 1fr) var(--wb-side);
      block-size: var(--wb-h);

      &:not(:has(>.wb-side)) {
        grid-template-columns: var(--wb-rail) minmax(0, 1fr);
      }

      &:not(:has(>.wb-rail)) {
        grid-template-columns: minmax(0, 1fr) var(--wb-side);
      }

      &:not(:has(>.wb-rail)):not(:has(>.wb-side)) {
        grid-template-columns: minmax(0, 1fr);
      }

      &>.wb-rail,
      &>.wb-side {
        max-block-size: none;
        border-block-end: 0;
      }

      &>.wb-rail {
        border-inline-end: var(--bw-2) solid var(--line-hi);
      }

      &>.wb-side {
        border-inline-start: var(--bw-2) solid var(--line-hi);
      }
    }
  }

  /* ---------------------------------------------------------------- REPO BAR
     "Which code am I looking at": repo · branch · dirty count + an actions
     slot. Identity only — live run telemetry belongs in .statusline. */
  .repobar {
    --accent: var(--primary);
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: var(--sp-2) var(--sp-3);
    inline-size: 100%;
    min-block-size: var(--ctrl-h);
    padding: var(--pad-snug);
    background: var(--panel-2);
    border: var(--bw-2) solid var(--line-hi);
    font-family: var(--font-mono);
    font-size: var(--fs-chip);
    color: var(--muted);

    & .repo-name {
      display: inline-flex;
      align-items: center;
      gap: var(--sp-2);
      font-weight: var(--fw-bold);
      color: var(--ink);
    }

    & .repo-branch {
      display: inline-flex;
      align-items: center;
      gap: var(--sp-2);
      min-inline-size: 0;
      color: var(--accent);
    }

    /* uncommitted work — gold because it's a "you have unsaved state" nudge */
    & .repo-dirty {
      padding-inline: var(--sp-2);
      background: color-mix(in srgb, var(--gold) 18%, transparent);
      border: var(--bw-1) solid var(--gold);
      font-size: var(--fs-label);
      letter-spacing: var(--ls-chrome);
      color: var(--gold);
    }

    & .repo-actions {
      display: flex;
      align-items: center;
      gap: var(--sp-2);
      margin-inline-start: auto;
    }
  }

  /* ------------------------------------------------------------- STATUS LINE
     The ambient bottom strip: run-state light + model · tokens · elapsed.
     Reads at a glance without stealing focus. data-state uses the shared
     run-state vocabulary; .sl-end pushes the trailing items right. */
  .statusline {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: var(--sp-2) var(--sp-4);
    inline-size: 100%;
    min-block-size: var(--ctrl-h-xs);
    padding: var(--sp-1) var(--sp-3);
    background: var(--panel);
    border-block-start: var(--bw-2) solid var(--line-hi);
    font-family: var(--font-mono);
    font-size: var(--fs-label);
    letter-spacing: var(--ls-chrome);
    color: var(--dim);

    & .sl-state {
      display: inline-flex;
      align-items: center;
      gap: var(--sp-2);
      font-weight: var(--fw-bold);
      color: var(--ink);

      &::before {
        content: "";
        inline-size: .5rem;
        block-size: .5rem;
        background: var(--state, var(--dim));
        border: var(--bw-1) solid var(--line);
      }
    }

    &[data-state="thinking"] .sl-state::before,
    &[data-state="running"] .sl-state::before {
      animation: blink 1.1s steps(2) infinite;
    }

    & .sl-item {
      display: inline-flex;
      align-items: center;
      gap: var(--sp-1);
      white-space: nowrap;
    }

    & .sl-end {
      display: inline-flex;
      align-items: center;
      gap: var(--sp-4);
      margin-inline-start: auto;
    }
  }

  /* ----------------------------------------------------------------- SANDBOX
     The cloud dev container: is my machine alive, and what is it? State light
     + spec readout + an actions slot. Vocabulary mapping: thinking = booting,
     running = live, queued = stopped/idle, error = crashed. */
  .sandbox {
    --accent: var(--primary);
    display: grid;
    grid-template-columns: auto 1fr;
    column-gap: var(--sp-3);
    align-items: start;
    inline-size: 100%;
    padding: var(--pad-box);
    background: var(--panel);
    border: var(--bw-2) solid var(--line-hi);
    border-inline-start: var(--accent-bar) solid var(--state, var(--line-hi));
    box-shadow: var(--sh-3);

    &::before {
      content: "";
      grid-row: 1 / span 2;
      margin-block-start: var(--sp-1);
      inline-size: var(--dot);
      block-size: var(--dot);
      background: var(--state, var(--dim));
      border: var(--bw-1) solid var(--line);
      box-shadow: var(--sh-1);
    }

    &[data-state="thinking"]::before,
    &[data-state="running"]::before {
      animation: blink 1.1s steps(2) infinite;
    }

    & .sb-head {
      grid-column: 2;
      display: flex;
      flex-wrap: wrap;
      align-items: baseline;
      gap: var(--sp-2) var(--sp-3);
      font-family: var(--font-mono);
    }

    & .sb-name {
      font-size: var(--fs-chip);
      font-weight: var(--fw-bold);
      text-transform: uppercase;
      letter-spacing: var(--ls-chrome);
      color: var(--ink);
    }

    & .sb-actions {
      display: flex;
      gap: var(--sp-2);
      margin-inline-start: auto;
    }

    & .sb-specs {
      grid-column: 2;
      display: flex;
      flex-wrap: wrap;
      gap: var(--sp-1) var(--sp-4);
      margin-block-start: var(--sp-2);
      font-family: var(--font-mono);
      font-size: var(--fs-label);
      color: var(--dim);

      &>span {
        display: inline-flex;
        gap: var(--sp-1);
      }

      & b {
        font-weight: var(--fw-regular);
        color: var(--muted);
      }
    }
  }

  /* -------------------------------------------------------------------- PLAN
     What the agent INTENDS to do, with live state per step — the todo list
     you watch tick over. Different jobs, on purpose: .tasklist is a static
     done/open checklist, .trace is what already happened (with tool I/O),
     .plan is the forward-looking queue. Marks share the run-state colours. */
  .plan {
    counter-reset: plan;
    list-style: none;
    margin: 0;
    padding: 0;
    inline-size: 100%;
    font-family: var(--font-mono);

    &>.plan-step {
      counter-increment: plan;
      display: grid;
      grid-template-columns: auto minmax(0, 1fr);
      column-gap: var(--sp-3);
      align-items: start;
      padding-block: var(--sp-2);
      font-size: var(--fs-chip);
      color: var(--text);
    }

    &>.plan-step+.plan-step {
      border-block-start: var(--bw-1) solid var(--line-hi);
    }

    /* the step mark: its number in a square tinted + ringed by the run state.
       A soft mix (not a solid fill) keeps the numeral legible at every state. */
    &>.plan-step::before {
      content: counter(plan);
      display: flex;
      align-items: center;
      justify-content: center;
      inline-size: 1.25rem;
      block-size: 1.25rem;
      background: color-mix(in srgb, var(--state, var(--panel-2)) 26%, var(--slot));
      border: var(--bw-2) solid var(--state, var(--line-hi));
      box-shadow: var(--sh-1);
      font-size: var(--fs-label);
      font-weight: var(--fw-bold);
      color: var(--ink);
    }

    &>.plan-step[data-state="thinking"]::before,
    &>.plan-step[data-state="running"]::before {
      animation: blink 1.1s steps(2) infinite;
    }

    /* a finished step recedes — the eye should land on what's live */
    &>.plan-step[data-state="done"] {
      color: var(--muted);
    }

    & .plan-note {
      grid-column: 2;
      margin-block-start: var(--sp-1);
      font-size: var(--fs-label);
      color: var(--dim);
    }
  }

  /* -------------------------------------------------------------- PERMISSION
     The gate: "the agent wants to run this — allow?" One request, one
     decision. Warn accent by default because it blocks the loop. Add
     .decided after the user answers: the record stays, the buttons go. */
  .perm {
    --accent: var(--warn);
    display: flex;
    flex-direction: column;
    gap: var(--sp-2);
    inline-size: 100%;
    padding: var(--pad-box);
    background: var(--panel);
    border: var(--bw-2) solid var(--line-hi);
    border-inline-start: var(--accent-bar) solid var(--accent);
    box-shadow: var(--sh-3);

    & .perm-kind {
      align-self: flex-start;
      padding-inline: var(--sp-2);
      background: var(--accent);
      border: var(--bw-1) solid var(--line);
      font-family: var(--font-mono);
      font-size: var(--fs-label);
      font-weight: var(--fw-bold);
      text-transform: uppercase;
      letter-spacing: var(--ls-chrome);
      color: var(--ink-on-accent);
    }

    /* exactly what will run — verbatim, monospaced, never truncated */
    & .perm-target {
      display: block;
      overflow-x: auto;
      padding: var(--pad-snug);
      background: var(--slot);
      border: var(--bw-1) solid var(--line-hi);
      font-family: var(--font-mono);
      font-size: var(--fs-body);
      white-space: pre;
      color: var(--ink);
    }

    & .perm-why {
      margin: 0;
      font-size: var(--fs-body);
      color: var(--muted);
    }

    & .perm-actions {
      display: flex;
      flex-wrap: wrap;
      gap: var(--sp-2);
      margin-block-start: var(--sp-1);
    }

    &.decided {
      opacity: var(--op-disabled);

      & .perm-actions {
        display: none;
      }
    }
  }

  /* --------------------------------------------------------------- DIFF STAT
     The aggregate of a change set: N files, +added / −removed, and a
     proportional bar. Pair with .filechange rows for the per-file list.
     Bar slices size from --seg, same contract as .usage-bar. */
  .diffstat {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: var(--sp-2) var(--sp-3);
    font-family: var(--font-mono);
    font-size: var(--fs-chip);
    color: var(--muted);

    & .ds-files {
      font-weight: var(--fw-bold);
      color: var(--ink);
    }

    & .ds-add {
      color: var(--good);
    }

    & .ds-del {
      color: var(--crit);
    }

    & .ds-bar {
      display: flex;
      inline-size: 5rem;
      block-size: var(--dot);
      background: var(--slot);
      border: var(--bw-1) solid var(--line-hi);

      &>i {
        flex: 0 0 var(--seg, 0%);
      }

      &>i.add {
        background: var(--good);
      }

      &>i.del {
        background: var(--crit);
      }
    }
  }

  /* ------------------------------------------------------------- FILE CHANGE
     One changed file: status mark (A/M/D/R) · path · ± counts. Render it as
     an <a> or <button> so it's focusable and jumps to that file's diff. */
  .filechange {
    display: grid;
    grid-template-columns: auto minmax(0, 1fr) auto;
    align-items: center;
    gap: var(--sp-3);
    inline-size: 100%;
    padding: var(--pad-snug);
    background: var(--panel);
    border: var(--bw-1) solid var(--line-hi);
    font-family: var(--font-mono);
    font-size: var(--fs-chip);
    text-align: start;
    text-decoration: none;
    color: var(--text);
    transition: background var(--dur-fast) var(--ease);

    &:hover {
      background: var(--panel-2);
    }

    & .fc-mark {
      display: flex;
      align-items: center;
      justify-content: center;
      inline-size: 18px;
      block-size: 18px;
      background: color-mix(in srgb, var(--mark, var(--steel)) 26%, var(--slot));
      border: var(--bw-1) solid var(--mark, var(--steel));
      font-size: var(--fs-label);
      font-weight: var(--fw-bold);
      color: var(--ink);
    }

    & .fc-path {
      overflow: hidden;
      text-overflow: ellipsis;
      white-space: nowrap;
      color: var(--ink);
    }

    /* the directory recedes so the filename — the thing you scan for — pops */
    & .fc-dir {
      color: var(--dim);
    }

    & .fc-count {
      display: inline-flex;
      gap: var(--sp-2);
      font-size: var(--fs-label);

      & .add {
        color: var(--good);
      }

      & .del {
        color: var(--crit);
      }
    }
  }

  /* git status letter → colour. Added is good, modified is a nudge, deleted
     is destructive, renamed/moved is neutral-informational. */
  .filechange[data-change="A"] {
    --mark: var(--good);
  }

  .filechange[data-change="M"] {
    --mark: var(--gold);
  }

  .filechange[data-change="D"] {
    --mark: var(--crit);
  }

  .filechange[data-change="R"] {
    --mark: var(--blue);
  }

  /* -------------------------------------------------------------------- HUNK
     A reviewable slice of a diff: range + scope + keep/revert actions.
     Works as a plain <div> (always open) or a <details> (collapsible, zero
     JS). The body stays a plain .diff — this recipe owns only the review
     chrome around it, so <nes-diff> or hand-written diff HTML both fit. */
  .hunk {
    inline-size: 100%;
    background: var(--panel);
    border: var(--bw-2) solid var(--line-hi);
    box-shadow: var(--sh-2);

    &>.hunk-head {
      display: flex;
      flex-wrap: wrap;
      align-items: center;
      gap: var(--sp-2) var(--sp-3);
      padding: var(--pad-snug);
      background: var(--panel-2);
      font-family: var(--font-mono);
      font-size: var(--fs-label);
      list-style: none;
    }

    /* only a <summary> head is clickable; a <div> head must not look it */
    &>summary.hunk-head {
      cursor: pointer;

      &::-webkit-details-marker {
        display: none;
      }
    }

    & .hunk-range {
      color: var(--cyan);
    }

    & .hunk-scope {
      color: var(--dim);
    }

    & .hunk-actions {
      display: flex;
      gap: var(--sp-2);
      margin-inline-start: auto;
    }

    /* the embedded diff sheds its own frame — the hunk is the frame */
    &>.diff,
    &>nes-diff .diff {
      border: 0;
      border-block-start: var(--bw-2) solid var(--line-hi);
    }
  }

  /* ------------------------------------------------------------------ CHECKS
     Did it actually work: lint / types / unit / build — one row each, with
     the shared run-state vocabulary and an optional link to the logs. */
  .checks {
    list-style: none;
    margin: 0;
    padding: 0;
    inline-size: 100%;
    font-family: var(--font-mono);

    &>.check-item {
      display: flex;
      flex-wrap: wrap;
      align-items: center;
      gap: var(--sp-2) var(--sp-3);
      padding: var(--pad-snug);
      background: var(--panel);
      border: var(--bw-1) solid var(--line-hi);
      font-size: var(--fs-chip);
      color: var(--text);

      &::before {
        content: "";
        flex: none;
        inline-size: var(--pip);
        block-size: var(--pip);
        background: var(--state, var(--dim));
        border: var(--bw-1) solid var(--line);
        box-shadow: var(--sh-1);
      }
    }

    /* collapse the seam between neighbours into one line */
    &>.check-item+.check-item {
      border-block-start: 0;
    }

    &>.check-item[data-state="thinking"]::before,
    &>.check-item[data-state="running"]::before {
      animation: blink 1.1s steps(2) infinite;
    }

    & .chk-name {
      font-weight: var(--fw-bold);
      color: var(--ink);
    }

    & .chk-meta {
      font-size: var(--fs-label);
      color: var(--dim);
    }

    & .chk-link {
      margin-inline-start: auto;
      font-size: var(--fs-label);
      color: var(--cyan);
    }
  }

  /* ----------------------------------------------------------------- RUN BAR
     The process strip: stop/start, the command, the port it bound, and how
     long it took. One running task per bar. */
  .runbar {
    --accent: var(--primary);
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: var(--sp-2) var(--sp-3);
    inline-size: 100%;
    min-block-size: var(--ctrl-h);
    padding: var(--pad-snug);
    background: var(--slot);
    border: var(--bw-2) solid var(--line-hi);
    font-family: var(--font-mono);
    font-size: var(--fs-chip);
    color: var(--muted);

    &::before {
      content: "";
      flex: none;
      inline-size: var(--dot);
      block-size: var(--dot);
      background: var(--state, var(--dim));
      border: var(--bw-1) solid var(--line);
    }

    &[data-state="thinking"]::before,
    &[data-state="running"]::before {
      animation: blink 1.1s steps(2) infinite;
    }

    & .run-cmd {
      font-weight: var(--fw-bold);
      color: var(--ink);
    }

    & .run-url {
      color: var(--accent);
    }

    & .run-meta {
      margin-inline-start: auto;
      font-size: var(--fs-label);
      color: var(--dim);
    }
  }

  /* -------------------------------------------------------------------- LOGS
     A streaming log surface (<nes-logs> puts .logs on itself): one .logline
     per line, tinted by data-level. Sticks to the tail while it streams
     unless you scroll up. For a finished transcript reuse .terminal. */
  .logs {
    display: block;
    inline-size: 100%;
    max-block-size: var(--logs-h, 14rem);
    overflow: auto;
    padding: var(--pad-snug);
    background: var(--slot);
    border: var(--bw-2) solid var(--line-hi);
    font-family: var(--font-mono);
    font-size: var(--fs-body);
    line-height: var(--lh-body);
    color: var(--muted);
  }

  .logline {
    display: block;
    white-space: pre-wrap;
    overflow-wrap: anywhere;

    /* an author `display: block` outranks the UA's [hidden] rule, so the level
       filter would silently do nothing without this. Keep them together. */
    &[hidden] {
      display: none;
    }

    &[data-level="debug"] {
      color: var(--dim);
    }

    &[data-level="warn"] {
      color: var(--warn);
    }

    &[data-level="error"] {
      color: var(--crit);
    }

    &[data-level="done"] {
      color: var(--good);
    }
  }

  /* ------------------------------------------------------------ STACK TRACE
     The failure, readable: kind + message, then frames with YOUR code lit
     and vendor frames dimmed, plus the action slot that starts the next
     agent loop ("fix this"). Not a .callout — a callout is a message, this
     is structured evidence you click into. */
  .stacktrace {
    --accent: var(--crit);
    display: flex;
    flex-direction: column;
    gap: var(--sp-2);
    inline-size: 100%;
    padding: var(--pad-box);
    background: var(--panel);
    border: var(--bw-2) solid var(--line-hi);
    border-inline-start: var(--accent-bar) solid var(--accent);
    box-shadow: var(--sh-3);

    & .st-head {
      display: flex;
      flex-wrap: wrap;
      align-items: baseline;
      gap: var(--sp-2) var(--sp-3);
      font-family: var(--font-mono);
    }

    & .st-kind {
      font-size: var(--fs-chip);
      font-weight: var(--fw-bold);
      text-transform: uppercase;
      letter-spacing: var(--ls-chrome);
      color: var(--accent);
    }

    & .st-msg {
      font-family: var(--font-body);
      font-size: var(--fs-body);
      color: var(--ink);
    }

    & .st-frames {
      list-style: none;
      margin: 0;
      padding: 0;
      overflow-x: auto;
      background: var(--slot);
      border: var(--bw-1) solid var(--line-hi);
      font-family: var(--font-mono);
      font-size: var(--fs-label);
    }

    & .st-frame {
      display: flex;
      gap: var(--sp-3);
      padding: var(--pad-tight);
      white-space: nowrap;
      color: var(--dim);
    }

    /* your code vs node_modules — the whole point of reading a trace */
    & .st-frame.app {
      background: color-mix(in srgb, var(--crit) 10%, transparent);
      color: var(--text);
    }

    & .st-fn {
      color: var(--muted);
    }

    & .st-frame.app .st-fn {
      color: var(--ink);
    }

    & .st-actions {
      display: flex;
      flex-wrap: wrap;
      gap: var(--sp-2);
    }
  }

  /* ------------------------------------------------------------ CHECKPOINTS
     Time travel: every snapshot you can rewind to. .timeline narrates
     history; .ckpt lets you GO there — each row owns a restore action, and
     .current marks where you are now. */
  .ckpt {
    --accent: var(--primary);
    list-style: none;
    margin: 0;
    padding: 0;
    inline-size: 100%;
    font-family: var(--font-mono);

    &>.ckpt-item {
      position: relative;
      display: flex;
      flex-wrap: wrap;
      align-items: center;
      gap: var(--sp-2) var(--sp-3);
      padding-block: var(--sp-2);
      padding-inline: var(--sp-5) var(--sp-3);
      border-inline-start: var(--bw-2) solid var(--line-hi);
      font-size: var(--fs-chip);
      color: var(--muted);

      &::before {
        content: "";
        position: absolute;
        inset-block-start: 50%;
        inset-inline-start: calc((var(--pip) + var(--bw-2)) / -2);
        translate: 0 -50%;
        inline-size: var(--pip);
        block-size: var(--pip);
        background: var(--slot);
        border: var(--bw-2) solid var(--line-hi);
      }
    }

    /* the last rail segment stops at its own marker instead of dangling */
    &>.ckpt-item:last-child {
      border-inline-start-color: transparent;
    }

    &>.ckpt-item.current {
      color: var(--ink);

      &::before {
        background: var(--accent);
        border-color: var(--accent);
      }
    }

    & .cp-time {
      flex: none;
      font-size: var(--fs-label);
      color: var(--dim);
    }

    & .cp-label {
      min-inline-size: 0;
      overflow: hidden;
      text-overflow: ellipsis;
      white-space: nowrap;
    }

    & .cp-actions {
      margin-inline-start: auto;
    }
  }

  /* ------------------------------------------------------------------ DEPLOY
     Where the code actually went: environment · URL · duration + commit.
     One row per environment (preview / staging / production). */
  .deploy {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: var(--sp-2) var(--sp-3);
    inline-size: 100%;
    min-block-size: var(--ctrl-h);
    padding: var(--pad-snug);
    background: var(--panel);
    border: var(--bw-2) solid var(--line-hi);
    border-inline-start: var(--accent-bar) solid var(--state, var(--line-hi));
    box-shadow: var(--sh-2);
    font-family: var(--font-mono);
    font-size: var(--fs-chip);
    color: var(--muted);

    & .dp-env {
      font-size: var(--fs-label);
      font-weight: var(--fw-bold);
      text-transform: uppercase;
      letter-spacing: var(--ls-chrome);
      color: var(--ink);
    }

    & .dp-url {
      min-inline-size: 0;
      overflow: hidden;
      text-overflow: ellipsis;
      white-space: nowrap;
      color: var(--cyan);
    }

    & .dp-meta {
      margin-inline-start: auto;
      font-size: var(--fs-label);
      color: var(--dim);
    }
  }

  /* --------------------------------------------------------------- FILE TABS
     The open-buffer strip: which files are open, which is focused, which is
     unsaved. <nes-tabs> switches panels of content; .filetabs switches the
     document inside ONE pane — different job, so a different recipe. */
  .filetabs {
    --accent: var(--primary);
    display: flex;
    inline-size: 100%;
    overflow-x: auto;
    background: var(--panel-2);
    border-block-end: var(--bw-2) solid var(--line-hi);
    scrollbar-width: thin;

    & .filetab {
      display: inline-flex;
      flex: none;
      align-items: center;
      gap: var(--sp-2);
      min-block-size: var(--ctrl-h-sm);
      padding-inline: var(--sp-3);
      background: var(--slot);
      border-inline-end: var(--bw-1) solid var(--line-hi);
      border-block-end: var(--bw-2) solid transparent;
      font-family: var(--font-mono);
      font-size: var(--fs-label);
      color: var(--dim);
    }

    & .filetab.active {
      background: var(--screen);
      border-block-end-color: var(--accent);
      color: var(--ink);
    }

    /* the name and the close × are real buttons, stripped to inherit the tab */
    & .ft-name,
    & .ft-close {
      padding: 0;
      background: none;
      border: 0;
      font: inherit;
      color: inherit;
      cursor: pointer;
    }

    & .ft-close {
      line-height: var(--lh-none);
      color: var(--dim);

      &:hover {
        color: var(--crit);
      }
    }

    /* unsaved marker — gold, same "you have pending state" language as
       .repo-dirty, so one glance reads the same across the workspace */
    & .ft-dirty {
      flex: none;
      inline-size: 6px;
      block-size: 6px;
      background: var(--gold);
    }
  }

  /* ----------------------------------------------------------- APP PREVIEW
     <nes-preview>: the running app in a framed viewport — URL bar, reload,
     and mobile/tablet/desktop widths. Named .appview (not .preview) to stay
     clear of .code-preview's demo pane. The bar composes existing recipes
     (.input, .btn, .segment) — this only frames and lays out. */
  .appview {
    --accent: var(--primary);
    display: flex;
    flex-direction: column;
    inline-size: 100%;
    background: var(--screen);
    border: var(--bw-2) solid var(--line-hi);
    box-shadow: var(--sh-4);

    & .av-bar {
      display: flex;
      flex-wrap: wrap;
      align-items: center;
      gap: var(--sp-2);
      padding: var(--sp-2);
      background: var(--panel-2);
      border-block-end: var(--bw-2) solid var(--line-hi);
    }

    & .av-url {
      flex: 1 1 8rem;
      min-inline-size: 0;
    }

    & .av-stage {
      display: flex;
      justify-content: center;
      overflow: auto;
      padding: var(--sp-3);
      background: var(--bg);
    }

    & iframe {
      inline-size: var(--av-w, 100%);
      max-inline-size: 100%;
      block-size: var(--av-h, 22rem);
      background: var(--screen);
      border: var(--bw-2) solid var(--line);
      box-shadow: var(--sh-3);
    }
  }

  /* viewport presets — real device widths, so a mobile bug reproduces */
  .appview[data-view="mobile"] {
    --av-w: 375px;
  }

  .appview[data-view="tablet"] {
    --av-w: 768px;
  }

  .appview[data-view="desktop"] {
    --av-w: 100%;
  }

  /* ======================================================================
     MAP OF CONTENT · <nes-toc> — the live "on this page" index.
     Mobile-first by construction: the DEFAULT shape is a collapsible sticky
     bar that names the section you are in (one tap for the list, never in
     the way). It becomes an open sticky rail only when there is room —
     <nes-toc> sets data-rail itself from rail-at (default 74rem), so there
     is ONE rail block here instead of a media query duplicated per mode.
     The list body is the existing .outline recipe: indent, active state and
     44px touch rows already live there, so this adds only the frame, the
     bar and the sticky behaviour. Offsets come from --toc-top (how far
     below the page chrome it sticks) and --toc-offset (how far a jumped-to
     heading clears that chrome).
     ====================================================================== */
  nes-toc {
    --accent: var(--primary);
    /* a block already fills its container, and no `inline-size: 100%` means a
       page can inset it with plain margins without overflowing the column */
    display: block;
    position: sticky;
    inset-block-start: var(--toc-top, 0px);
    z-index: var(--z-sticky);
    margin-block-end: var(--sp-5);
    background: var(--panel);
    border: var(--bw-2) solid var(--line-hi);

    /* fewer headings than `min` → the component hides itself, so a page that
       doesn't need an index costs the consumer no conditional rendering */
    &[hidden] {
      display: none;
    }

    /* the collapsed bar: label · current section · caret */
    & .toc-bar {
      display: flex;
      align-items: center;
      gap: var(--sp-2);
      inline-size: 100%;
      min-block-size: var(--ctrl-h-lg);
      padding-inline: var(--sp-4);
      background: none;
      border: 0;
      cursor: pointer;
      font-family: var(--font-mono);
      font-size: var(--fs-label);
      text-transform: uppercase;
      letter-spacing: var(--ls-chrome);
      text-align: start;
      color: var(--accent);
    }

    /* which section you are in — the reason a collapsed bar is worth reading */
    & .toc-now {
      min-inline-size: 0;
      overflow: hidden;
      text-overflow: ellipsis;
      white-space: nowrap;
      text-transform: none;
      letter-spacing: normal;
      color: var(--muted);
    }

    & .toc-caret {
      margin-inline-start: auto;
      transition: rotate var(--dur-fast) var(--ease);
    }

    &[data-open] .toc-caret {
      rotate: 180deg;
    }

    /* the rail's heading — hidden while the bar carries the label */
    & .toc-lab {
      display: none;
      font-family: var(--font-mono);
      font-size: var(--fs-label);
      text-transform: uppercase;
      letter-spacing: var(--ls-chrome);
      color: var(--accent);
    }

    /* closed by default: capped at half the viewport so an open index can
       never bury the content it indexes */
    & .outline {
      display: none;
      max-block-size: 50dvh;
      overflow-y: auto;
      padding: 0 var(--sp-3) var(--sp-3);
      border-block-start: var(--bw-2) solid var(--line-hi);
    }

    &[data-open] .outline {
      display: flex;
    }
  }

  /* the rail shape — one block, applied when <nes-toc> decides there's room */
  nes-toc[data-rail] {
    block-size: calc(100dvh - var(--toc-top, 0px));
    overflow-y: auto;
    margin-block-end: 0;
    padding: var(--sp-5) var(--sp-4) var(--sp-7) 0;
    background: none;
    border: 0;

    & .toc-bar {
      display: none;
    }

    & .toc-lab {
      display: block;
      margin-block-end: var(--sp-3);
    }

    & .outline {
      display: flex;
      max-block-size: none;
      overflow: visible;
      padding: 0;
      border-block-start: 0;
    }
  }

  /* ===================================================================== */
  /*  WAVE 8 — the primitives an app kept hand-rolling: a toolbar, an anchored */
  /*  panel that nothing can clip, and a split view you can drag.            */
  /* ===================================================================== */

  /* ----------------------------------------------------------------- TOOLBAR */
  /* A row of actions. On a phone it SCROLLS rather than wrapping into a ragged
     block — a toolbar that reflows moves the button you were aiming at.
     `.wrap` opts into wrapping when the actions are equals, not a sequence.
     (.editor-toolbar is the editor's own tuned variant of the same idea.)      */
  .toolbar {
    display: flex;
    align-items: center;
    gap: var(--sp-2);
    padding: var(--pad-snug);
    background: var(--panel);
    border: var(--bw-2) solid var(--line-hi);
    overflow-x: auto;
    overscroll-behavior-x: contain;
    scrollbar-width: none;

    &::-webkit-scrollbar {
      display: none;
    }

    /* a control in a toolbar keeps its size; the row scrolls instead */
    &>* {
      flex: none;
    }

    &.wrap {
      flex-wrap: wrap;
      overflow: visible;
    }

    /* everything after this sits at the far end */
    & .toolbar-gap {
      flex: 1 1 auto;
      min-inline-size: var(--sp-2);
    }

    & .toolbar-sep {
      align-self: stretch;
      inline-size: var(--bw-2);
      margin-inline: var(--sp-1);
      background: var(--line-hi);
    }
  }

  /* ----------------------------------------------------------------- POPOVER */
  /* An anchored panel on the native popover API: it lives in the top layer, so
     unlike .menu (absolute, inside .dropdown) NO ancestor overflow can clip it —
     the reason a dropdown inside a scrolling pane always looked cut off. Esc and
     click-outside come free from popover="auto"; <nes-popover> only places it. */
  .popover {
    position: fixed;
    margin: 0;
    inline-size: max-content;
    max-inline-size: min(92vw, 22rem);
    padding: var(--pad-box);
    color: var(--text);
    background: var(--panel);
    border: var(--bw-2) solid var(--line-hi);
    box-shadow: var(--sh-4);

    /* light-dismiss handles the click-away, so the backdrop stays invisible */
    &::backdrop {
      background: none;
    }

    &:popover-open {
      animation: pop-in var(--dur-fast) var(--ease);
    }

    & .popover-title {
      display: block;
      margin-block-end: var(--sp-2);
      font-family: var(--font-mono);
      font-size: var(--fs-label);
      font-weight: var(--fw-bold);
      text-transform: uppercase;
      letter-spacing: var(--ls-chrome);
      color: var(--accent, var(--primary));
    }
  }

  @keyframes pop-in {
    from {
      opacity: 0;
      transform: translateY(-4px);
    }
  }

  /* ------------------------------------------------------------------- SPLIT */
  /* Two panes and a divider you can drag, arrow, or double-click to reset.
     --split is the first pane's size; the divider is a real separator so a
     keyboard can move it. Mobile-first: `dir="column"` stacks, and the divider
     gets a --tap-wide hit area on a coarse pointer without getting fatter.      */
  nes-split {
    --split: 50%;
    display: grid;
    grid-template-columns: var(--split) auto minmax(0, 1fr);
    inline-size: 100%;
    background: var(--slot);
    border: var(--bw-2) solid var(--line-hi);

    &[dir="column"] {
      grid-template-columns: none;
      grid-template-rows: var(--split) auto minmax(0, 1fr);
    }

    &>* {
      min-inline-size: 0;
      overflow: auto;
    }

    & .split-bar {
      position: relative;
      inline-size: var(--sp-2);
      background: var(--line-hi);
      border: 0;
      padding: 0;
      cursor: col-resize;
      touch-action: none;

      &:hover,
      &:focus-visible {
        background: var(--accent, var(--primary));
      }
    }

    &[dir="column"] .split-bar {
      inline-size: auto;
      block-size: var(--sp-2);
      cursor: row-resize;
    }

    /* while dragging, the panes must not eat the pointer or select text */
    &[data-dragging] {
      user-select: none;

      &>* {
        pointer-events: none;
      }

      & .split-bar {
        pointer-events: auto;
        background: var(--accent, var(--primary));
      }
    }
  }

  /* ---- touch targets: on a coarse pointer every interactive box clears the
     WCAG 2.5.8 floor (24×24px), and anything you press to *act* gets --tap (44px,
     2.5.5). Sizes only ever bump up, so nothing shifts on a mouse. Measured on a
     390×844 phone viewport with pointer: coarse by `pnpm check:mobile`. ---- */
  @media (pointer: coarse) {

    .btn,
    .select,
    .input,
    .swatch,
    .tags,
    .combo>.trigger {
      min-block-size: var(--tap);
    }

    /* a square control has to grow BOTH ways or the tap misses sideways — and a
       36x44 swatch is not square any more either, which this system does not do */
    .swatch {
      min-inline-size: var(--tap);
    }

    /* one-glyph controls: give them a square target too */
    .pg,
    .stepper>button {
      min-inline-size: var(--tap-dense);
    }

    /* a row in something you scroll: --tap-dense keeps a phone list from running
       off the screen, and still clears the 24px floor by 16px */
    .chip,
    .pg,
    .segment>button,
    .stepper>button,
    .suggest-item,
    .result,
    .outline>a,
    .filetabs .filetab,
    .filechange {
      min-block-size: var(--tap-dense);
    }

    /* the documented checkbox / radio / switch pattern is a <label class="check">,
       so the whole row is the target — make that row a real one (it measured 28px). */
    .check {
      min-block-size: var(--tap);
    }

    /* the split divider stays 8px wide; the thumb gets --tap either side of it */
    .split-bar::after {
      content: "";
      position: absolute;
      inset-block: 0;
      inset-inline: calc((var(--tap) - var(--sp-2)) / -2);
    }

    nes-split[dir="column"] .split-bar::after {
      inset-inline: 0;
      inset-block: calc((var(--tap) - var(--sp-2)) / -2);
    }

    /* the tab's × is a 10px glyph — give it a real box to hit */
    .filetabs .ft-close {
      min-inline-size: 1.75rem;
      min-block-size: 1.75rem;
    }

    .pin input {
      inline-size: 2.75rem;
      block-size: 2.75rem;
    }

    /* 22px is as big as these can look without wrecking the row, and 22 < the 24px
       floor — so grow the hit test, not the box. A centred ::after takes each one
       to --tap; the box keeps its size and its hard 2px border. Each control still
       owns the hit test at its own centre, so neighbours can't steal a tap. */
    .checkbox,
    .radio {
      position: relative;
      inline-size: 22px;
      block-size: 22px;
    }

    .switch {
      position: relative;
    }

    :is(.checkbox, .radio, .switch)::before {
      content: "";
      position: absolute;
      inset-block-start: 50%;
      inset-inline-start: 50%;
      translate: -50% -50%;
      inline-size: max(100%, var(--tap));
      block-size: max(100%, var(--tap));
    }

    .menuitem {
      padding-block: var(--sp-3);
    }

    .tree .row {
      padding-block: var(--sp-2);
    }

    /* iOS auto-zooms when a focused field's font is < 16px. Floor the text-entry
       controls at 16px on touch devices so focusing an input never zooms the page
       (max() keeps the larger value where a control is already bigger).
       .chat-prompt / .composer own a *bare* textarea (no .textarea class), so they
       need naming here too — a chat box is the one field a phone user always taps. */
    .input,
    .textarea,
    .select,
    .tags>input,
    .chat-prompt textarea,
    .composer textarea {
      font-size: max(16px, var(--fs-body));
    }

    /* .chat-submit is sized from --ctrl-h rather than min-block-size, so bump its
       rung instead — the send button has to clear 44px like every other control. */
    .chat-submit {
      --ctrl-h: var(--ctrl-h-lg);
    }

    /* A walkthrough dot is a 12px button — a real control (it jumps to that step),
       and far under the 24px floor on a phone. Growing the dot would wreck the
       progress row, so grow only what the thumb hits: a transparent ::after takes
       the tap area to 44px while the dot keeps its size. Each dot still owns the
       hit test at its own centre, so neighbours do not steal each other's taps. */
    .wt-dot {
      position: relative;
    }

    .wt-dot::after {
      content: "";
      position: absolute;
      inset: -1rem;
    }
  }
}