/* ============================================================================
   GRAND GEN · STENCIL · COMPONENT KIT   —   v1.1
   ----------------------------------------------------------------------------
   Direction "WORKSHOP" — chosen by two independent critics (contrast lens and
   density lens) over "Showcase" and "Warm charcoal".

   THIS FILE IS THE ONLY PLACE WHERE A LITERAL COLOUR MAY APPEAR.
   Every screen of the stencil is assembled from the classes below and adds
   NOTHING of its own. Pretty lives in the kit, never on a screen.

   RULES BAKED IN, DO NOT BREAK THEM ON A SCREEN:
     · one accent. A second accent kills the direction.
     · a border that identifies a control is --bd-control (>=3:1). A hairline
       is decoration and must never be the only thing marking a control.
     · a border that is the only FORM a container has is --bd-edge. Density may
       re-tune it. Density may never delete it. (v1.1 — see 1.4.)
     · every state is carried by TWO signals, not colour alone.
     · density changes SEMANTIC tokens, never the type scale. The single
       carve-out is the touch query, and it is named where it happens.
     · no web fonts, no CDN, no icon sets, no illustrations, no animation
       beyond disclosure and hover.

   v1.1 CHANGELOG — what the acceptance audit (6 lenses + sceptics) changed:
     · --bd-edge split out of --bd-hairline. In v1 "compact" set the hairline to
       transparent, and card / accordion / canvas / empty state / row divider /
       slider track / progress / counter had NOTHING else carrying their form:
       the surviving background step measured 1.00-1.07:1, which this very file
       calls "not a signal". Decoration may still vanish. Form may not.
     · legend is now the first child of fieldset, as the a11y comment always
       claimed. The old `.group > legend` selector never matched anything.
     · every control has a real name: label[for], not a neighbouring span.
     · strict contrast raised --tx-2 too; it used to leave tertiary text
       BRIGHTER than secondary (8.02:1 vs 6.66:1) — an inverted hierarchy.
     · comfortable no longer edits the type scale, per the rule above.
     · the touch query now sits AFTER the density blocks and wins on
       specificity: equal-specificity source order used to let "compact"
       shrink a control below the touch minimum on a phone.
     · honest numbers in the comments. Claims that measured differently were
       corrected, not re-worded.
   ========================================================================= */

/* ============================================================================
   1. TOKENS
   ========================================================================= */
:root {

  /* ---- 1.1 type stacks · system only ----------------------------------- */
  --font-ui:
      "Segoe UI Variable Text", "Segoe UI Variable Static Text", "Segoe UI",
      system-ui, -apple-system, "SF Pro Text", Roboto, "Noto Sans", Arial, sans-serif;
  --font-ui-display:
      "Segoe UI Variable Display", "Segoe UI Variable Static Display",
      "Segoe UI Semibold", "Segoe UI", system-ui, -apple-system, Roboto, Arial, sans-serif;
  --font-mono:
      "Cascadia Mono", "Cascadia Code", Consolas, ui-monospace, "SF Mono",
      Menlo, "Roboto Mono", "DejaVu Sans Mono", monospace;

  /* ---- 1.2 backgrounds · warm neutral, hue ~30deg ----------------------
     Deliberately NOT blue-black: blue greys read "consumer app", warm greys
     read "tool". Level 0 deepest, level 2 highest.                        */
  --bg-0: #171614;
  --bg-1: #1E1D1A;
  --bg-2: #262421;

  /* GRAFT (direction C, both critics): a neutral well for generated images.
     A warm background tints the preview and misrepresents our own product.
     Anything showing a render sits on THIS, never on --bg-0.              */
  --bg-canvas: #101010;

  /* ---- 1.3 surfaces · interactive things live here --------------------- */
  --sf-0: #201F1C;   /* recessed: input well, track, disabled control       */
  --sf-1: #2A2825;   /* control at rest                                    */
  --sf-2: #333029;   /* control hovered                                    */
  --sf-3: #3B3830;   /* control active / open / selected row               */

  /* ---- 1.4 borders · THREE tiers, and the difference is what may vanish --
     --bd-hairline  decoration. A separator you would not miss. Density may
                    delete it outright.
     --bd-edge      the only FORM a container has (card, accordion, canvas,
                    empty state, row divider, track, badge well). Density may
                    RE-TUNE it, never delete it. This tier exists because the
                    background step underneath measures 1.00-1.07:1, and this
                    file already ruled that a value step alone is not a signal.
     --bd-control   the border IS the affordance. 3.82:1 on --bg-0,
                    3.63:1 on --bg-1, 3.11:1 on its own --sf-1 fill.
                    (v1 claimed a flat "3.56:1" — it was one pair of four.)   */
  --bd-hairline:    #302E2A;
  --bd-hairline-hi: #3A3733;
  --bd-edge:        #3A3733;
  --bd-edge-hi:     #454139;
  --bd-control:     #7A7269;
  --bd-control-hi:  #8A8177;

  /* ---- 1.5 text · three levels + disabled ------------------------------ */
  --tx-1: #EDE9E3;
  --tx-2: #A8A29A;
  --tx-3: #948E84;
  --tx-off: #7C776F;           /* disabled still >=3:1 — we never hide state */

  /* ---- 1.6 accent · exactly one, muted brass --------------------------- */
  --ac:        #C8853F;
  --ac-hover:  #DB9A52;
  --ac-press:  #B0742F;
  --ac-ink:    #120F0C;
  --ac-quiet:  #8E5F2C;
  --ac-tint-1: rgb(200 133 63 / 0.10);
  --ac-tint-2: rgb(200 133 63 / 0.16);
  --ac-tint-3: rgb(200 133 63 / 0.22);

  /* ---- 1.7 semantic · four, and only four ------------------------------
     --gate is violet on purpose: "locked" must never be confusable with
     "actionable brass".                                                    */
  --ok:   #78A85F;  --ok-tint:   rgb(120 168  95 / 0.14);
  --warn: #D8B24A;  --warn-tint: rgb(216 178  74 / 0.14);
  --err:  #E4796C;  --err-tint:  rgb(228 121 108 / 0.14);
  --gate: #B08CD9;  --gate-tint: rgb(176 140 217 / 0.14);

  /* ---- 1.8 focus · one ring for the whole kit -------------------------- */
  --focus: #F2C88A;
  --focus-w: 2px;
  --focus-offset: 2px;
}

:root[data-accent="brass"] {   
  --ac: #C8853F; --ac-hover: #DB9A52; --ac-press: #B0742F; --ac-quiet: #8E5F2C;
  --ac-ink: #120F0C; --focus: #F2C88A;
  --ac-tint-1: rgb(200 133 63 / 0.10); --ac-tint-2: rgb(200 133 63 / 0.16); --ac-tint-3: rgb(200 133 63 / 0.22);
}
:root[data-accent="wine"] {    
  --ac: #C05B62; --ac-hover: #D4737A; --ac-press: #A64A51; --ac-quiet: #8A4148;
  --ac-ink: #14100F; --focus: #EFA6AB;
  --ac-tint-1: rgb(192 91 98 / 0.10); --ac-tint-2: rgb(192 91 98 / 0.16); --ac-tint-3: rgb(192 91 98 / 0.22);
}
:root[data-accent="moss"] {    
  --ac: #7FA262; --ac-hover: #95B678; --ac-press: #6B8C52; --ac-quiet: #5D7748;
  --ac-ink: #0F120D; --focus: #BEDCA6;
  --ac-tint-1: rgb(127 162 98 / 0.10); --ac-tint-2: rgb(127 162 98 / 0.16); --ac-tint-3: rgb(127 162 98 / 0.22);
}
:root[data-accent="sea"] {     
  --ac: #5E9BA8; --ac-hover: #74B0BD; --ac-press: #4E8590; --ac-quiet: #446F78;
  --ac-ink: #0D1113; --focus: #A6D6E0;
  --ac-tint-1: rgb(94 155 168 / 0.10); --ac-tint-2: rgb(94 155 168 / 0.16); --ac-tint-3: rgb(94 155 168 / 0.22);
}
:root[data-accent="orchid"] {  
  --ac: #9B7BC4; --ac-hover: #AF92D3; --ac-press: #856AA8; --ac-quiet: #6E5890;
  --ac-ink: #100E14; --focus: #CFBBE8;
  --ac-tint-1: rgb(155 123 196 / 0.10); --ac-tint-2: rgb(155 123 196 / 0.16); --ac-tint-3: rgb(155 123 196 / 0.22);
}
:root[data-accent="ash"] {     
  --ac: #9A948B; --ac-hover: #B0AAA0; --ac-press: #837D75; --ac-quiet: #6E6963;
  --ac-ink: #111110; --focus: #D2CCC2;
  --ac-tint-1: rgb(154 148 139 / 0.10); --ac-tint-2: rgb(154 148 139 / 0.16); --ac-tint-3: rgb(154 148 139 / 0.22);
}

:root[data-accent="rust"] {          
  --ac: #B4633C; --ac-hover: #C67853; --ac-press: #955232; --ac-quiet: #77452C;
  --ac-ink: #120E0C; --focus: #EDA27E;
  --ac-tint-1: rgb(180 99 60 / 0.10); --ac-tint-2: rgb(180 99 60 / 0.16); --ac-tint-3: rgb(180 99 60 / 0.22);
}
:root[data-accent="rose"] {          
  --ac: #CE7E96; --ac-hover: #D99CAE; --ac-press: #C3607E; --ac-quiet: #B44B6B;
  --ac-ink: #120C0E; --focus: #F7D0DB;
  --ac-tint-1: rgb(206 126 150 / 0.10); --ac-tint-2: rgb(206 126 150 / 0.16); --ac-tint-3: rgb(206 126 150 / 0.22);
}
:root[data-accent="plum"] {          
  --ac: #A2648C; --ac-hover: #B17E9F; --ac-press: #8A5377; --ac-quiet: #724763;
  --ac-ink: #120C10; --focus: #E0A1C9;
  --ac-tint-1: rgb(162 100 140 / 0.10); --ac-tint-2: rgb(162 100 140 / 0.16); --ac-tint-3: rgb(162 100 140 / 0.22);
}
:root[data-accent="indigo"] {        
  --ac: #7280C0; --ac-hover: #8E99CD; --ac-press: #5667B3; --ac-quiet: #4A599B;
  --ac-ink: #0C0D12; --focus: #BCC6F0;
  --ac-tint-1: rgb(114 128 192 / 0.10); --ac-tint-2: rgb(114 128 192 / 0.16); --ac-tint-3: rgb(114 128 192 / 0.22);
}
:root[data-accent="teal"] {          
  --ac: #4F9B8A; --ac-hover: #63B09F; --ac-press: #418072; --ac-quiet: #37665C;
  --ac-ink: #0C1211; --focus: #86DECB;
  --ac-tint-1: rgb(79 155 138 / 0.10); --ac-tint-2: rgb(79 155 138 / 0.16); --ac-tint-3: rgb(79 155 138 / 0.22);
}
:root[data-accent="olive"] {         
  --ac: #9A9B54; --ac-hover: #ADAE6A; --ac-press: #808146; --ac-quiet: #67673B;
  --ac-ink: #12120C; --focus: #DCDD8C;
  --ac-tint-1: rgb(154 155 84 / 0.10); --ac-tint-2: rgb(154 155 84 / 0.16); --ac-tint-3: rgb(154 155 84 / 0.22);
}
:root[data-accent="amber"] {         
  --ac: #CBA23E; --ac-hover: #D4B15E; --ac-press: #B18B30; --ac-quiet: #90732C;
  --ac-ink: #12110C; --focus: #F4D78F;
  --ac-tint-1: rgb(203 162 62 / 0.10); --ac-tint-2: rgb(203 162 62 / 0.16); --ac-tint-3: rgb(203 162 62 / 0.22);
}
:root[data-accent="slate"] {         
  --ac: #7E8B99; --ac-hover: #959FAB; --ac-press: #697785; --ac-quiet: #5A6570;
  --ac-ink: #0C0F12; --focus: #B5C8DD;
  --ac-tint-1: rgb(126 139 153 / 0.10); --ac-tint-2: rgb(126 139 153 / 0.16); --ac-tint-3: rgb(126 139 153 / 0.22);
}

.swatch {
  position: relative;
  display: inline-grid; place-items: center; width: var(--h-lg); height: var(--h-lg);
  padding: 0; border: 1px solid var(--bd-control); border-radius: var(--r-pill);
  background: transparent; cursor: pointer;
}

.swatch::before {
  content: ""; position: absolute; inset: 0; border-radius: inherit;
  background: var(--sw);
}

.swatch { --sw-check: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cpath d='M5 12.6l4.7 4.7L19 7.2' fill='none' stroke='%23000' stroke-width='3.4' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E"); }

.swatch[aria-pressed="true"] { box-shadow: 0 0 0 2px var(--bg-1), 0 0 0 4px var(--sw); }

.swatch[aria-pressed="true"]::before {
  -webkit-mask-image: linear-gradient(#000, #000), var(--sw-check);
          mask-image: linear-gradient(#000, #000), var(--sw-check);
  -webkit-mask-size: 100% 100%, 60% 60%;
          mask-size: 100% 100%, 60% 60%;
  -webkit-mask-position: center, center;
          mask-position: center, center;
  -webkit-mask-repeat: no-repeat, no-repeat;
          mask-repeat: no-repeat, no-repeat;
  -webkit-mask-composite: xor;
          mask-composite: exclude;
}
.swatch:focus-visible { outline: var(--focus-w) solid var(--focus); outline-offset: var(--focus-offset); }
.swatches { display: flex; gap: var(--sp-4); flex-wrap: wrap; }

.swatches--two { display: grid; grid-template-columns: repeat(7, max-content);
                 justify-content: start; }

.note {
  border: 1px dashed var(--bd-edge); border-left: 3px solid var(--gate);
  border-radius: var(--r-2); background: var(--bg-0);
  padding: var(--sp-5); display: grid; gap: var(--sp-4);
  
  align-content: start;
}
.note::before {
  content: "NOTE";
  font: var(--fw-bold) var(--fs-2xs)/1 var(--font-ui);
  letter-spacing: .08em; color: var(--gate);
}
.note > * { color: var(--tx-3); font-size: var(--fs-2xs); line-height: var(--lh-2xs); }

.devline { color: var(--gate); }
.devline::after {
  content: "";
  font-weight: var(--fw-normal); letter-spacing: 0; text-transform: none;
  color: var(--tx-3); font-size: var(--fs-3xs);
}

.fork {
  border: 1px dashed var(--bd-edge); border-left: 3px solid var(--warn);
  border-radius: var(--r-2); background: var(--bg-0);
  padding: var(--sp-5); display: grid; gap: var(--sp-4);
  
  align-content: start;
}
.fork::before {
  content: "";
  font: var(--fw-bold) var(--fs-2xs)/1 var(--font-ui);
  letter-spacing: .08em; color: var(--warn);
}
.fork__q { color: var(--tx-2); font-size: var(--fs-xs); line-height: var(--lh-xs); font-weight: var(--fw-medium); }
.fork__pair { display: grid; gap: var(--sp-4); grid-template-columns: 1fr auto 1fr; align-items: stretch; }
@media (max-width: 720px) { .fork__pair { grid-template-columns: 1fr; } }
.fork__b {
  border: 1px solid var(--bd-edge); border-radius: var(--r-2);
  background: var(--bg-1); padding: var(--sp-5); display: grid; gap: var(--sp-3);
  align-content: start;
}
.fork__b--now { border-color: var(--bd-control); background: var(--bg-2); }
.fork__b--now .fork__t::after {
  content: ""; color: var(--ac); font-weight: var(--fw-normal);
}
.fork__t { color: var(--tx-1); font-size: var(--fs-xs); line-height: var(--lh-xs); font-weight: var(--fw-bold); }
.fork__b > p, .fork__b > span { color: var(--tx-3); font-size: var(--fs-2xs); line-height: var(--lh-2xs); }
.fork__cost { color: var(--warn); font-size: var(--fs-2xs); line-height: var(--lh-2xs); }
.fork__or {
  display: grid; place-items: center; color: var(--warn);
  font: var(--fw-bold) var(--fs-2xs)/1 var(--font-ui); letter-spacing: .08em;
}
@media (max-width: 720px) { .fork__or { padding: var(--sp-2) 0; } }
.fork__src { color: var(--tx-3); font-size: var(--fs-3xs); line-height: var(--lh-2xs); }

.fork--closed { border-left-color: var(--ok); }
.fork--closed::before { content: ""; color: var(--ok); }
.fork--closed .fork__b--now { border-color: var(--ok); background: var(--bg-2); }
.fork--closed .fork__b--now .fork__t::after { content: ""; color: var(--ok); font-weight: var(--fw-normal); }
.fork--closed .fork__b:not(.fork__b--now) { opacity: .55; }

:root {

  /* ---- 1.9 radius · signature is 5px -----------------------------------
     Chips are softened RECTANGLES, not pills. Pills read "blog tag cloud",
     rectangles read "parameter". This single number carries the direction. */
  --r-1: 3px;
  --r-2: 5px;
  --r-3: 8px;
  --r-4: 12px;
  --r-pill: 999px;   /* numeric badges ONLY */

  /* ---- 1.10 spacing · 4px base with a 2px half-step -------------------- */
  --sp-1: 2px;  --sp-2: 4px;  --sp-3: 6px;  --sp-4: 8px;  --sp-5: 12px;
  --sp-6: 16px; --sp-7: 20px; --sp-8: 24px; --sp-9: 32px; --sp-10: 48px;

  /* ---- 1.11 control height ladder · the density lever ------------------ */
  --h-xs: 22px; --h-sm: 28px; --h-md: 32px; --h-lg: 40px; --h-xl: 48px;

  /* ---- 1.12 type scale · base 13px -------------------------------------
     NOTHING below this line is touched by the density lever. See 1.15.    */
  --fs-3xs: 10px; --lh-3xs: 12px;
  --fs-2xs: 11px; --lh-2xs: 14px;
  --fs-xs:  12px; --lh-xs:  16px;
  --fs-sm:  13px; --lh-sm:  18px;
  --fs-md:  14px; --lh-md:  20px;
  --fs-lg:  16px; --lh-lg:  22px;
  --fs-xl:  20px; --lh-xl:  26px;
  --fs-2xl: 26px; --lh-2xl: 32px;
  --fw-normal: 400; --fw-medium: 500; --fw-bold: 600;
  --ls-tight: -0.006em;
  /* GRAFT (C): 11px uppercase needs .08em, not .02em, or it sets as a blob. */
  --ls-caps: 0.08em;

  /* ---- 1.13 elevation · depth from surface value, not blur ------------- */
  --edge-top:   inset 0 1px 0 rgb(255 255 255 / 0.055);
  --edge-press: inset 0 1px 2px rgb(0 0 0 / 0.45);
  --sh-1: 0 1px 2px rgb(0 0 0 / 0.32);
  --sh-2: 0 6px 16px rgb(0 0 0 / 0.42), 0 1px 3px rgb(0 0 0 / 0.30);
  --sh-3: 0 20px 48px rgb(0 0 0 / 0.55), 0 2px 8px rgb(0 0 0 / 0.40);
  --glow-ac: 0 0 0 1px rgb(200 133 63 / 0.30), 0 0 14px rgb(200 133 63 / 0.16);

  /* ---- 1.14 motion ----------------------------------------------------- */
  --dur-1:  90ms;  /* state feedback */
  --dur-2: 140ms;  /* colour settle  */
  --dur-3: 200ms;  /* disclosure — THE allowed animation */
  --dur-4: 260ms;  /* popover / drawer — reserved for kit v2 */

  --ease-out:  cubic-bezier(0.22, 0.61, 0.36, 1);
  --ease-snap: cubic-bezier(0.20, 0.00, 0.00, 1);

  /* FIX (both critics): direction A shipped --row-hover at 1.05:1, which is
     invisible — and it was the ONLY hover cue on a table row. Raised, and a
     second non-colour signal (a control-weight left edge) is added on
     .row:hover below. The accent left edge belongs to SELECTED, not hover:
     two states must not share one signal. */
  --row-hover: rgb(255 255 255 / 0.055);

  /* ---- 1.15 layout ----------------------------------------------------- */
  --row-label-w: clamp(96px, 22%, 168px);
  
  --row-st-w: 104px;
  --panel-w: clamp(280px, 24vw, 360px);   /* reserved for kit v2 (app frame) */
  --pool-max-h: clamp(160px, 34vh, 340px);
  --hit-min: 44px;   /* GRAFT (B): minimum touch target, applied via ::after */
  --z-sticky: 10; --z-popover: 100; --z-modal: 1000; --z-toast: 2000;
}

/* Density lever. GRAFT (B): direction A only had a lever DOWNWARD; a
   comfortable end is needed for the casual skin, where a screen holds three
   large tiles rather than forty knobs.
   RULE: density edits SEMANTIC tokens (heights, spacing, decoration),
   NEVER the type scale in 1.12. v1 broke this at the comfortable end; the
   font override was removed rather than the rule. */

:root[data-density="compact"] {
  --h-sm: 24px; --h-md: 28px; --h-lg: 34px; --h-xl: 42px;
  --sp-5: 8px; --sp-6: 12px; --sp-7: 14px; --sp-8: 16px;
  --sp-9: 20px; --sp-10: 28px;
  /* Decoration goes, so ~34 outlines stop competing with content. Form does
     NOT go: --bd-edge is raised instead, because at this spacing the edge is
     the only thing left telling two stacked blocks apart. */
  --bd-hairline: transparent;
  --bd-edge:     #47433B;
  --bd-edge-hi:  #55503F;
}
:root[data-density="comfortable"] {
  --h-sm: 32px; --h-md: 38px; --h-lg: 46px; --h-xl: 56px;
  --sp-5: 16px; --sp-6: 20px; --sp-7: 26px; --sp-8: 32px;
  --sp-9: 40px; --sp-10: 60px;
}

/* Touch: ONE query re-tunes the whole product. No screen is restyled.
   It sits AFTER the density blocks and doubles its own specificity on
   purpose: a media query does NOT raise specificity, so in v1 an explicit
   "compact" beat it by source order and shrank a control to 28px under a
   thumb. Touch minimums win over a density preference — always.
   CARVE-OUT, stated where it happens: this is the one place allowed to
   touch the type scale, because it is a different axis (input device),
   not a density preference. */
@media (pointer: coarse) {
  :root:root { --h-sm: 34px; --h-md: 40px; --h-lg: 46px; --h-xl: 54px;
               --fs-sm: 14px; --lh-sm: 20px; }
}
@media (prefers-reduced-motion: reduce) {
  :root { --dur-1: 1ms; --dur-2: 1ms; --dur-3: 1ms; --dur-4: 1ms; }
  
  html { scroll-behavior: auto; }
}

html { scroll-behavior: smooth; }

:where(section[id], [id]:target) {
  scroll-margin-top: calc(var(--h-sm) + var(--h-xl) + var(--sp-5));
}

/* GRAFT (B, a11y critic): an explicit strict mode, wired to the OS request
   rather than to a setting nobody finds.
   v1 raised --tx-3 only, which left TERTIARY text brighter than SECONDARY
   (8.02:1 against 6.66:1) — the hierarchy read upside down in the exact mode
   meant to help. Both levels move now, and they keep their order. */
@media (prefers-contrast: more) {
  :root { --bd-hairline: #4A4740; --bd-edge: #5A554B; --bd-edge-hi: #6A6459;
          --tx-1: #F7F4EF; --tx-2: #D2CCC2; --tx-3: #B9B2A8; --tx-off: #948E84; }
}

:root[data-a11y="strict"] {
  --bd-hairline: #4A4740; --bd-edge: #5A554B; --bd-edge-hi: #6A6459;
  --tx-1: #F7F4EF; --tx-2: #D2CCC2; --tx-3: #B9B2A8; --tx-off: #948E84;
}

/* ============================================================================
   2. BASE
   ========================================================================= */
html { color-scheme: dark; }
* { box-sizing: border-box; }
/* The UA rule for [hidden] is display:none at the lowest possible weight, so
   ANY class in this file that sets a display beats it. A component that hides
   itself must actually be hidden — this line is not optional. */
[hidden] { display: none !important; }
body {
  margin: 0;
  background: var(--bg-0);
  color: var(--tx-1);
  font-family: var(--font-ui);
  font-size: var(--fs-sm);
  line-height: var(--lh-sm);
  font-optical-sizing: auto;
}
/* Every number in the product goes through this. Columns of numbers must
   line up — tabular figures are not optional in a parameter UI. */
.num, .mono, input[type="number"] {
  font-family: var(--font-mono);
  font-variant-numeric: tabular-nums;
  font-feature-settings: "tnum" 1;
}
:where(a) { color: var(--ac-hover); text-underline-offset: 2px; }

:where(.btn, .rail__item, .tab, .ccard, .menu__item, .appbar__brand, .pager__p, .chip, .tile, .row a) {
  text-decoration: none;
}

/* One focus ring, one place. outline survives masks and forced-colors, which
   a box-shadow ring does not — that was a fatal finding against direction C. */
:where(button, a, input, select, textarea, [tabindex], .chip, .tile, .seg__btn):focus-visible {
  outline: var(--focus-w) solid var(--focus);
  outline-offset: var(--focus-offset);
  border-radius: var(--r-2);
}
/* Exception, measured: inside a segment the buttons are 2px apart, so an
   outward ring lands ON the neighbour — and against a brass-filled neighbour
   the ring is 1.95:1, i.e. gone on that edge. Inside the button it sits on
   its own fill and keeps 10.5:1 on all four sides. */
.seg__btn:focus-visible { outline-offset: -2px; }

/* GRAFT (B): guaranteed hit target without changing visual size, and
   clamped so it cannot overlap the neighbouring row in a dense pool.
   Applied in the kit on .chip — v1 declared it and used it nowhere. */
.hit-pad { position: relative; }
.hit-pad::after {
  content: ""; position: absolute; left: 0; right: 0;
  top: 50%; transform: translateY(-50%);
  height: min(var(--hit-min), 100% + var(--sp-3));
}

/* ============================================================================
   3. TEXT PRIMITIVES
   ========================================================================= */
.t-title   { font-family: var(--font-ui-display); font-size: var(--fs-xl); line-height: var(--lh-xl);
             font-weight: var(--fw-bold); letter-spacing: var(--ls-tight); margin: 0; }
.t-section { font-family: var(--font-ui-display); font-size: var(--fs-lg); line-height: var(--lh-lg);
             font-weight: var(--fw-bold); margin: 0; }
.t-label   { font-size: var(--fs-xs); line-height: var(--lh-xs); color: var(--tx-2); font-weight: var(--fw-medium); }
.t-hint    { font-size: var(--fs-2xs); line-height: var(--lh-2xs); color: var(--tx-3); }
.t-caps    { font-size: var(--fs-2xs); line-height: var(--lh-2xs); text-transform: uppercase;
             letter-spacing: var(--ls-caps); color: var(--tx-3); font-weight: var(--fw-medium); }
/* A label is a <label for>. This class only paints it. */
label.t-label { cursor: pointer; }

.emo { font-family: "Segoe UI Emoji", "Apple Color Emoji", "Noto Color Emoji",
       "Segoe UI Symbol", sans-serif;
       font-size: 1.05em; line-height: 1; vertical-align: -.08em;
       font-variant-emoji: emoji; }

/* ============================================================================
   4. BUTTON
   ========================================================================= */
.btn {
  --_bg: var(--sf-1); --_bd: var(--bd-control); --_tx: var(--tx-1);
  display: inline-flex; align-items: center; justify-content: center; gap: var(--sp-3);
  height: var(--h-md); padding-inline: var(--sp-5);
  border: 1px solid var(--_bd); border-radius: var(--r-2);
  background: var(--_bg); color: var(--_tx);
  font: var(--fw-medium) var(--fs-sm)/1 var(--font-ui);
  box-shadow: var(--edge-top);
  cursor: pointer; user-select: none; white-space: nowrap;
  transition: background var(--dur-1) var(--ease-out),
              border-color var(--dur-1) var(--ease-out),
              box-shadow var(--dur-1) var(--ease-out),
              transform var(--dur-1) var(--ease-snap);
}
.btn:hover   { --_bg: var(--sf-2); --_bd: var(--bd-control-hi); }
/* Pressed is carried by THREE signals — value, inset shadow, 1px travel —
   so it survives colour blindness and forced-colors. The border is raised
   with the fill: v1 kept it at --bd-control while the fill climbed to sf-3,
   which dropped the affordance edge to 2.47:1 exactly while pressed. */
.btn:active  { --_bg: var(--sf-3); --_bd: var(--bd-control-hi);
               box-shadow: var(--edge-press); transform: translateY(1px); }
.btn--primary { --_bg: var(--ac); --_bd: var(--ac); --_tx: var(--ac-ink); }
.btn--primary:hover  { --_bg: var(--ac-hover); --_bd: var(--ac-hover); }
.btn--primary:active { --_bg: var(--ac-press); --_bd: var(--ac-press); }
.btn--ghost   { --_bg: transparent; --_bd: transparent; --_tx: var(--tx-2); box-shadow: none; }
.btn--ghost:hover { --_bg: var(--sf-1); --_bd: var(--bd-hairline-hi); --_tx: var(--tx-1); }
/* A GHOST THAT CARRIES WORDS IS STILL A BUTTON. "Cancel" standing next to a filled primary read as
   a caption until the pointer touched it — and the pointer is exactly what a person on a phone does
   not have, so the affordance was appearing only for the people who least needed it. It keeps the
   transparent fill, which is what makes it the quiet one of the pair; it gets its edge back.
   Icon-only ghosts stay invisible on purpose: a close cross in a dialog corner is furniture, and
   boxing every one of them would crowd the very heads they sit in. */
.btn--ghost:not(.btn--icon) { --_bd: var(--bd-control); --_tx: var(--tx-1); box-shadow: var(--edge-top); }
.btn--ghost:not(.btn--icon):hover { --_bg: var(--sf-1); --_bd: var(--bd-control-hi); }
.btn--danger  { --_bg: var(--err-tint); --_bd: var(--err); --_tx: var(--err); }
.btn--danger:hover { --_bg: rgb(228 121 108 / 0.22); }

.btn[disabled], .btn[disabled]:hover, .btn[disabled]:active,
.btn[aria-disabled="true"], .btn[aria-disabled="true"]:hover, .btn[aria-disabled="true"]:active {
  --_bg: var(--sf-0); --_bd: var(--bd-edge); --_tx: var(--tx-off);
  box-shadow: none; cursor: not-allowed; transform: none;
}
.btn--sm { height: var(--h-sm); padding-inline: var(--sp-4); font-size: var(--fs-xs); }
.btn--lg { height: var(--h-lg); padding-inline: var(--sp-7); font-size: var(--fs-md); }
/* The one per screen. */
.btn--hero { height: var(--h-xl); padding-inline: var(--sp-9); font-size: var(--fs-md); border-radius: var(--r-3); }
/* The width of whatever it is standing in. For a button that is the only thing on its line and
   the way into something bigger — the market, for one — a button the width of its own words reads
   as an afterthought next to the block above it. */
.btn--block { width: 100%; }
/* The price rides INSIDE the button and recomputes with the batch. .btn is
   already an inline-flex with a gap, so this is a child, not a new component. */

.btn__price { font-family: inherit; font-variant-numeric: tabular-nums;
              opacity: .82; font-weight: var(--fw-normal); }
/* Busy must also SAY it is busy: pointer-events alone leaves the keyboard
   free to fire the action again. Markup carries aria-busy + aria-disabled. */
.btn--busy { box-shadow: var(--edge-top), var(--glow-ac); pointer-events: none; }
.btn--busy::before {
  content: ""; width: 12px; height: 12px; border-radius: var(--r-pill);
  border: 2px solid currentColor; border-top-color: transparent;
  animation: kit-spin 700ms linear infinite;
}
@keyframes kit-spin { to { transform: rotate(360deg); } }
@media (prefers-reduced-motion: reduce) { .btn--busy::before { animation: none; opacity: .5; } }

/* ============================================================================
   5. CHIP — multi-select with a limit. 15 slots ride on this one.
   ========================================================================= */
.chip {
  --_bg: var(--sf-1); --_bd: var(--bd-control); --_tx: var(--tx-2);
  position: relative;
  display: inline-flex; align-items: center; gap: var(--sp-3);
  min-height: var(--h-md); padding: 0 var(--sp-4);
  border: 1px solid var(--_bd); border-radius: var(--r-2);
  background: var(--_bg); color: var(--_tx);
  font: var(--fw-medium) var(--fs-sm)/1 var(--font-ui);
  cursor: pointer; user-select: none;
  transition: background var(--dur-1) var(--ease-out), border-color var(--dur-1) var(--ease-out),
              color var(--dur-1) var(--ease-out);
}
.chip:hover { --_bg: var(--sf-2); --_bd: var(--bd-control-hi); --_tx: var(--tx-1); }

/* GRAFT (C): the order box is ALWAYS reserved, so selecting never reflows the
   pool. Direction B animated a label from zero width and re-laid-out the grid
   on every click — that was its worst density defect. */
.chip__ord {
  display: inline-grid; place-items: center;
  width: 16px; height: 16px; flex: none;
  border-radius: var(--r-1);
  font-family: var(--font-mono); font-size: var(--fs-3xs); line-height: 1;
  background: transparent; color: transparent;
  transition: background var(--dur-1) var(--ease-out), color var(--dur-1) var(--ease-out);
}
.chip[aria-pressed="true"] {
  --_bg: var(--ac-tint-1); --_bd: var(--ac); --_tx: var(--tx-1);
}
.chip[aria-pressed="true"]:hover { --_bg: var(--ac-tint-2); }
/* Selected is carried by border colour AND the filled ordinal box AND weight —
   three signals, so the state survives without colour vision. */
.chip[aria-pressed="true"] .chip__ord { background: var(--ac); color: var(--ac-ink); }
.chip[aria-pressed="true"] .chip__label { font-weight: var(--fw-bold); }

.chip--limit { --_bg: var(--sf-0); --_tx: var(--tx-3); cursor: not-allowed; }
.chip[disabled], .chip[aria-disabled="true"] {
  --_bg: var(--sf-0); --_bd: var(--bd-edge); --_tx: var(--tx-off);
  cursor: not-allowed;
}
/* A gated chip is NOT disabled: it is pickable the moment the tier changes,
   and clicking it must be able to open the offer. It keeps aria-disabled off
   and carries the word "Premium" inside its own accessible name. */
.chip--gated { --_bd: var(--gate); --_bg: var(--gate-tint); --_tx: var(--tx-2); }

/* Counter shown next to a pool heading: "3 / 17". */
.counter {
  display: inline-flex; align-items: center; height: var(--h-xs);
  padding-inline: var(--sp-3); border-radius: var(--r-pill);
  background: var(--sf-0); border: 1px solid var(--bd-edge);
  font-family: var(--font-mono); font-size: var(--fs-2xs); color: var(--tx-2);
  font-variant-numeric: tabular-nums;
}
.counter--full { border-color: var(--warn); color: var(--warn); background: var(--warn-tint); }

.counter[data-fill="0"] { color: var(--err); border-color: var(--err); background: var(--err-tint); }
.counter[data-fill="1"] { color: #D8B24A; }
.counter[data-fill="2"] { color: #D2BD52; }
.counter[data-fill="3"] { color: #BFC65A; }
.counter[data-fill="4"] { color: #A5CE62; }
.counter[data-fill="5"] { color: #8FD86E; }

/* ============================================================================
   6. TILE — the pool tile. 17 slots ride on this: species, pose, outfit…
   ========================================================================= */
.tile {
  position: relative; display: grid; gap: var(--sp-3);
  padding: var(--sp-4); border: 1px solid var(--bd-control); border-radius: var(--r-2);
  background: var(--sf-1); color: var(--tx-2); cursor: pointer; text-align: left;
  font: var(--fw-medium) var(--fs-sm)/1.2 var(--font-ui);
  transition: background var(--dur-1) var(--ease-out), border-color var(--dur-1) var(--ease-out);
}
.tile:hover { background: var(--sf-2); border-color: var(--bd-control-hi); color: var(--tx-1); }
.tile__thumb { aspect-ratio: 3 / 4; border-radius: var(--r-1); background: var(--bg-canvas);
               display: grid; place-items: center; color: var(--tx-off); font-size: var(--fs-2xs);
               overflow: hidden; }

.tile__thumb img { width: 100%; height: 100%; object-fit: cover; display: block; }

.tile[aria-pressed="true"] {
  border-color: var(--ac); background: var(--ac-tint-1); color: var(--tx-1);
  box-shadow: inset 0 0 0 2px var(--ac);
}

.tile[aria-disabled="true"] {
  background: var(--sf-0); color: var(--tx-3); cursor: not-allowed;
}
.tile[aria-disabled="true"]:hover { background: var(--sf-0); border-color: var(--bd-control); color: var(--tx-3); }
.pool {
  display: grid; gap: var(--sp-3);
  grid-template-columns: repeat(auto-fill, minmax(104px, 1fr));
  max-height: var(--pool-max-h); overflow: auto; padding: var(--sp-1);
}
.pool--chips { display: flex; flex-wrap: wrap; grid-template-columns: none; }

/* ============================================================================
   7. SEGMENT — 2-4 mutually exclusive options. 15 slots.
   ========================================================================= */

.seg { position: relative; display: inline-flex; padding: 2px; gap: 2px;
       background: var(--sf-0); border: 1px solid var(--bd-control); border-radius: var(--r-2); }
.seg::before {
  content: ""; position: absolute; left: 0; top: 0; z-index: 0; pointer-events: none;
  width: var(--ind-w, 0); height: var(--ind-h, 0);
  transform: translate(var(--ind-x, 0px), var(--ind-y, 0px));
  background: var(--ac); border-radius: var(--r-1);
}
.seg--live::before {
  transition: transform var(--dur-2) var(--ease-out),
              width var(--dur-2) var(--ease-out),
              height var(--dur-2) var(--ease-out);
}
.seg__btn {
  
  min-height: calc(var(--h-md) - 6px); padding: 2px var(--sp-5);
  display: inline-flex; align-items: center; justify-content: center;
  border: 0; border-radius: var(--r-1); background: transparent; color: var(--tx-2);
  font: var(--fw-medium) var(--fs-sm)/1.25 var(--font-ui); cursor: pointer;
  transition: background var(--dur-1) var(--ease-out), color var(--dur-1) var(--ease-out);
}
.seg__btn { position: relative; z-index: 1; }
.seg__btn:hover { background: var(--sf-1); color: var(--tx-1); }

.seg__btn + .seg__btn::before {
  content: ""; position: absolute; left: -1.5px; top: 25%; bottom: 25%;
  
  width: 1px; background: var(--bd-control); pointer-events: none;
  transition: opacity var(--dur-2) var(--ease-out);
}
.seg__btn[aria-pressed="true"]::before,
.seg__btn[aria-pressed="true"] + .seg__btn::before { opacity: 0; }

.seg--tight .seg__btn { padding-inline: var(--sp-4); }

.seg--stack .seg__btn { white-space: normal; text-align: center; padding-block: var(--sp-2); }

.seg__btn[aria-pressed="true"] { background: var(--ac); color: var(--ac-ink); font-weight: var(--fw-bold); }
.seg--live .seg__btn[aria-pressed="true"],
.seg--live .seg__btn[aria-pressed="true"]:hover { background: transparent; }

/* ============================================================================
   8. FIELDS — input, textarea, select
   Every control is named by a real <label for>. A neighbouring <span> paints
   a label but is not one: v1 had 16 of them and those controls had no name
   at all in the accessibility tree.
   ========================================================================= */

.field > .t-label, .field > .anchor > .t-label,
.slider__head > .t-label, .slider__head > .anchor > .t-label {
  font-size: var(--fs-sm); line-height: var(--lh-sm);
  color: var(--tx-1); font-weight: var(--fw-medium);
}

.field { display: grid; gap: var(--sp-2); align-content: start; }

.t-label--drag { cursor: ew-resize; user-select: none; touch-action: none;
                 border-bottom: 1px dotted var(--bd-control);
                 justify-self: start; padding-bottom: 1px; }
.t-label--drag:hover { border-bottom-color: var(--ac); color: var(--tx-1); }
.t-label--drag.is-scrubbing { color: var(--ac); border-bottom-color: var(--ac); }
.field__row { display: grid; grid-template-columns: var(--row-label-w) 1fr; gap: var(--sp-4);
              align-items: center; }

.input, .select, .textarea, .combo__btn {
  width: 100%; min-height: var(--h-md); padding: var(--sp-3) var(--sp-4);
  
  background-color: var(--sf-0); color: var(--tx-1);
  border: 1px solid var(--bd-control); border-radius: var(--r-2);
  font: var(--fw-normal) var(--fs-sm)/var(--lh-sm) var(--font-ui);
  box-shadow: var(--edge-press);
  
  transition: border-color var(--dur-1) var(--ease-out), background-color var(--dur-1) var(--ease-out);
}
.input::placeholder, .textarea::placeholder { color: var(--tx-3); }
.input:hover, .select:hover, .textarea:hover,
.combo__btn:hover:not(:disabled) { border-color: var(--bd-control-hi); }

.input:focus, .select:focus, .textarea:focus, .combo__btn:focus { background-color: var(--sf-1); }
.textarea { min-height: 84px; resize: vertical; }
/* A field that is USUALLY one line but may run to three. It starts the height of an input, so a
   form of them doesn't read as a wall of boxes, and it keeps the grip in the corner — which is
   the whole point: the person decides how much room the answer needs, not the markup. */
.textarea--short { min-height: var(--h-lg); }

.input:disabled, .select:disabled, .textarea:disabled, .combo__btn:disabled {
  
  color: var(--tx-off); background-color: var(--bg-0);
  border-color: var(--bd-hairline); cursor: not-allowed;
}
.numctl__b:disabled { opacity: .4; cursor: not-allowed; }

.input--short { max-width: 180px; }
.select { appearance: none; padding-right: var(--sp-8);
  background-image: linear-gradient(45deg, transparent 50%, var(--tx-2) 50%),
                    linear-gradient(135deg, var(--tx-2) 50%, transparent 50%);
  background-position: calc(100% - 16px) center, calc(100% - 11px) center;
  background-size: 5px 5px, 5px 5px; background-repeat: no-repeat; }

.combo { position: relative; width: 100%; }
.combo__native { display: none; }

.combo__btn { display: flex; align-items: center; gap: var(--sp-3);
             text-align: left; cursor: pointer; }
.combo__val { flex: 1 1 auto; min-width: 0; overflow: hidden;
             text-overflow: ellipsis; white-space: nowrap; }

.combo__chev { width: 8px; height: 8px; flex: none;
              border-right: 1.5px solid var(--tx-2); border-bottom: 1.5px solid var(--tx-2);
              transform: rotate(45deg) translate(-2px, -2px);
              transition: transform var(--dur-3) var(--ease-out); }
.combo.is-open .combo__chev { transform: rotate(225deg) translate(-2px, -2px); }
.combo__btn:disabled .combo__chev { border-color: var(--tx-off); }

.combo__panel {
  position: absolute; left: 0; right: 0; top: calc(100% + var(--sp-2));
  z-index: var(--z-popover);
  display: none; grid-template-rows: 0fr; opacity: 0;
  background: var(--bg-2); border: 1px solid var(--bd-control);
  border-radius: var(--r-3); box-shadow: var(--sh-2);
  transition: grid-template-rows var(--dur-3) var(--ease-out),
              opacity var(--dur-3) var(--ease-out),
              display var(--dur-3) allow-discrete;
}
.combo.is-open .combo__panel { display: grid; grid-template-rows: 1fr; opacity: 1; }
@starting-style { .combo.is-open .combo__panel { grid-template-rows: 0fr; opacity: 0; } }

.combo--up .combo__panel { top: auto; bottom: calc(100% + var(--sp-2)); }
.combo__inner { overflow: hidden; }

.combo__list { position: relative; max-height: min(320px, 46vh); overflow-y: auto;
              padding: var(--sp-2); display: grid; gap: 1px; }
.combo__opt { display: flex; align-items: center; gap: var(--sp-3);
             min-height: var(--h-md); padding: var(--sp-2) var(--sp-4);
             border-radius: var(--r-2); cursor: pointer; color: var(--tx-1);
             font: var(--fw-normal) var(--fs-sm)/var(--lh-sm) var(--font-ui); }
.combo__opt:hover { background: var(--sf-1); }

.combo__opt[aria-selected="true"] { background: var(--ac-tint-2); font-weight: var(--fw-medium); }
.combo__opt[aria-disabled="true"] { color: var(--tx-off); cursor: not-allowed; }
.combo__opt[aria-disabled="true"]:hover { background: transparent; }

.combo__grp + .combo__grp { margin-top: var(--sp-2); }
.combo__grpn { padding: var(--sp-2) var(--sp-4) var(--sp-1);
              font: var(--fw-medium) var(--fs-2xs)/1.2 var(--font-ui);
              letter-spacing: .06em; text-transform: uppercase; color: var(--tx-3); }
.combo__grp + .combo__grp .combo__grpn { border-top: 1px solid var(--bd-hairline);
                                      padding-top: var(--sp-3); }
/* Error is carried by the border, the wash AND the message — and the message
   is WIRED to the control with aria-describedby, plus aria-invalid, or the
   third signal exists only for people who can see red. */

.field--err .input, .field--err .textarea, .field--err .select,
.field--err .combo__btn { border-color: var(--err); background-color: var(--err-tint); }
.field__msg { font-size: var(--fs-2xs); color: var(--err); }
.field__msg[hidden] { display: none; }

.field--warn .input, .field--warn .textarea, .field--warn .select,
.field--warn .combo__btn {
  
  border-color: var(--warn); background-color: var(--warn-tint); }
.field--warn .field__msg { color: var(--warn); }

.otp { display: flex; gap: var(--sp-3); }
.otpfield { display: grid; gap: var(--sp-2); }

.otp__cell {
  width: 3ch; height: var(--h-xl); flex: none; padding: 0;
  text-align: center; font: var(--fw-bold) var(--fs-xl)/1 var(--font-mono);
  color: var(--tx-1); background: var(--sf-0);
  border: 1px solid var(--bd-control); border-radius: var(--r-2);
  box-shadow: var(--edge-press);
  transition: border-color var(--dur-1) var(--ease-out), background var(--dur-1) var(--ease-out);
}
.otp__cell:hover { border-color: var(--bd-control-hi); }
.otp__cell:focus { background: var(--sf-1); border-color: var(--ac); outline: 0; }
.otp__cell:focus-visible { outline: var(--focus-w) solid var(--focus); outline-offset: var(--focus-offset); }

.otp__cell[data-filled="1"] { border-color: var(--bd-control-hi); }
.field--err .otp__cell { border-color: var(--err); background: var(--err-tint); }

.orline { display: flex; align-items: center; gap: var(--sp-4); color: var(--tx-3);
          font-size: var(--fs-2xs); text-transform: lowercase; }
.orline::before, .orline::after { content: ""; flex: 1 1 auto; height: 1px; background: var(--bd-edge); }

.inputwrap { position: relative; display: block; }
.inputwrap > .input { padding-right: var(--sp-9); }
.inputwrap__btn {
  position: absolute; right: var(--sp-2); top: 50%; transform: translateY(-50%);
  display: inline-grid; place-items: center; width: var(--h-md); height: var(--h-md);
  border: 0; border-radius: var(--r-2); background: transparent; color: var(--tx-3);
  cursor: pointer;
}
.inputwrap__btn:hover { color: var(--tx-1); background: var(--sf-1); }

.inputwrap--text { display: flex; align-items: center; gap: var(--sp-3); }
.inputwrap--text > .input { flex: 1 1 auto; min-width: 0; padding-right: var(--sp-4); }
.inputwrap__btn:focus-visible { outline: var(--focus-w) solid var(--focus); outline-offset: var(--focus-offset); }

.ico--eye::before { content: ""; position: absolute; inset: 0; margin: auto;
  width: 1em; height: .58em; border: 1.5px solid currentColor;
  border-radius: 100% 0 100% 0 / 60% 0 60% 0; transform: rotate(45deg); }
.ico--eye::after { content: ""; position: absolute; inset: 0; margin: auto;
  width: .26em; height: .26em; border-radius: 50%; background: currentColor; }
.inputwrap__btn[aria-pressed="true"] { color: var(--ac); }

.helpdot {
  display: inline-grid; place-items: center; width: 16px; height: 16px;
  padding: 0; margin-left: var(--sp-2); vertical-align: -.16em;
  border: 1px solid var(--bd-control); border-radius: var(--r-pill);
  background: transparent; color: var(--tx-3); cursor: pointer;
  font: var(--fw-bold) 10px/1 var(--font-ui);
}
.helpdot:hover { color: var(--tx-1); border-color: var(--bd-control-hi); }
.helpdot:focus-visible { outline: var(--focus-w) solid var(--focus); outline-offset: var(--focus-offset); }

.pwmeter { display: flex; align-items: center; gap: var(--sp-3); }
.pwmeter[hidden] { display: none; }
.pwmeter__dot { width: 8px; height: 8px; flex: none; border-radius: var(--r-pill);
                background: var(--bd-control); transition: background var(--dur-1) var(--ease-out); }
.pwmeter__label { font-size: var(--fs-2xs); color: var(--tx-3); }
.pwmeter[data-lvl="1"] .pwmeter__dot:nth-child(1) { background: var(--err); }
.pwmeter[data-lvl="1"] .pwmeter__label { color: var(--err); }
.pwmeter[data-lvl="2"] .pwmeter__dot:nth-child(-n+2) { background: var(--warn); }
.pwmeter[data-lvl="2"] .pwmeter__label { color: var(--warn); }
.pwmeter[data-lvl="3"] .pwmeter__dot { background: var(--ok); }
.pwmeter[data-lvl="3"] .pwmeter__label { color: var(--ok); }
.field__count { font-family: var(--font-mono); font-size: var(--fs-2xs); color: var(--tx-3); justify-self: end; }
.field__count--over { color: var(--err); }
/* A bounded number: the floor is expressed by the control, not checked after
   the fact. Contract test T3 — "cannot be expressed" beats "we validate". */
.input--num { max-width: 12ch; }

/* ============================================================================
   9. TOGGLE
   ========================================================================= */
.toggle { display: inline-flex; align-items: center; gap: var(--sp-4); cursor: pointer; user-select: none; }
.toggle__track {
  width: 40px; height: 22px; flex: none; border-radius: var(--r-pill);
  background: var(--sf-0); border: 1px solid var(--bd-control);
  position: relative; transition: background var(--dur-2) var(--ease-out), border-color var(--dur-2) var(--ease-out);
}
.toggle__track::after {
  content: ""; position: absolute; inset: 2px auto 2px 2px; width: 16px;
  border-radius: var(--r-pill); background: var(--tx-2);
  transition: transform var(--dur-2) var(--ease-out), background var(--dur-2) var(--ease-out);
}
.toggle input { position: absolute; opacity: 0; width: 0; height: 0; }
.toggle input:checked + .toggle__track { background: var(--ac-tint-3); border-color: var(--ac); }
.toggle input:checked + .toggle__track::after { transform: translateX(18px); background: var(--ac); }
.toggle input:focus-visible + .toggle__track { outline: var(--focus-w) solid var(--focus); outline-offset: var(--focus-offset); }
/* Disabled dims the track AND the words. WCAG exempts disabled from contrast,
   but a half-faded switch beside full-strength text reads as a rendering bug. */
.toggle input:disabled + .toggle__track { opacity: .5; }
.toggle input:disabled ~ .t-label { color: var(--tx-off); }

/* ============================================================================
   10. SLIDER — named, with a live value. 9 slots.
   The value is live for EVERY slider in the product, wired once by class in
   the kit page script — never per-instance by id, which is what v1 did (one
   of five sliders moved its number; the other four were painted text).
   ========================================================================= */
.slider { display: grid; gap: var(--sp-2); }
.slider__head { display: flex; align-items: baseline; justify-content: space-between; gap: var(--sp-4); }
.slider__val { font-family: var(--font-mono); font-size: var(--fs-xs); color: var(--tx-1);
               font-variant-numeric: tabular-nums; }
.slider input[type="range"] { -webkit-appearance: none; appearance: none; width: 100%; height: 20px; background: transparent; }
.slider input[type="range"]::-webkit-slider-runnable-track {
  height: 4px; border-radius: var(--r-1); background: var(--sf-0); border: 1px solid var(--bd-edge); }
.slider input[type="range"]::-moz-range-track {
  height: 4px; border-radius: var(--r-1); background: var(--sf-0); border: 1px solid var(--bd-edge); }
.slider input[type="range"]::-webkit-slider-thumb {
  -webkit-appearance: none; width: 14px; height: 14px; margin-top: -6px;
  border-radius: var(--r-pill); background: var(--ac); border: 1px solid var(--ac-press);
  box-shadow: var(--sh-1); cursor: grab; }
.slider input[type="range"]::-moz-range-thumb {
  width: 14px; height: 14px; border-radius: var(--r-pill);
  background: var(--ac); border: 1px solid var(--ac-press); cursor: grab; }
.slider input[type="range"]:focus-visible { outline: var(--focus-w) solid var(--focus); outline-offset: var(--focus-offset); }
/* An anchor mark, e.g. "the model's recommended value" — a competitor trick
   worth keeping: it tells the user where sane is without forbidding anything. */
.slider__anchor { position: relative; height: 14px; }
.slider__anchor i { position: absolute; top: -10px; left: var(--at, 50%);
                    width: 1px; height: 6px; background: var(--tx-3); }
.slider__anchor b { position: absolute; top: -2px; left: var(--at, 50%); transform: translateX(-50%);
                    font: var(--fw-normal) var(--fs-3xs)/1 var(--font-ui); color: var(--tx-3);
                    white-space: nowrap; }

/* ============================================================================
   11. CONTAINERS — card, group, accordion
   ========================================================================= */
.card {
  background: var(--bg-1); border: 1px solid var(--bd-edge); border-radius: var(--r-3);
  padding: var(--sp-6); display: grid; gap: var(--sp-5);
  
  align-content: start;
}
.card--raised { background: var(--bg-2); box-shadow: var(--sh-1); }

.card > .t-section:first-child {
  margin: calc(var(--sp-6) * -1) calc(var(--sp-6) * -1) 0;
  padding: var(--sp-6);
  border-bottom: 1px solid var(--bd-edge);
}
/* A card that holds a flush list or a flush image: the kit owns this, so a
   screen never writes style="padding:0;overflow:hidden" — v1's own demo did. */
.card--flush { padding: 0; overflow: hidden; gap: 0; }

/* FIX (density critic): direction A leant on a background step of 1.11:1 for
   hierarchy, which is not a signal. A second carrier is added — a 2px accent
   rule on the group edge — so nesting reads by FORM, not by value alone.
   The legend IS the head: it must be the first child of the fieldset or the
   browser stops treating it as the group's name. v1 wrapped it in a div and
   still carried a comment promising the opposite. */
.group { border: 0; padding: 0; margin: 0; display: grid; gap: var(--sp-4); }
.group__head { display: block; float: none;
               padding: 0 0 0 var(--sp-4); margin-bottom: var(--sp-1);
               border-left: 2px solid var(--bd-control); }
.group--accent > .group__head { border-left-color: var(--ac); }
.group__body { display: grid; gap: var(--sp-4); padding-left: var(--sp-4); }
/* The grid a parameter panel is laid out on. It belongs to the kit, not to a
   screen. The column gap is deliberately wide: a right-aligned slider value
   sitting 6px from the next column's left-aligned label reads as one phrase
   ("0.80 Clip skip") — measured on the forty-knob panel. */
/* A field that needs the whole row. The knobs grid is sized for "28" and "4.5"; a fifteen-digit
   seed in one of those cells is a number you cannot read to the end. */
.knobs > .field--wide { grid-column: 1 / -1; }
/* and the control inside it stretches too — .numctl--wide caps itself at 26ch, which is the
   right cap for a number of steps and the wrong one for a fifteen-digit seed. */
.field--wide .numctl { max-width: none; width: 100%; }
.knobs { display: grid; gap: var(--sp-3) var(--sp-8);
         grid-template-columns: repeat(auto-fit, minmax(210px, 1fr)); }

.acc { border: 1px solid var(--bd-edge); border-radius: var(--r-3); background: var(--bg-1);
       overflow: hidden; height: max-content; }
.acc + .acc { margin-top: var(--sp-3); }
.acc__btn {
  width: 100%; display: flex; align-items: center; gap: var(--sp-4);
  height: var(--h-lg); padding-inline: var(--sp-5);
  background: transparent; border: 0; color: var(--tx-1); cursor: pointer;
  font: var(--fw-medium) var(--fs-sm)/1 var(--font-ui); text-align: left;
  transition: background var(--dur-1) var(--ease-out);
}
.acc__btn::-webkit-details-marker { display: none; }
.acc__btn:hover { background: var(--sf-1); }

.acc__sw { flex: none; margin-right: var(--sp-2); }
.acc__sw .toggle__track { cursor: pointer; }

.acc__ttl { flex: 1 1 auto; min-width: 0; }
.acc__note { color: var(--tx-3); font-weight: var(--fw-normal); }
.acc__note::before { content: " — "; }
.acc__chev { margin-left: auto; width: 8px; height: 8px; flex: none;
             border-right: 1.5px solid var(--tx-2); border-bottom: 1.5px solid var(--tx-2);
             transform: rotate(45deg) translate(-2px, -2px);
             transition: transform var(--dur-3) var(--ease-out); }
.acc[open] .acc__chev { transform: rotate(225deg) translate(-2px, -2px); }
/* The one animation the kit allows. */
.acc__panel { display: grid; grid-template-rows: 0fr; transition: grid-template-rows var(--dur-3) var(--ease-out); }
/* ⚠️ NO SLIDE INSIDE A LEVEL-GATED BLOCK, and it is a correctness rule rather than a taste one.
   A [data-pro] block spends the start of its life in display:none, and an accordion hidden
   before the browser has ever laid it out cannot interpolate this transition afterwards: the row
   parks at zero, so the block stands at the height of its own title while everything inside it
   paints underneath — over whatever follows it on the page. Every one of the seven Pro sections
   did this. Priming the layout first was tried and is NOT reliable: the same clean run passed
   twice and failed once. An animation that is sometimes a broken layout is not worth keeping,
   and at 200ms nobody is going to miss it. */
[data-pro] .acc__panel { transition: none; }
.acc[open] .acc__panel { grid-template-rows: 1fr; }
.acc__inner { overflow: hidden; }
.acc__inner > div { padding: var(--sp-5); border-top: 1px solid var(--bd-edge); display: grid; gap: var(--sp-4); }

/* Rows in a dense list. The divider is --bd-edge, not a hairline: in compact
   it is the ONLY thing separating one generation from the next, and v1 let
   density erase it exactly where the list gets tightest. */
.row { display: flex; align-items: center; gap: var(--sp-4); min-height: var(--h-md);
       padding-inline: var(--sp-4); border-bottom: 1px solid var(--bd-edge);
       border-left: 2px solid transparent;
       transition: background var(--dur-1) var(--ease-out), border-color var(--dur-1) var(--ease-out); }
.row:last-child { border-bottom: 0; }

.row:where([aria-selected], [role="option"], [role="button"], [tabindex], [href]):hover,
.row--link:hover,
:where(a, button, label, summary) > .row:hover {
  background: var(--row-hover); border-left-color: var(--bd-control);
}

.row--link { position: relative; }
.row--link a[href]::after { content: ""; position: absolute; inset: 0; }

.row--link :where(button, input, select, label, summary) { position: relative; z-index: 1; }
.row[aria-selected="true"] { background: var(--ac-tint-1); border-left-color: var(--ac); }

.row__st { flex: none; min-width: var(--row-st-w); display: flex; align-items: center; }

.row--wrap { flex-wrap: wrap; }

.facts { display: grid; }
.facts > .fact { border-bottom: 1px solid var(--bd-edge); }
.facts > .fact:last-child { border-bottom: 0; }
.fact { display: flex; align-items: center; gap: var(--sp-4);
        min-height: var(--h-md); padding-inline: var(--sp-4); }

.card:not(.card--flush) .fact:not(.card--flush *) { padding-inline: 0; }
/* ============================================================================
   12. GATE — the paywall model, grafted whole from direction B.
   ONE paid tier, and it is called Premium. v1 printed "Pro" three times and
   "Premium" once — the kit is where the product's vocabulary is set, so a
   second name for one tier would have been copied onto every screen.
   Three different "you can't", and they must never look the same:
     .gate-badge  — this exists but costs money       (violet, informative)
     .chip--limit — you could, but the limit is full  (control-weight border
                    kept + the group counter turns warn; the dashed label was
                    removed 2026-08-26, see the note at .chip--limit)
     [disabled]   — not applicable here at all        (recessed, edge only)
   ========================================================================= */
.gate-badge {
  display: inline-flex; align-items: center; height: var(--h-xs);
  padding-inline: var(--sp-3); border-radius: var(--r-1);
  background: var(--gate-tint); border: 1px solid var(--gate); color: var(--gate);
  /* Text, never a 9px icon literal — that was a fatal finding against B. */
  font: var(--fw-bold) var(--fs-2xs)/1 var(--font-ui);
  text-transform: uppercase; letter-spacing: var(--ls-caps);
}
/* Tint on tint composites to #3E3548 and drops 11px bold text to 4.20:1.
   Nested, the badge drops its own wash and lets the border carry it: 5.28:1. */
.gate-note .gate-badge, .chip--gated .gate-badge { background: transparent; }

.gate-note { display: block;
             padding: var(--sp-4); border-radius: var(--r-2);
             background: var(--gate-tint); border: 1px solid var(--gate); color: var(--tx-1);
             font-size: var(--fs-xs); line-height: var(--lh-xs); }

.gate-note > .gate-badge, .gate-note > .btn, .gate-note > .badge,
.gate-note > .ico, .gate-note > .counter { vertical-align: middle; }
.gate-note > .gate-badge, .gate-note > .btn { margin-left: var(--sp-3); }

/* ============================================================================
   13. FEEDBACK — badge, toast, empty state, progress
   ========================================================================= */

.badge { display: inline-flex; align-items: center; height: var(--h-xs); padding-inline: var(--sp-3);
         border-radius: var(--r-1); font-size: var(--fs-2xs); font-weight: var(--fw-medium);
         border: 1px solid currentColor; flex: none; white-space: nowrap; }
.badge--ok { color: var(--ok); background: var(--ok-tint); }
.badge--warn { color: var(--warn); background: var(--warn-tint); }
.badge--err { color: var(--err); background: var(--err-tint); }
.badge--neutral { color: var(--tx-2); background: var(--sf-0); border-color: var(--bd-edge); }

.toast { display: flex; align-items: flex-start; gap: var(--sp-4);
         padding: var(--sp-4) var(--sp-5); border-radius: var(--r-2);
         background: var(--bg-2); border: 1px solid var(--bd-control);
         box-shadow: var(--sh-2); max-width: 460px; }
.toast--ok { border-left: 3px solid var(--ok); }
.toast--warn { border-left: 3px solid var(--warn); }
.toast--err { border-left: 3px solid var(--err); }

.empty { display: grid; gap: var(--sp-4); place-items: center; align-content: center; text-align: center;
         padding: var(--sp-10) var(--sp-6); border: 1px dashed var(--bd-edge);
         border-radius: var(--r-3); color: var(--tx-3); }

.progress { height: 6px; min-width: 96px; border-radius: var(--r-pill); background: var(--sf-0);
            border: 1px solid var(--bd-edge); overflow: hidden; }
/* The filled part is DATA, so it comes in as a custom property, not as a
   style declaration written on a screen. Same trick as .slider__anchor. */
.progress > i { display: block; height: 100%; width: var(--at, 0); background: var(--ac); }

/* ============================================================================
   14. MODAL
   ========================================================================= */
.modal[open] { display: grid; }
.modal { border: 0; padding: 0; background: transparent; max-width: none; max-height: none;
         width: 100%; height: 100%; place-items: center; }
.modal::backdrop { background: rgb(0 0 0 / 0.62); }

.modal__box { width: min(520px, 100%, calc(100vw - var(--sp-8))); background: var(--bg-1);
              border: 1px solid var(--bd-control); border-radius: var(--r-4);
              box-shadow: var(--sh-3); display: grid; }
.modal__head { padding: var(--sp-6); border-bottom: 1px solid var(--bd-edge);
               display: flex; align-items: center; gap: var(--sp-4); }

.modal__x { margin-left: auto; flex: none; }

.modal__head:has(> .push) .modal__x { margin-left: 0; }

.linkbtn { border: 0; padding: 0; background: transparent; color: var(--ac);
           font: inherit; cursor: pointer; text-align: left; }
.linkbtn:hover { text-decoration: underline; }
.linkbtn:focus-visible { outline: var(--focus-w) solid var(--focus); outline-offset: var(--focus-offset); }

.modal__body { padding: var(--sp-6); display: grid; gap: var(--sp-4); align-content: start; }
.modal__foot { padding: var(--sp-5) var(--sp-6); border-top: 1px solid var(--bd-edge);
               display: flex; justify-content: flex-end; gap: var(--sp-4); }

.modal__foot--split { justify-content: space-between; }

.modal__box--tall { height: min(86vh, 900px); grid-template-rows: auto 1fr auto; }
.modal__box--tall .modal__body { min-height: 0; overflow-y: auto; }

.diff { display: grid; border: 1px solid var(--bd-edge); border-radius: var(--r-2);
        overflow: hidden; font: var(--fs-2xs)/1.55 var(--font-mono); }
.diff__f { padding: var(--sp-2) var(--sp-4); background: var(--sf-0); color: var(--tx-3);
           border-bottom: 1px solid var(--bd-edge); }
.diff__l { display: grid; grid-template-columns: 1.1em 1fr; gap: var(--sp-3);
           padding: var(--sp-1) var(--sp-4); color: var(--tx-3); }
.diff__s { color: var(--tx-3); user-select: none; }
.diff__l--add { background: var(--ok-tint); color: var(--tx-1); }
.diff__l--add .diff__s { color: var(--ok); }
.diff__l--del { background: var(--err-tint); color: var(--tx-2); }
.diff__l--del .diff__s { color: var(--err); }

.acc--slim { border-radius: var(--r-2); }
.acc--slim .acc__btn { height: auto; min-height: var(--h-md); padding-block: var(--sp-3); }
.acc--slim + .acc--slim { margin-top: var(--sp-2); }

/* ============================================================================
   15. CANVAS — anything showing a render sits on the neutral well.
   ========================================================================= */

.canvas { background: var(--bg-canvas); border: 1px solid var(--bd-edge);
          border-radius: var(--r-3); display: grid; place-items: center;
          color: var(--tx-off); font-size: var(--fs-xs); overflow: hidden;
          --canvas-cap: min(52vh, 460px);
          max-height: var(--canvas-cap); }
.canvas img { width: 100%; height: 100%; object-fit: cover; display: block; }
/* A picture you can open is a button, and a button arrives with its own padding, font and text
   colour. Strip those so it stays the same box, and add the one thing that should differ. */
button.canvas { padding: 0; font: inherit; color: inherit; text-align: inherit; cursor: zoom-in; }
/* A generation must be viewable whole, not cropped to a box. Cover is right
   for a thumbnail, contain is right for the thing the user just paid for. */
.canvas--fit img { object-fit: contain; background: var(--bg-canvas); }

/* ============================================================================
   16. SIMULATION BANNER — mandated by the plan. Never removed, never subtle.
   ========================================================================= */
.sim {
  position: sticky; top: 0; z-index: var(--z-sticky);
  display: flex; align-items: center; gap: var(--sp-4);
  height: var(--h-sm); padding-inline: var(--sp-5);
  background: repeating-linear-gradient(135deg, var(--warn-tint) 0 10px, transparent 10px 20px), var(--bg-2);
  border-bottom: 1px solid var(--warn);
  color: var(--warn); font: var(--fw-bold) var(--fs-2xs)/1 var(--font-ui);
  text-transform: uppercase; letter-spacing: var(--ls-caps);
}

.sim__map {
  margin-left: auto; flex: none; color: var(--warn);
  border: 1px solid var(--warn); border-radius: var(--r-1);
  padding: 2px var(--sp-4); text-decoration: none; white-space: nowrap;
}
.sim__map:hover { background: var(--warn-tint); text-decoration: none; }
.sim__map:focus-visible { outline: var(--focus-w) solid var(--focus); outline-offset: var(--focus-offset); }

/* ============================================================================
   17. LAYOUT HELPERS — the only layout the kit owns. Screens add nothing.
   The three utilities at the bottom exist because v1's own demo page wrote
   style="padding:0", style="margin-left:auto" and style="aspect-ratio:…"
   inline — the kit broke its own rule inside its own demonstration.
   ========================================================================= */
/* align-content:start — a stack keeps its own rhythm even when it is a grid
   item next to something tall. Without it three placeholder lines beside a
   16:9 canvas get spread down the whole column like a ransom note. */
.stack   { display: grid; gap: var(--sp-5); align-content: start; }
.stack-s { display: grid; gap: var(--sp-3); align-content: start; }
.rowflex { display: flex; align-items: center; gap: var(--sp-4); flex-wrap: wrap; }

/* Back on the left, the view switch dead centre, nothing on the right. A plain space-between
   would hang the switch off centre and move it again the moment the back button's label changed
   length; three tracks keep it put whatever sits beside it. */
.pbar { display: grid; grid-template-columns: 1fr auto 1fr; align-items: center; gap: var(--sp-4); }
.pbar > :first-child { justify-self: start; }
.pbar > :last-child  { justify-self: end; }

/* The bar takes whatever is left between its label and its number. */
.progress--inline { flex: 1 1 120px; }

/* Sits exactly where the "default" badge used to, and only while the pointer is on the row: on a
   list whose whole job is "pick one to edit", an Edit printed on every line all the time is noise
   that says nothing the list did not already say. */
.row__edit { opacity: 0; transition: opacity var(--dur-1) var(--ease-out); }
.row:hover .row__edit,
.row:focus-within .row__edit { opacity: 1; }
/* No pointer to hover with, so it simply stays. */
@media (pointer: coarse) { .row__edit { opacity: 1; } }
.spread  { display: flex; align-items: center; justify-content: space-between; gap: var(--sp-4); }
/* Panels use grid, never column-count: fragmenting a card across a column
   break was a real defect in the winning direction. */
.cols    { display: grid; gap: var(--sp-5); grid-template-columns: repeat(auto-fit, minmax(240px, 1fr)); }

.cols--wide { grid-template-columns: repeat(auto-fit, minmax(320px, 1fr)); }

.cols--top { align-items: start; }

.wrap    { max-width: 1180px; margin-inline: auto; padding: var(--sp-8) var(--sp-6); }
.push    { margin-left: auto; }

.push--mid { margin-right: auto; }
.pad-y   { padding-block: var(--sp-8); }

.stack-xs{ display: grid; gap: 2px; align-content: start; min-width: 0; }

.ellipsis{ overflow: hidden; text-overflow: ellipsis; white-space: nowrap; min-width: 0; }
.pad     { padding: var(--sp-4); }
.pad-s   { padding: var(--sp-3); }
.flush   { padding: 0; }
.canvas--sm { max-width: 220px; }
.w-60    { width: 60%; }
.center  { display: grid; place-items: center; }

.ar-1    { aspect-ratio: 1;      --ar-w: 1; }
.ar-3-4  { aspect-ratio: 3 / 4;  --ar-w: 0.75; }
.ar-16-9 { aspect-ratio: 16 / 9; --ar-w: 1.7778; }
.measure { max-width: 64ch; }

.ph { display: grid; align-content: center; justify-items: center;
      gap: 2px; text-align: center; }
.ph__note { color: var(--tx-off); opacity: .8; }

/* ############################################################################
   ############################################################################
   PART TWO — COMPOSITION (kit v2)
   ----------------------------------------------------------------------------
   Part one is the ORGANS: button, chip, tile, field, slider. Part two is the
   BODY they live in: frame, navigation, cards, conversation, overlays, tables.
   The audit that closed v1 found this gap in one sentence — "the kit owns
   .stack/.rowflex/.spread/.cols/.wrap and nothing else, so every screen would
   have had to invent its own layout", which is the one thing a stencil may
   never do.

   Same rules apply, unchanged: one accent · form never carried by a value step
   alone · two signals per state · no icon SETS (the glyphs below are drawn in
   CSS from the same borders everything else uses) · no animation beyond
   disclosure and hover.
   ############################################################################
   ######################################################################### */

/* ============================================================================
   18. ICON — a primitive, not a set.
   Six shapes cover the whole product. Each is built from borders and gradients
   the kit already owns, sized in em so it rides the text it sits in, and it
   inherits currentColor so it can never introduce a colour of its own.
   ========================================================================= */
.ico { display: inline-block; position: relative; width: 1em; height: 1em; flex: none;
       vertical-align: -0.12em; color: inherit; }
.ico--chev::before { content: ""; position: absolute; inset: 0; margin: auto;
       width: .42em; height: .42em; border-right: 1.5px solid currentColor;
       border-bottom: 1.5px solid currentColor; transform: rotate(45deg) translate(-.06em, -.06em); }
.ico--close::before, .ico--close::after { content: ""; position: absolute; inset: 0; margin: auto;
       width: .82em; height: 1.5px; background: currentColor; }
.ico--close::before { transform: rotate(45deg); }
.ico--close::after  { transform: rotate(-45deg); }
.ico--plus::before, .ico--plus::after { content: ""; position: absolute; inset: 0; margin: auto;
       width: .82em; height: 1.5px; background: currentColor; }
.ico--plus::after { transform: rotate(90deg); }
.ico--dots::before { content: ""; position: absolute; inset: 0; margin: auto;
       width: 1.5px; height: .82em;
       background: linear-gradient(currentColor 0 1.5px, transparent 1.5px 100%) 0 0 / 100% .34em repeat-y; }
.ico--search::before { content: ""; position: absolute; top: .04em; left: .04em;
       width: .62em; height: .62em; border: 1.5px solid currentColor; border-radius: 50%; }
.ico--search::after { content: ""; position: absolute; right: .1em; bottom: .1em;
       width: .3em; height: 1.5px; background: currentColor; transform: rotate(45deg); }
.ico--check::before { content: ""; position: absolute; inset: 0; margin: auto;
       width: .32em; height: .6em; border-right: 1.5px solid currentColor;
       border-bottom: 1.5px solid currentColor; transform: rotate(45deg) translate(-.04em, -.08em); }

.ico--bell::before { content: ""; position: absolute; left: .14em; top: .04em;
       width: .72em; height: .6em; border: 1.5px solid currentColor;
       border-radius: .36em .36em .08em .08em; }
.ico--bell::after { content: ""; position: absolute; left: .42em; top: .72em;
       width: .16em; height: .16em; background: currentColor; border-radius: 50%; }

.ico--dot::before { content: ""; position: absolute; inset: 0; margin: auto;
       width: .42em; height: .42em; border-radius: 50%; background: currentColor; }
.ico--flip { transform: rotate(180deg); }

.ico--left  { transform: rotate(90deg); }
.ico--right { transform: rotate(-90deg); }

[data-sort] .ico--chev { transition: transform var(--dur-2) var(--ease-out); }
[data-sort="asc"] .ico--chev { transform: rotate(180deg); }

.ico--dice::before { content: ""; position: absolute; inset: .06em;
       border: 1.5px solid currentColor; border-radius: .2em; }

.ico--dice::after { content: ""; position: absolute; top: 50%; left: 50%;
       width: 2px; height: 2px; margin: -1px 0 0 -1px; border-radius: 50%;
       background: currentColor;
       box-shadow: -.2em -.2em 0 0 currentColor, .2em .2em 0 0 currentColor; }
.numctl__dice .ico { font-size: 15px; }

/* A square button that holds exactly one glyph. It still needs a name — the
   markup carries aria-label, because a glyph is not a word. */
.btn--icon { width: var(--h-md); padding-inline: 0; }
.btn--icon.btn--sm { width: var(--h-sm); }

.ico--grid::before { content: ""; position: absolute; left: .1em; top: .1em;
       width: .3em; height: .3em; background: currentColor; border-radius: 1px;
       box-shadow: .5em 0 0 currentColor, 0 .5em 0 currentColor, .5em .5em 0 currentColor; }

.ico--chat::before { content: ""; position: absolute; left: .06em; top: .1em;
       width: .88em; height: .58em; border: 1.5px solid currentColor; border-radius: .22em; }
.ico--chat::after { content: ""; position: absolute; left: .24em; top: .64em;
       width: 0; height: 0;
       border-left: .1em solid transparent; border-right: .1em solid transparent;
       border-top: .18em solid currentColor; }
.ico--user::before { content: ""; position: absolute; left: .3em; top: .08em;
       width: .4em; height: .4em; border: 1.5px solid currentColor; border-radius: 50%; }
.ico--user::after { content: ""; position: absolute; left: .14em; top: .58em;
       width: .72em; height: .42em; border: 1.5px solid currentColor; border-bottom: 0;
       border-radius: .36em .36em 0 0; }

.ico--spark::before { content: ""; position: absolute; left: .12em; top: .1em;
       width: .46em; height: .46em; background: currentColor;
       clip-path: polygon(50% 0, 62% 38%, 100% 50%, 62% 62%, 50% 100%, 38% 62%, 0 50%, 38% 38%); }
.ico--spark::after { content: ""; position: absolute; right: .08em; bottom: .1em;
       width: .3em; height: .3em; background: currentColor;
       clip-path: polygon(50% 0, 62% 38%, 100% 50%, 62% 62%, 50% 100%, 38% 62%, 0 50%, 38% 38%); }

.ico--tune::before { content: ""; position: absolute; left: .08em; top: .3em;
       width: .84em; height: 1.5px; background: currentColor;
       box-shadow: 0 .3em 0 currentColor; }
.ico--tune::after { content: ""; position: absolute; left: .24em; top: .22em;
       width: .18em; height: .18em; border-radius: 50%; background: currentColor;
       box-shadow: .34em .3em 0 currentColor; }
.ico--box::before { content: ""; position: absolute; left: .08em; top: .22em;
       width: .84em; height: .66em; border: 1.5px solid currentColor; border-radius: .08em; }

.ico--box::after { content: ""; position: absolute; left: .08em; top: .22em;
       width: .84em; height: .22em; background: currentColor;
       border-radius: .08em .08em 0 0; }

.ico--cup::before { content: ""; position: absolute; left: .16em; top: .1em;
       width: .68em; height: .36em; border: 1.5px solid currentColor;
       border-radius: .05em .05em .3em .3em; }
.ico--cup::after { content: ""; position: absolute; left: .44em; top: .48em;
       width: .12em; height: .2em; background: currentColor;
       box-shadow: 0 .2em 0 currentColor, -.12em .2em 0 currentColor, .12em .2em 0 currentColor; }

.ico--gear::before { content: ""; position: absolute; inset: .06em;
       border: 2px dotted currentColor; border-radius: 50%; }
.ico--gear::after { content: ""; position: absolute; inset: 0; margin: auto;
       width: .26em; height: .26em; border: 1.5px solid currentColor; border-radius: 50%; }

/* ============================================================================
   19. APP FRAME — the thing every screen sits inside.
   Header is sticky, the rail is --panel-w (the token v1 declared and never
   used), the main column scrolls. A screen picks a layout class; a screen
   never writes grid-template-areas of its own.
   ========================================================================= */
.app { min-height: 100vh; display: grid; grid-template-rows: auto 1fr; background: var(--bg-0); }
/* The same frame shown inside a bordered stage — used by the kit page and by
   any screen that embeds a preview of another screen. */
.app--embed { min-height: 0; border: 1px solid var(--bd-edge); border-radius: var(--r-3); overflow: hidden; }
.app--embed .appbar { position: static; }

.appbar {
  position: sticky; top: var(--h-sm); z-index: var(--z-sticky);
  display: flex; align-items: center; gap: var(--sp-5);
  height: var(--h-xl); padding-inline: var(--sp-6);
  background: var(--bg-1); border-bottom: 1px solid var(--bd-edge);
}

/* z-index: auto matters as much as the position here. .appbar sets z-index: var(--z-sticky) for
   the real sticky bar, and this modifier used to reset only the position — but z-index applies to
   a GRID ITEM even at position: static, and this bar is a grid item of .split__main. So it kept
   claiming the sticky level, tied with the page's own sticky app bar, and being later in the
   document it won the tie: the user menu opening out of the top bar was painted UNDER the chat
   header. Nothing here needs a layer of its own — it does not overlap anything. */
.appbar--static { position: static; z-index: auto; }

.appbar__burger {
  flex: none; display: inline-grid; place-items: center;
  width: var(--h-md); height: var(--h-md);
  
  padding: 0; border: 0; border-radius: var(--r-2); background: transparent;
  color: var(--tx-2); cursor: pointer;
  transition: color var(--dur-1) var(--ease-out), background var(--dur-1) var(--ease-out);
}
.appbar__burger:hover { color: var(--tx-1); background: var(--sf-1); }

.appbar__burger[aria-expanded="true"] {
  width: auto; gap: var(--sp-2); padding-inline: var(--sp-3);
  grid-auto-flow: column; place-items: center;
  background: var(--ac-tint-2); color: var(--tx-1);
  box-shadow: inset 2px 0 0 var(--ac), inset -2px 0 0 var(--ac);
}

.appbar__burger .ico--left { display: none; font-size: 14px; }
.appbar__burger[aria-expanded="true"] .ico--left { display: inline-block; }
.appbar__burger:focus-visible { outline: var(--focus-w) solid var(--focus); outline-offset: var(--focus-offset); }
.appbar__burger .ico { font-size: 18px; }

.appbar__brand { font-family: var(--font-ui-display); font-weight: var(--fw-bold);
                 font-size: var(--fs-md); letter-spacing: var(--ls-tight); color: var(--tx-1); }
.appbar__brand b { color: var(--ac); font-weight: inherit; }

.rail__item {
  display: flex; align-items: center; gap: var(--sp-3); width: 100%;
  min-height: var(--h-md); padding: var(--sp-2) var(--sp-4);
  border: 0; border-left: 2px solid transparent; border-radius: var(--r-2);
  background: transparent; color: var(--tx-2); text-align: left;
  font: var(--fw-medium) var(--fs-sm)/1.25 var(--font-ui); cursor: pointer; text-decoration: none;
  
  transition: background var(--dur-1) var(--ease-out), color var(--dur-1) var(--ease-out),
              border-color var(--dur-2) var(--ease-out);
}
.rail__item:hover { background: var(--sf-1); color: var(--tx-1); }
.rail__item[aria-current] { color: var(--tx-1); font-weight: var(--fw-bold);
                            border-left-color: var(--ac); background: var(--ac-tint-1); }

/* Vertical rail, used by the settings screen and the constructor. */

.rail { display: grid; gap: var(--sp-1); align-content: start; padding: var(--sp-4);
        border-right: 1px solid var(--bd-edge);
        position: sticky; align-self: start;
        top: calc(var(--h-sm) + var(--h-xl) + var(--sp-5));
        max-height: calc(100vh - var(--h-sm) - var(--h-xl) - var(--sp-8));
        overflow-y: auto; }

/* Two-pane: a list on the left, the thing itself on the right. Chat, storage,
   settings and the arena all reduce to this one shape. Below 900px it stacks —
   the stencil is shown on a laptop, but the shape must not be a desktop-only
   trick we discover on a phone later. */
/* The generator's own header: title on the left, the Simple/Pro switch dead centre, nothing on
   the right — three tracks for the same reason .pbar has them, so the switch does not drift when
   the title beside it changes length. */
.genhead { display: grid; grid-template-columns: 1fr auto 1fr; align-items: center;
           gap: var(--sp-4); padding: var(--sp-5) var(--sp-6); }
.genhead > :first-child { justify-self: start; }
.genhead > :last-child  { justify-self: end; }

/* Prompts on the left, the picture on the right — and wrapping rather than a breakpoint. A media
   query asks the WINDOW how wide it is, but what decides here is the middle column, which is the
   window minus the side panel; measured at 1022px wide the query fired and folded a layout that
   had room for both. Flex wrap asks the actual space instead.

   ⚠️ The bases have to SUM to something the middle column can hold. flex-wrap breaks a line when
   the items do not fit at their BASE widths — it does not shrink them first and wrap only if that
   fails. Two 380px bases came to 780 in a 622px column and the picture dropped below, looking
   exactly as if the rule had not applied at all. */
.genmain { display: flex; flex-wrap: wrap; gap: var(--sp-5); align-items: flex-start; }
.genmain > :first-child { flex: 3 1 300px; min-width: 0; }
.genmain > :last-child  { flex: 1 1 280px; min-width: 240px; max-width: 420px; }

.split { display: grid; grid-template-columns: var(--panel-w) 1fr; min-height: 0; }
.split > * { min-width: 0; }
.split__side { border-right: 1px solid var(--bd-edge); display: grid; align-content: start; }

.split__side:has(> .rail) { align-content: stretch; }
.split__main { padding: var(--sp-6); display: grid; gap: var(--sp-5); align-content: start; }

.split__main--flush { padding: 0; gap: 0; }

@media (max-width: 900px) {
  .split { grid-template-columns: 1fr; }
  .split__side { border-right: 0; border-bottom: 1px solid var(--bd-edge); }
}

:root {
  
  --side-icon: 24px;
  --side-w: 220px;
  --side-w-narrow: 80px;
  
  --side-w-compact: 96px;
  
  --side-mode: roomy;

  --side-pad-narrow: calc((var(--side-w-narrow) - 2 * var(--sp-5) - var(--side-icon)) / 2);
}

.shell { display: flex; align-items: flex-start; }
.shell__main { flex: 1 1 auto; min-width: 0; }

.side {
  
  --side-chrome: 320px;
  --side-item-h: clamp(26px, calc((100vh - var(--side-chrome)) / 12), var(--h-lg));
  
  position: sticky; top: calc(var(--h-sm) + var(--h-xl));
  flex: none; width: var(--side-w);
  height: calc(100vh - var(--h-sm) - var(--h-xl));
  display: flex; flex-direction: column;
  padding-block: var(--sp-5);
  background: var(--bg-1); border-right: 1px solid var(--bd-edge);
  
  transition: width var(--dur-4) var(--ease-out);
}
[data-side="narrow"] .side { width: var(--side-w-narrow); --side-chrome: 251px; }

.side__list { display: grid; grid-template-columns: minmax(0, 1fr);
              gap: var(--sp-5); align-content: start;
              padding: var(--sp-5); min-height: 0; overflow-y: auto;
              scrollbar-width: none; }
.side__list::-webkit-scrollbar { width: 0; height: 0; }
.side__group { display: grid; grid-template-columns: minmax(0, 1fr);
               gap: var(--sp-2); list-style: none; margin: 0; padding: 0; }
.side__group + .side__group { border-top: 1px solid var(--bd-hairline); padding-top: var(--sp-5); }

.side__row { position: relative; }

.side__item {
  position: relative;
  display: flex; align-items: center; gap: var(--sp-5);
  width: 100%; height: var(--side-item-h);
  
  padding-inline: var(--sp-5); padding-block: 0;
  border: 0; border-radius: var(--r-2); background: transparent;
  color: var(--tx-2); text-decoration: none; cursor: pointer; text-align: left;
  font: var(--fw-medium) var(--fs-sm)/1 var(--font-ui);
  white-space: nowrap; overflow: hidden;
  
  transition: width var(--dur-4) var(--ease-out),
              padding var(--dur-4) var(--ease-out),
              background var(--dur-1) var(--ease-out),
              color var(--dur-1) var(--ease-out);
}
.side__item:hover { background: var(--sf-1); color: var(--tx-1); }

.side__item[aria-current] { background: var(--ac-tint-2); color: var(--tx-1);
                            font-weight: var(--fw-bold);
                            box-shadow: inset 2px 0 0 var(--ac), inset -2px 0 0 var(--ac); }
[data-side="narrow"] .side__item { width: calc(var(--side-w-narrow) - 2 * var(--sp-5));
                                  padding-inline: var(--side-pad-narrow); }

.side__item .ico { font-size: clamp(12px, calc(var(--side-item-h) * 0.4), 16px);
                   transition: font-size var(--dur-4) var(--ease-out); }
[data-side="narrow"] .side__item .ico { font-size: clamp(16px, calc(var(--side-item-h) * 0.6), var(--side-icon)); }

/* THE LINE BOX HAS TO BE TALLER THAN THE LETTERS. The item sets `/1` — a line box exactly the
   font-size tall — and this element clips, because that is what makes the ellipsis work. Together
   they cut the tails off g, y, p, j and q: the descender hangs below the content box and there is
   an `overflow: hidden` sitting right on it. The parent is a fixed-height flex row that centres
   its children, so giving the word its own leading costs no layout and no shift. */
.side__word { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
              line-height: 1.5;
              transition: opacity var(--dur-3) var(--ease-out); }
.side__item .side__word { flex: 1 1 auto; }
[data-side="narrow"] .side__word { opacity: 0; pointer-events: none; }

.side__social { display: flex; flex-wrap: wrap; align-items: center; gap: var(--sp-3);
                padding-top: var(--sp-4); margin-top: var(--sp-2);
                border-top: 1px solid var(--bd-hairline); }

.side__social > .t-caps { flex: 1 0 100%; color: var(--tx-3); text-align: center; }
[data-side="narrow"] .side__social { display: none; }
.side__soc {
  display: inline-grid; place-items: center; flex: none;
  width: var(--h-md); height: var(--h-md);
  border-radius: var(--r-2); color: var(--tx-3); text-decoration: none;
  transition: color var(--dur-1) var(--ease-out), background var(--dur-1) var(--ease-out);
}
.side__soc:hover { color: var(--tx-1); background: var(--sf-1); }
.side__soc:focus-visible { outline: var(--focus-w) solid var(--focus); outline-offset: var(--focus-offset); }
.side__soc .ico { font-size: 16px; }

.ico--x::before, .ico--x::after { content: ""; position: absolute; inset: 0; margin: auto;
       width: .86em; height: 2px; background: currentColor; border-radius: 1px; }
.ico--x::before { transform: rotate(45deg); }
.ico--x::after  { transform: rotate(-45deg); }

.ico--bsky::before, .ico--bsky::after { content: ""; position: absolute; top: .16em;
       width: .42em; height: .68em; background: currentColor;
       clip-path: polygon(100% 0, 100% 100%, 0 62%, 18% 18%); }
.ico--bsky::before { left: .06em; }
.ico--bsky::after  { right: .06em; transform: scaleX(-1); }

.ico--deviant::before { content: ""; position: absolute; inset: .04em .14em;
       background: currentColor;
       clip-path: polygon(100% 0, 100% 30%, 55% 100%, 0 100%, 0 70%, 45% 0); }

.ico--bunny::before { content: ""; position: absolute; left: .24em; top: .46em;
       width: .52em; height: .44em; border: 1.5px solid currentColor;
       border-radius: .26em .26em .2em .2em; }
.ico--bunny::after { content: ""; position: absolute; left: .34em; top: .08em;
       width: .12em; height: .38em; background: currentColor; border-radius: .06em;
       box-shadow: .22em 0 0 currentColor; }

.side__foot { margin-top: auto; display: grid; grid-template-columns: minmax(0, 1fr);
              gap: var(--sp-2); padding: var(--sp-5);
              border-top: 1px solid var(--bd-hairline); }
.side__foot > * { min-width: 0; }

.side__tip {
  position: fixed; z-index: var(--z-popover); left: 0; top: 0;
  padding: var(--sp-3) var(--sp-5); border-radius: var(--r-3);
  background: var(--bg-2); border: 1px solid var(--bd-control); box-shadow: var(--sh-2);
  color: var(--tx-1); font: var(--fw-medium) var(--fs-sm)/1 var(--font-ui);
  white-space: nowrap; pointer-events: none;
  opacity: 0; transition: opacity var(--dur-2) var(--ease-out);
}

.side__tip::before {
  content: ""; position: absolute; left: -5px; top: 50%; margin-top: -4px;
  width: 8px; height: 8px; transform: rotate(45deg);
  background: var(--bg-2);
  border-left: 1px solid var(--bd-control); border-bottom: 1px solid var(--bd-control);
}
.side__tip[data-on] { opacity: 1; }

.ico--clock::before { content: ""; position: absolute; inset: .08em;
       border: 1.5px solid currentColor; border-radius: 50%; }
.ico--clock::after { content: ""; position: absolute; left: .5em; top: .28em;
       width: .18em; height: .24em;
       border-left: 1.5px solid currentColor; border-bottom: 1.5px solid currentColor; }

.ico--bank::before { content: ""; position: absolute; left: .16em; top: .14em;
       width: .68em; height: .22em; border: 1.5px solid currentColor; border-radius: 50%; }
.ico--bank::after { content: ""; position: absolute; left: .16em; top: .25em;
       width: .68em; height: .46em; border: 1.5px solid currentColor; border-top: 0;
       border-radius: 0 0 .34em .34em / 0 0 .2em .2em; }

.ico--book::before { content: ""; position: absolute; left: .16em; top: .08em;
       width: .72em; height: .84em; border: 1.5px solid currentColor;
       border-radius: .06em .1em .1em .06em; }
.ico--book::after { content: ""; position: absolute; left: .16em; top: .08em;
       width: .16em; height: .84em; background: currentColor; border-radius: .06em 0 0 .06em; }

.ico--discord::before { content: ""; position: absolute; left: .06em; top: .2em;
       width: .88em; height: .56em; border: 1.5px solid currentColor;
       border-radius: .28em .28em .34em .34em; }
.ico--discord::after { content: ""; position: absolute; left: .3em; top: .42em;
       width: .12em; height: .14em; background: currentColor; border-radius: 50%;
       box-shadow: .28em 0 0 currentColor; }

.ico--support::before { content: ""; position: absolute; left: .12em; top: .14em;
       width: .76em; height: .4em; border: 1.5px solid currentColor; border-bottom: 0;
       border-radius: .38em .38em 0 0; }
.ico--support::after { content: ""; position: absolute; left: .1em; top: .48em;
       width: .16em; height: .3em; background: currentColor; border-radius: .06em;
       box-shadow: .64em 0 0 currentColor; }

.ico--globe::before { content: ""; position: absolute; inset: .06em;
       border: 1.5px solid currentColor; border-radius: 50%; }
.ico--globe::after { content: ""; position: absolute; left: .13em; top: .34em;
       width: .74em; height: 1.5px; background: currentColor;
       box-shadow: 0 .2em 0 currentColor; }

.ico--burger::before { content: ""; position: absolute; left: .08em; top: .22em;
       width: .84em; height: 1.5px; background: currentColor; border-radius: 1px;
       box-shadow: 0 .26em 0 currentColor, 0 .52em 0 currentColor; }

@media (max-width: 1024px) {
  
  :root { --side-mode: compact; }

  .side { width: var(--side-w-narrow); --side-chrome: 251px; }
  .side__item { width: calc(var(--side-w-narrow) - 2 * var(--sp-5));
                padding-inline: var(--side-pad-narrow); }
  .side__item .ico { font-size: clamp(16px, calc(var(--side-item-h) * 0.6), var(--side-icon)); }
  [data-side="narrow"] .side__word { opacity: 0; pointer-events: none; }
  [data-side="narrow"] .side__social { display: none; }

  
  
  [data-side="wide"] .side { width: var(--side-w-compact); --side-chrome-tile: 364px; }
  [data-side="wide"] .side__list { padding: var(--sp-4) var(--sp-3); gap: var(--sp-4); }
  [data-side="wide"] .side__group { gap: 2px; }
  [data-side="wide"] .side__group + .side__group { padding-top: var(--sp-4); }

  [data-side="wide"] .side {
    
    --side-tile-h: clamp(26px, calc((100vh - var(--side-chrome-tile)) / 12), 44px);
  }
  [data-side="wide"] .side__item {
    flex-direction: column; justify-content: center; align-items: center; gap: 2px;
    width: 100%; height: var(--side-tile-h); padding: 2px;
    white-space: normal; text-align: center; overflow: visible;
  }
  [data-side="wide"] .side__item .ico { font-size: clamp(13px, calc(var(--side-tile-h) * 0.42), 18px); }
  [data-side="wide"] .side__item .side__word {
    flex: none; opacity: 1; pointer-events: auto;
    font-size: var(--fs-3xs); line-height: var(--lh-3xs); font-weight: var(--fw-medium);
    white-space: normal; overflow: visible; text-overflow: clip; text-align: center;
  }

  
  [data-side="wide"] .side__foot {
    margin-top: 0; flex: 1 1 auto; display: flex; flex-direction: column;
    padding: var(--sp-4) var(--sp-3);
  }
  
  [data-side="wide"] .side__social {
    display: flex; flex-direction: column; flex-wrap: nowrap; align-items: center;
    margin-top: auto; gap: var(--sp-2);
  }
  [data-side="wide"] .side__social > .t-caps { flex: none; width: 100%; }
  
  [data-side="wide"] .side__soc { width: 24px; height: 24px; }
  [data-side="wide"] .side__soc .ico { font-size: 14px; }
}

.picks { display: flex; flex-wrap: wrap; gap: var(--sp-4); align-items: start; }
.pick  { position: relative; width: 72px; cursor: pointer; display: grid; gap: var(--sp-2); }
.pick input { position: absolute; opacity: 0; width: 0; height: 0; }
.pick__thumb { aspect-ratio: 3 / 4; display: grid; place-items: center;
       background: var(--bg-canvas); border: 1px solid var(--bd-edge); border-radius: var(--r-2);
       color: var(--tx-off); font: var(--fw-medium) var(--fs-2xs)/1 var(--font-ui);
       overflow: hidden; }
.pick__thumb img { width: 100%; height: 100%; object-fit: cover; display: block; }

.pick:has(input:checked) .pick__thumb { outline: var(--focus-w) solid var(--ac); outline-offset: 2px; }

/* THE ROSTER STRIP. The row of cards is the way OUT of this turn into a different one, so it
   belongs at the foot of the column as a row. Two dozen of them stopped being a row — laid out
   free it ran to seven and took more height than the card, the skills and the inventory put
   together, which reads as the main decision on the screen and is not. Capped to two rows and
   scrolled: still a strip, still all twenty-four in it. */
.picks--roster { max-height: 244px; overflow-y: auto; scrollbar-width: thin;
       padding: var(--sp-3) var(--sp-3) var(--sp-3) 0;
       border-top: 1px solid var(--bd-hairline); }

/* THE ADD TILE. Same class, same width, same aspect — so it sits IN the row of frames and travels
   with them as the row wraps, instead of standing apart underneath. Dashed, because that is what
   this build already uses for "something goes here", and each word is its own grid row: at 72px a
   line of prose would either clip or break where it liked. */
/* The frame's place in the running order, in its own corner. Filled with the accent rather than
   outlined: it has to survive being laid over a picture of any colour, and a picture is exactly
   what is behind it. */
.pick__n { position: absolute; top: 4px; right: 4px; z-index: 1;
           min-width: 18px; height: 18px; padding-inline: 4px;
           display: grid; place-items: center; border-radius: var(--r-pill);
           background: var(--ac); color: var(--ac-ink);
           font: var(--fw-bold) var(--fs-2xs)/1 var(--font-mono); }

a.pick--add { text-decoration: none; }
.pick--add .pick__thumb { border-style: dashed; color: var(--tx-2);
                          align-content: center; gap: 2px; line-height: 1.15; padding: var(--sp-2); }
.pick--add:hover .pick__thumb { border-color: var(--ac); color: var(--tx-1); background: var(--sf-0); }
.pick input:focus-visible ~ .pick__thumb { outline: var(--focus-w) solid var(--focus);
       outline-offset: var(--focus-offset); }

.foot { margin-top: var(--sp-10); padding: var(--sp-8) var(--sp-6);
        border-top: 1px solid var(--bd-edge); background: var(--bg-1); }

.foot__info { display: grid; gap: var(--sp-3); justify-items: start; max-width: 68ch;
              color: var(--tx-2); }
.foot__info b { color: var(--tx-1); font-family: var(--font-ui-display);
                font-size: var(--fs-md); letter-spacing: var(--ls-tight); }

.counter--btn { cursor: pointer; border-color: var(--bd-control); }
.counter--btn:hover { background: var(--sf-1); color: var(--tx-1); }
.counter--btn[aria-expanded="true"] { background: var(--sf-2); color: var(--tx-1); border-color: var(--ac); }
.counter--btn:focus-visible { outline: var(--focus-w) solid var(--focus); outline-offset: var(--focus-offset); }

/* ============================================================================
   20. TABS
   ========================================================================= */

.tabs { position: relative; display: flex; gap: var(--sp-1); border-bottom: 1px solid var(--bd-edge); }
.tabs::after {
  content: ""; position: absolute; left: 0; bottom: 0; z-index: 1; pointer-events: none;
  height: 2px; width: var(--ind-w, 0);
  transform: translateX(var(--ind-x, 0px));
  background: var(--ac);
}

.tabs--big { gap: var(--sp-4); }
.tabs--big .tab { height: var(--h-xl); padding-inline: var(--sp-6);
                  font-size: var(--fs-md); letter-spacing: var(--ls-tight); }
.tabs--big::after { height: 3px; }

.tabs--live::after {
  transition: transform var(--dur-2) var(--ease-out), width var(--dur-2) var(--ease-out);
}
.tab {
  height: var(--h-lg); padding-inline: var(--sp-5);
  background: transparent; border: 0; border-bottom: 2px solid transparent;
  color: var(--tx-2); font: var(--fw-medium) var(--fs-sm)/1 var(--font-ui); cursor: pointer;
  transition: color var(--dur-1) var(--ease-out), background var(--dur-1) var(--ease-out);
}
.tab:hover { color: var(--tx-1); background: var(--sf-1); }

.tab[aria-selected="true"] { color: var(--tx-1); font-weight: var(--fw-bold); border-bottom-color: var(--ac); }
.tabs--live .tab[aria-selected="true"] { border-bottom-color: transparent; }

/* ============================================================================
   21. SEARCH
   ========================================================================= */
.search { position: relative; display: flex; align-items: center; }
.search .ico--search { position: absolute; left: var(--sp-4); color: var(--tx-3); pointer-events: none; }
.search .input { padding-left: var(--sp-9); }

.search .input[type="search"]::-webkit-search-cancel-button { -webkit-appearance: none; appearance: none; }
.search__clear {
  position: absolute; right: var(--sp-2);
  display: grid; place-items: center; width: 24px; height: 24px; padding: 0;
  border: 1px solid var(--bd-edge); border-radius: var(--r-2);
  background: var(--sf-1); color: var(--tx-2); cursor: pointer; font-size: var(--fs-2xs);
  transition: border-color var(--dur-1) var(--ease-out), background var(--dur-1) var(--ease-out);
}
.search__clear:hover { border-color: var(--err); background: var(--sf-2); }
.search__clear[hidden] { display: none; }

.search__add { position: absolute; right: 38px; height: calc(var(--h-sm) - 6px); }
.search:has(.search__add:not([hidden])) .input { padding-right: 196px; }
.search:has(.search__clear:not([hidden])):not(:has(.search__add:not([hidden]))) .input { padding-right: 44px; }

/* ============================================================================
   22. AVATAR — never an <img> alone: a fallback letter is part of the
   component, because a missing render must not become an empty hole.
   ========================================================================= */
.avatar { width: var(--h-lg); height: var(--h-lg); flex: none; border-radius: var(--r-2);
          background: var(--bg-canvas); border: 1px solid var(--bd-edge);
          display: grid; place-items: center; overflow: hidden;
          font: var(--fw-bold) var(--fs-sm)/1 var(--font-ui-display); color: var(--tx-3); }
.avatar img { width: 100%; height: 100%; object-fit: cover; display: block; }
.avatar--sm { width: var(--h-md); height: var(--h-md); font-size: var(--fs-2xs); }
.avatar--lg { width: 64px; height: 64px; font-size: var(--fs-lg); }
.avatar--round { border-radius: var(--r-pill); }

/* ============================================================================
   23. CHARACTER CARD — the catalogue unit. Thumb, name, one line of meta,
   badges, and secondary actions that appear on hover but exist for the
   keyboard at all times (focus-within, not hover alone).
   ========================================================================= */

.ccard { position: relative; display: grid; gap: var(--sp-3); align-content: start;
         background: var(--bg-1); border: 1px solid var(--bd-edge); border-radius: var(--r-3);
         padding: var(--sp-3); text-align: left; cursor: pointer; color: inherit;
         transition: border-color var(--dur-1) var(--ease-out), background var(--dur-1) var(--ease-out); }
.ccard:hover, .ccard:focus-within { border-color: var(--bd-control); background: var(--bg-2); }

.ccard__thumb { position: relative; aspect-ratio: 3 / 4; border-radius: var(--r-2); background: var(--bg-canvas);
                display: grid; place-items: center; color: var(--tx-off); font-size: var(--fs-2xs);
                overflow: hidden; }

.ccard__thumb { border: 0; padding: 0; font: inherit; color: var(--tx-off); width: 100%; }
.ccard__thumb img { width: 100%; height: 100%; object-fit: cover; display: block; }
.ccard__name { font-weight: var(--fw-bold); font-size: var(--fs-sm); color: var(--tx-1); }
.ccard__meta { display: flex; align-items: center; gap: var(--sp-3); flex-wrap: wrap; }

.ccard__row { display: flex; align-items: flex-start; gap: var(--sp-3); }
.ccard__row .ccard__name {
  flex: 1 1 auto; min-width: 0;
  white-space: normal; overflow-wrap: anywhere;
  display: -webkit-box; -webkit-box-orient: vertical; -webkit-line-clamp: 2; overflow: hidden;
}

.ccard__acts { display: flex; gap: var(--sp-2); flex: none; }
/* A number with a caption, used for counts on a card and stats on a profile. */
.stat { display: inline-flex; align-items: baseline; gap: var(--sp-2); color: var(--tx-2); font-size: var(--fs-2xs); }
.stat b { font-family: var(--font-mono); font-variant-numeric: tabular-nums; color: var(--tx-1); font-weight: var(--fw-medium); }

.grid-cards { display: grid; gap: var(--sp-5); grid-template-columns: repeat(auto-fill, minmax(200px, 1fr)); }

/* ============================================================================
   24. CONVERSATION — bubble, narrator, composer.
   The character speaks from the left on a raised surface, the user from the
   right on the accent tint. Direction is carried by SIDE and by SURFACE, so
   the two never depend on colour alone. Narration is neither: it is centred,
   italic and unboxed, because it is not somebody talking.
   ========================================================================= */
.thread { display: grid; gap: var(--sp-5); padding: var(--sp-6); align-content: start; }

.thread--tall { max-height: 60vh; overflow-y: auto; overscroll-behavior: contain; }

/* justify-items keeps a bubble the width of its own words: without it every
   message stretches to the column and a three-dot "typing" reads as an empty
   card the size of a paragraph. */
.msg { display: grid; gap: var(--sp-2); max-width: min(62ch, 82%); justify-items: start; }
.msg__body { padding: var(--sp-4) var(--sp-5); border-radius: var(--r-3);
             background: var(--bg-2); border: 1px solid var(--bd-edge); color: var(--tx-1); }
.msg__meta { display: flex; align-items: center; gap: var(--sp-3); color: var(--tx-3); font-size: var(--fs-2xs); }
.msg--me { justify-self: end; justify-items: end; }
.msg--me .msg__body { background: var(--ac-tint-1); border-color: var(--ac-quiet); border-bottom-right-radius: var(--r-1); }
.msg--me .msg__meta { justify-content: flex-end; }
.msg--char .msg__body { border-bottom-left-radius: var(--r-1); }
/* Actions on a message: keyboard-reachable always, visually quiet until asked. */
.msg__acts { display: flex; gap: var(--sp-2); opacity: 0; transition: opacity var(--dur-1) var(--ease-out); }
.msg:hover .msg__acts, .msg:focus-within .msg__acts { opacity: 1; }
@media (pointer: coarse) { .msg__acts { opacity: 1; } }
.narrator { justify-self: center; max-width: 58ch; text-align: center; font-style: italic;
            color: var(--tx-2); font-size: var(--fs-xs); padding-block: var(--sp-2); }
/* Three dots while the character types. This is feedback, not decoration, so
   it is the one place besides disclosure allowed to move. */
.typing { display: inline-flex; gap: 3px; align-items: center; height: var(--lh-sm); }
.typing i { width: 4px; height: 4px; border-radius: var(--r-pill); background: var(--tx-3);
            animation: kit-blink 1.1s infinite; }
.typing i:nth-child(2) { animation-delay: .18s; }
.typing i:nth-child(3) { animation-delay: .36s; }
@keyframes kit-blink { 0%, 60%, 100% { opacity: .3; } 30% { opacity: 1; } }
@media (prefers-reduced-motion: reduce) { .typing i { animation: none; } }

.composer { display: grid; gap: var(--sp-3); padding: var(--sp-5);
            border-top: 1px solid var(--bd-edge); background: var(--bg-1); }
.composer__row { display: flex; align-items: flex-end; gap: var(--sp-4); }
.composer .textarea { min-height: var(--h-lg); }

/* ============================================================================
   25. MENU / POPOVER — the "⋮". Uses the native popover API where present so
   the light-dismiss and the top layer are the browser's job, not ours.
   ========================================================================= */
.menu { position: absolute; z-index: var(--z-popover); min-width: 200px;
        top: calc(100% + var(--sp-2)); right: 0;
        background: var(--bg-2); border: 1px solid var(--bd-control); border-radius: var(--r-3);
        box-shadow: var(--sh-2); padding: var(--sp-2); display: none; gap: 1px; margin: 0; }
.menu.is-open { display: grid; }

.menu--left { right: auto; left: 0; }

.menu--up { top: auto; bottom: calc(100% + var(--sp-2)); }

.menu { transform: translateY(-4px); opacity: 0;
        transition: opacity var(--dur-3) var(--ease-out),
                    transform var(--dur-3) var(--ease-out),
                    display var(--dur-3) allow-discrete; }
.menu--up { transform: translateY(4px); }
.menu.is-open { opacity: 1; transform: none; }
@starting-style {
  .menu.is-open { opacity: 0; transform: translateY(-4px); }
  .menu--up.is-open { transform: translateY(4px); }
}
.menu__item { display: flex; align-items: center; gap: var(--sp-4);
              height: var(--h-md); padding-inline: var(--sp-4); border: 0; border-radius: var(--r-2);
              background: transparent; color: var(--tx-1); cursor: pointer; text-align: left;
              font: var(--fw-normal) var(--fs-sm)/1 var(--font-ui); }
.menu__item:hover { background: var(--sf-1); }

a.menu__item { text-decoration: none; color: var(--tx-1); }
a.menu__item:hover { text-decoration: none; }
.menu__item--danger { color: var(--err); }

.menu__item--warn { color: var(--warn); }
.menu__sep { height: 1px; background: var(--bd-edge); margin: var(--sp-2) 0; }
.anchor { position: relative; }

.menu--credits { min-width: 300px; padding: 0; gap: 0; }
.credits__head { display: grid; gap: var(--sp-3); padding: var(--sp-5); }
.credits__top { display: flex; align-items: baseline; gap: var(--sp-4); }

.credits__now { font: var(--fw-bold) var(--fs-lg)/1 var(--font-mono); color: var(--tx-1); }
.credits__of { font-size: var(--fs-xs); color: var(--tx-3); }

.credits__row { display: flex; align-items: flex-start; gap: var(--sp-4);
                padding: 0 var(--sp-5) var(--sp-4);
                font-size: var(--fs-xs); line-height: var(--lh-sm); color: var(--tx-2); }
.credits__row .ico { margin-top: .1em; color: var(--tx-3); }
.credits__row b { color: var(--tx-1); font-family: var(--font-mono); font-weight: var(--fw-bold); }
.menu--credits .menu__sep { margin: 0; }
.menu--credits .menu__item { height: var(--h-lg); padding-inline: var(--sp-5); border-radius: 0; }
.menu--credits .menu__item:first-of-type { margin-top: var(--sp-2); }
.menu--credits .menu__item:last-child { margin-bottom: var(--sp-2); }

.bell { position: relative; }

.bell__n {
  position: absolute; top: -4px; right: -5px; min-width: 14px; height: 14px; padding: 0 3px;
  border-radius: var(--r-pill); background: var(--err); color: var(--bg-0);
  font: var(--fw-bold) var(--fs-3xs)/14px var(--font-ui); text-align: center;
  pointer-events: none;
}
.menu--notif { min-width: 320px; max-width: 380px; padding: 0; gap: 0; }
.notif__head, .notif__foot {
  display: flex; align-items: center; gap: var(--sp-4);
  padding: var(--sp-4) var(--sp-5);
}
.notif__head { border-bottom: 1px solid var(--bd-edge); }
.notif__foot { border-top: 1px solid var(--bd-edge); }

.notif__item {
  display: grid; grid-template-columns: 8px 1fr auto; gap: var(--sp-4);
  align-items: start; width: 100%;
  padding: var(--sp-4) var(--sp-5);
  border-bottom: 1px solid var(--bd-hairline);
  color: var(--tx-2); font: var(--fw-normal) var(--fs-2xs)/var(--lh-2xs) var(--font-ui);
}
.notif__item:hover { background: var(--sf-1); }
.notif__body {
  display: block; width: 100%; padding: 0; text-align: left; cursor: pointer;
  background: transparent; border: 0; color: inherit; font: inherit;
}

.notif__acts { display: flex; gap: var(--sp-2); opacity: 0;
               transition: opacity var(--dur-1) var(--ease-out); }
.notif__item:hover .notif__acts, .notif__item:focus-within .notif__acts { opacity: 1; }
@media (pointer: coarse) { .notif__acts { opacity: 1; } }

.notif__dot { width: 8px; height: 8px; border-radius: 50%; background: transparent; margin-top: .4em; }
.notif--unread .notif__dot { background: var(--ac); }
.notif--unread .notif__t { color: var(--tx-1); font-weight: var(--fw-medium); }
.notif__t { display: block; }
.notif__time { display: block; color: var(--tx-3); font-size: var(--fs-3xs); margin-top: var(--sp-2); }

/* Tooltip: CSS only, opens on hover AND on keyboard focus. The text lives in
   the markup, so a screen reader gets it via aria-describedby, not from a
   pseudo-element (content: attr() is invisible to most AT). */
.tip { position: absolute; z-index: var(--z-popover); bottom: calc(100% + var(--sp-3)); left: 50%;
       transform: translateX(-50%); width: max-content; max-width: 34ch;
       padding: var(--sp-3) var(--sp-4); border-radius: var(--r-2);
       background: var(--bg-2); border: 1px solid var(--bd-control); box-shadow: var(--sh-2);
       color: var(--tx-1); font-size: var(--fs-2xs); line-height: var(--lh-2xs);
       opacity: 0; pointer-events: none; transition: opacity var(--dur-1) var(--ease-out); }
.anchor:hover > .tip, .anchor:focus-within > .tip { opacity: 1; }

.help { width: 18px; height: 18px; flex: none; padding: 0; display: inline-grid; place-items: center;
        border: 1px solid var(--bd-control); border-radius: var(--r-pill);
        background: transparent; color: var(--tx-2); cursor: pointer;
        font: var(--fw-bold) var(--fs-2xs)/1 var(--font-ui); vertical-align: .05em; }
.help:hover { border-color: var(--ac); color: var(--ac); }
.help[aria-expanded="true"] { background: var(--ac); border-color: var(--ac); color: var(--ac-ink); }
.pop { position: absolute; z-index: var(--z-popover); left: 0; top: calc(100% + var(--sp-3));
       width: max-content; max-width: 40ch;
       background: var(--bg-2); border: 1px solid var(--bd-control); border-radius: var(--r-3);
       box-shadow: var(--sh-2); padding: var(--sp-5); display: none; gap: var(--sp-3); }
.pop.is-open { display: grid; }
.pop--right { left: auto; right: 0; }

.pop--wide { max-width: 56ch; }

.pop--up { top: auto; bottom: calc(100% + var(--sp-3)); }

.pop { transform: translateY(-4px); opacity: 0;
       transition: opacity var(--dur-3) var(--ease-out),
                   transform var(--dur-3) var(--ease-out),
                   display var(--dur-3) allow-discrete; }
.pop--up { transform: translateY(4px); }
.pop.is-open { opacity: 1; transform: none; }
@starting-style {
  .pop.is-open { opacity: 0; transform: translateY(-4px); }
  .pop--up.is-open { transform: translateY(4px); }
}
.pop__head { display: flex; align-items: flex-start; gap: var(--sp-4); }
.pop__title { font-weight: var(--fw-bold); font-size: var(--fs-sm); color: var(--tx-1); }

.pop__close { margin-left: auto; }
.pop__body { font-size: var(--fs-xs); line-height: var(--lh-sm); color: var(--tx-2); }

.numsheet .modal__box { width: min(320px, calc(100vw - var(--sp-8))); }
.numsheet__val { font-family: var(--font-mono); font-size: var(--fs-2xl); line-height: var(--lh-2xl);
                 text-align: center; }
.numsheet__range { text-align: center; }

/* ============================================================================
   26. TOASTER — the fixed stack. --z-toast finally has a consumer.
   ========================================================================= */
.toaster { position: fixed; z-index: var(--z-toast); right: var(--sp-6); bottom: var(--sp-6);
           display: grid; gap: var(--sp-3); justify-items: end; pointer-events: none; }
.toaster > .toast { pointer-events: auto; }

.toaster--top { top: var(--sp-6); bottom: auto; right: auto;
                left: 50%; transform: translateX(-50%); justify-items: center; }

.toast__x { flex: none; margin-left: var(--sp-3); }

.toaster--top .toast { position: relative; padding-inline: var(--sp-9); }
.toaster--top .toast > :first-child { flex: 1 1 auto; text-align: center; }
.toaster--top .toast__x { position: absolute; right: var(--sp-3); top: 50%;
                          transform: translateY(-50%); margin: 0; }

/* ============================================================================
   27. TABLE — the tier grid and any list with columns.
   ========================================================================= */
.table { width: 100%; border-collapse: collapse; font-size: var(--fs-sm); }
.table th, .table td { text-align: left; padding: var(--sp-4) var(--sp-5);
                       border-bottom: 1px solid var(--bd-edge); vertical-align: top; }

.table tr:has(.btn) td, .table tr:has(.btn) th { vertical-align: middle; }
.table th { color: var(--tx-2); font-weight: var(--fw-medium); font-size: var(--fs-xs);
            text-transform: uppercase; letter-spacing: var(--ls-caps); }

.table tbody tr:where([aria-selected], [role="option"], [tabindex], [href]):hover {
  background: var(--row-hover);
}
.table td.num, .table th.num { text-align: right; font-family: var(--font-mono); font-variant-numeric: tabular-nums; }
.table--fixed { table-layout: fixed; }

/* Tier columns. "Current" is carried by the accent edge AND the word, never by
   colour alone; "recommended" is a badge, not a bigger card. */
.tiers { display: grid; gap: var(--sp-5); grid-template-columns: repeat(auto-fit, minmax(220px, 1fr)); }

.tier { display: flex; flex-direction: column; gap: var(--sp-4); padding: var(--sp-6);
        background: var(--bg-1); border: 1px solid var(--bd-edge); border-radius: var(--r-3); }

.tier > .btn:last-child { margin-top: auto; }
.tier--now { border-color: var(--ac); box-shadow: inset 3px 0 0 var(--ac); }
.tier__price { font-family: var(--font-mono); font-size: var(--fs-2xl); line-height: var(--lh-2xl);
               color: var(--tx-1); font-variant-numeric: tabular-nums; }
.tier__price small { font-family: var(--font-ui); font-size: var(--fs-xs); color: var(--tx-3); }
.tier ul { margin: 0; padding: 0; list-style: none; display: grid; gap: var(--sp-3); }
.tier li { display: flex; gap: var(--sp-3); align-items: flex-start; font-size: var(--fs-xs); color: var(--tx-2); }
.tier li .ico { margin-top: .15em; color: var(--ok); }
.tier li.is-off { color: var(--tx-off); }
.tier li.is-off .ico { color: var(--tx-off); }

/* ============================================================================
   28. DROPZONE — file in, thumbnail out.
   ========================================================================= */
.drop { display: grid; place-items: center; gap: var(--sp-3); text-align: center;
        padding: var(--sp-9) var(--sp-6); border: 1px dashed var(--bd-control); border-radius: var(--r-3);
        background: var(--sf-0); color: var(--tx-2); cursor: pointer;
        transition: background var(--dur-1) var(--ease-out), border-color var(--dur-1) var(--ease-out); }
.drop:hover { background: var(--sf-1); border-color: var(--bd-control-hi); }
.drop--over { border-style: solid; border-color: var(--ac); background: var(--ac-tint-1); color: var(--tx-1); }
.thumbs { display: flex; gap: var(--sp-3); flex-wrap: wrap; }
.thumb { position: relative; width: 72px; height: 72px; border-radius: var(--r-2);
         background: var(--bg-canvas); border: 1px solid var(--bd-edge); overflow: hidden;
         display: grid; place-items: center; color: var(--tx-off); font-size: var(--fs-3xs); }
.thumb img { width: 100%; height: 100%; object-fit: cover; }
.thumb__x { position: absolute; top: 2px; right: 2px; width: 18px; height: 18px; padding: 0;
            display: grid; place-items: center; border-radius: var(--r-1);
            background: rgb(0 0 0 / 0.62); border: 1px solid var(--bd-control); color: var(--tx-1);
            font-size: 10px; cursor: pointer; }

/* ============================================================================
   29. COLOUR — a swatch is a component, not a page trick.
   v1 kept the only colour sample in the demo page's own <style>, while the
   kit's first rule says a literal colour may live nowhere else. The value
   arrives as --c, so a screen still writes no colour.
   ========================================================================= */
.color { display: inline-flex; align-items: center; gap: var(--sp-3);
         height: var(--h-md); padding-inline: var(--sp-3) var(--sp-4);
         background: var(--sf-1); border: 1px solid var(--bd-control); border-radius: var(--r-2);
         color: var(--tx-2); font: var(--fw-medium) var(--fs-xs)/1 var(--font-ui); cursor: pointer; }
.color__dot { width: 18px; height: 18px; flex: none; border-radius: var(--r-1);
              background: var(--c, var(--sf-3)); border: 1px solid rgb(255 255 255 / 0.22); }
.color[aria-pressed="true"] { border-color: var(--ac); background: var(--ac-tint-1); color: var(--tx-1); }
.palette { display: flex; gap: var(--sp-3); flex-wrap: wrap; }
.palette .color { padding-inline: var(--sp-3); }

/* ============================================================================
   30. STEPS — the wizard indicator.
   Done / now / ahead are three states, and each carries a shape as well as a
   value: filled + check, filled + number, hollow + number.
   ========================================================================= */
.steps { display: flex; align-items: center; gap: var(--sp-3); flex-wrap: wrap; }
.step { display: inline-flex; align-items: center; gap: var(--sp-3);
        color: var(--tx-3); font-size: var(--fs-xs); }
.step__n { width: 22px; height: 22px; flex: none; display: grid; place-items: center;
           border-radius: var(--r-pill); border: 1px solid var(--bd-control);
           background: transparent; color: var(--tx-2);
           font-family: var(--font-mono); font-size: var(--fs-2xs); }
.step--done .step__n { background: var(--ac-quiet); border-color: var(--ac-quiet); color: var(--ac-ink); }
.step--now  { color: var(--tx-1); font-weight: var(--fw-bold); }
.step--now  .step__n { background: var(--ac); border-color: var(--ac); color: var(--ac-ink); }
.step__bar { width: 28px; height: 1px; background: var(--bd-edge); }
/* A step you can press is a button, and a button arrives with its own chrome. Strip only what
   the UA adds — colour, size and weight are the .step classes' business, and naming any of them
   here would outrank .step--now and quietly flatten the current step. */
button.step { padding: 0; border: 0; background: none; font-family: inherit; cursor: pointer; }
/* And it has to answer the pointer, or it is a cursor change and nothing else. Only the label and
   the ring move — the mark keeps its size, so a strip of five doesn't twitch as you run along it. */
button.step:hover { color: var(--tx-1); }
button.step:hover .step__n { border-color: var(--bd-control-hi); }

/* ============================================================================
   31. PAGER
   ========================================================================= */

.pager { display: flex; align-items: center; gap: var(--sp-2); flex-wrap: wrap; }
.pager__p { min-width: var(--h-md); height: var(--h-md); padding-inline: var(--sp-3);
            display: grid; place-items: center; border-radius: var(--r-2);
            border: 1px solid var(--bd-edge); background: var(--sf-0); color: var(--tx-2);
            font-family: var(--font-mono); font-size: var(--fs-xs); cursor: pointer; }
.pager__p:hover { background: var(--sf-1); border-color: var(--bd-control); color: var(--tx-1); }
.pager__p[aria-current] { background: var(--ac); border-color: var(--ac); color: var(--ac-ink); font-weight: var(--fw-bold); }

/* ============================================================================
   32. SKELETON — a placeholder that does NOT shimmer. The kit allows no
   ambient animation; a static block at the right size says "loading" without
   a strobe next to a picture the user is waiting for.
   ========================================================================= */

.skel { background-color: var(--sf-0); border: 1px solid var(--bd-edge); border-radius: var(--r-2);
        color: transparent; user-select: none;
        background-image: linear-gradient(100deg,
          transparent 18%, rgb(255 255 255 / 0.05) 36%, rgb(255 255 255 / 0.10) 50%,
          rgb(255 255 255 / 0.05) 64%, transparent 82%);
        background-size: 220% 100%; background-repeat: no-repeat;
        animation: kit-sweep 1.5s linear infinite; }
@keyframes kit-sweep { from { background-position: -120% 0; } to { background-position: 220% 0; } }
.skel--still { animation: none; background-image: none; }
@media (prefers-reduced-motion: reduce) { .skel { animation: none; background-image: none; } }
/* Only the LINE takes a width from data; a skeleton laid over a component
   (.thumb.skel, .canvas.skel) must keep that component's own geometry. */
.skel--line { height: var(--lh-sm); width: var(--w, 100%); }

.fade-in { animation: kit-fade var(--dur-3) var(--ease-out) both; }
@keyframes kit-fade { from { opacity: 0; } to { opacity: 1; } }
@media (prefers-reduced-motion: reduce) { .fade-in { animation: none; } }
.skel--line + .skel--line { margin-top: var(--sp-3); }

/* ============================================================================
   33. CONSENT / AGE SHEET — bottom sheet, the first thing a visitor meets.
   Five categories, three buttons, no dark pattern: "reject all" is the same
   size and the same weight as "accept all".
   ========================================================================= */
.sheet { position: fixed; z-index: var(--z-modal); left: 0; right: 0; bottom: 0;
         background: var(--bg-1); border-top: 1px solid var(--bd-control);
         box-shadow: var(--sh-3); padding: var(--sp-6); display: grid; gap: var(--sp-5); }
.sheet__in { width: min(1180px, 100%); margin-inline: auto; display: grid; gap: var(--sp-5); }

.sheet--static { position: static; }

.sheet--narrow {
  left: var(--sp-6); right: auto; bottom: var(--sp-6);
  width: min(560px, calc(100% - var(--sp-6) * 2));
  padding: var(--sp-5);
  border: 1px solid var(--bd-control); border-radius: var(--r-3);
}
.sheet--narrow .sheet__in { width: 100%; margin-inline: 0; }

.sheet--narrow.sheet--static { left: auto; bottom: auto; }
.sheet__acts { display: flex; gap: var(--sp-4); flex-wrap: wrap; }
.sheet__acts .btn { flex: 1 1 180px; }

.btn--wrap { white-space: normal; height: auto; min-height: var(--h-md);
             padding-block: var(--sp-3); line-height: var(--lh-sm); text-align: center; }

/* ============================================================================
   34. MODAL WIDTHS — one size was never going to cover a confirm and an editor.
   ========================================================================= */

.modal__box--sm { width: min(400px, 100%, calc(100vw - var(--sp-8))); }
.modal__box--lg { width: min(840px, 100%, calc(100vw - var(--sp-8))); }

.modal__box--xl { width: min(1160px, 100%, calc(100vw - var(--sp-6))); }

/* ============================================================================
   35. CHIP, PART TWO — removable, and typed rather than picked.
   ========================================================================= */
.chip__x { display: inline-grid; place-items: center; width: 16px; height: 16px; flex: none;
           margin-right: calc(var(--sp-3) * -1 + 2px); border: 0; border-radius: var(--r-1);
           background: transparent; color: inherit; cursor: pointer; font-size: 10px; }
.chip__x:hover { background: rgb(255 255 255 / 0.10); color: var(--tx-1); }
.chipinput { display: flex; align-items: center; gap: var(--sp-3); flex-wrap: wrap;
             min-height: var(--h-lg); padding: var(--sp-3) var(--sp-4);
             background: var(--sf-0); border: 1px solid var(--bd-control); border-radius: var(--r-2);
             box-shadow: var(--edge-press); cursor: text; }
.chipinput:focus-within { border-color: var(--bd-control-hi); background: var(--sf-1); }
.chipinput input { flex: 1 1 8ch; min-width: 8ch; border: 0; background: transparent;
                   color: var(--tx-1); font: var(--fw-normal) var(--fs-sm)/1.4 var(--font-ui); outline: 0; }

.suggest { position: absolute; z-index: var(--z-popover); left: 0; right: 0; top: calc(100% + var(--sp-2));
           background: var(--bg-2); border: 1px solid var(--bd-control); border-radius: var(--r-2);
           box-shadow: var(--sh-2); padding: var(--sp-2); display: grid; gap: 1px; max-height: 220px; overflow: auto; }

.suggest--static { position: static; max-width: 360px;
                   background: transparent; box-shadow: none; }

.suggest__item { display: flex; align-items: center; gap: var(--sp-3); height: var(--h-md);
                 padding-inline: var(--sp-4); border-radius: var(--r-2); cursor: pointer;
                 color: var(--tx-1); font-size: var(--fs-sm); }
.suggest__item:hover, .suggest__item[aria-selected="true"] { background: var(--sf-1); }
.suggest__item mark { background: transparent; color: var(--ac-hover); font-weight: var(--fw-bold); }

.slider__ticks { display: flex; justify-content: space-between; gap: var(--sp-3); }
.slider__ticks span { font-size: var(--fs-3xs); line-height: var(--lh-3xs); color: var(--tx-3); }
.slider__ticks span[aria-current] { color: var(--tx-1); font-weight: var(--fw-bold); }

.modeswap__b { display: none; }
.modeswap:has(.seg__btn[data-mode="build"][aria-pressed="true"]) .modeswap__b--build,
.modeswap:has(.seg__btn[data-mode="line"][aria-pressed="true"]) .modeswap__b--line { display: grid; }

.snip--nosw { grid-template-columns: 14px 1fr auto; }

/* ============================================================================
   37. NUMBER CONTROL — the node-editor move, done our way.
   A number in a parameter panel is touched far more often than it is typed.
   So: minus on the left, plus on the right, and a die where a random value
   actually means something (seed, and only seed-like things).

   Two rules the kit already lives by decide the shape:
     · RESERVE, NEVER REFLOW. The field always carries the padding the two
       buttons will occupy, and the die always occupies its slot. They fade in,
       they never push. This is the same rule as the chip's ordinal box, and
       for the same reason: a panel of forty knobs must not twitch on hover.
     · TWO SIGNALS. Revealed on hover for a mouse, ALWAYS visible on a coarse
       pointer (there is no hover on a phone) and always visible while anything
       inside has keyboard focus.
   The input stays a real <input type="number">, so the native up/down arrows,
   min/max and the numeric keypad on a phone all keep working; only the ugly
   native spinner is hidden.
   ========================================================================= */
.numctl { position: relative; display: inline-flex; align-items: center; gap: var(--sp-2);
          width: 100%; max-width: 18ch; }

.numctl--wide { max-width: 26ch; }
.numctl__field { position: relative; flex: 1 1 auto; min-width: 0; display: flex; }
.numctl .input { width: 100%; text-align: center; padding-inline: var(--sp-7); }
.numctl input[type="number"] { appearance: textfield; -moz-appearance: textfield; }
.numctl input[type="number"]::-webkit-outer-spin-button,
.numctl input[type="number"]::-webkit-inner-spin-button { -webkit-appearance: none; margin: 0; }
.numctl__b {
  position: absolute; top: 2px; bottom: 2px; width: calc(var(--sp-7) - 2px);
  display: grid; place-items: center;
  border: 0; border-radius: var(--r-1); background: transparent; color: var(--tx-2);
  font: var(--fw-bold) var(--fs-md)/1 var(--font-ui); cursor: pointer;
  opacity: 0; transition: opacity var(--dur-1) var(--ease-out), background var(--dur-1) var(--ease-out);
}
.numctl__b--dec { left: 2px; }
.numctl__b--inc { right: 2px; }
.numctl__b:hover { background: var(--sf-2); color: var(--tx-1); }
.numctl__b:active { background: var(--sf-3); }
.numctl__dice, .numctl__reset { opacity: 0; flex: none; transition: opacity var(--dur-1) var(--ease-out); }
.numctl:hover .numctl__b, .numctl:focus-within .numctl__b,
.numctl:hover .numctl__dice, .numctl:focus-within .numctl__dice,
.numctl:hover .numctl__reset, .numctl:focus-within .numctl__reset { opacity: 1; }
.numctl__b:focus-visible, .numctl__dice:focus-visible, .numctl__reset:focus-visible { opacity: 1; }

@media (pointer: coarse) {
  .numctl__b, .numctl__dice, .numctl__reset { opacity: 1; }
}

.numctl[data-modified="true"] .numctl__reset { opacity: 1; }
.field:has(.numctl[data-modified="true"]) > .t-label::after {
  content: ""; display: inline-block; width: 5px; height: 5px; margin-left: var(--sp-3);
  border-radius: var(--r-pill); background: var(--ac); vertical-align: .1em;
}
.ico--reset::before { content: ""; position: absolute; inset: .1em;
       border: 1.5px solid currentColor; border-radius: 50%;
       border-top-color: transparent; border-right-color: transparent;
       transform: rotate(-45deg); }
.ico--reset::after { content: ""; position: absolute; top: .04em; right: .12em;
       border: .16em solid transparent; border-bottom-color: currentColor;
       transform: rotate(45deg); }

.numctl--rolled .input { border-color: var(--ac); }

.numctl--fixed .input { border-color: var(--warn); background: var(--warn-tint); }

/* ============================================================================
   38. SCROLLBAR — the one piece of chrome we do not get to redraw from
   scratch, so it is tuned instead. A pale system bar down the side of a warm
   dark tool is the single loudest thing on the screen and it belongs to
   nobody's design.
   Both syntaxes: the standard two properties (Firefox, and Chrome 121+) and
   the WebKit pseudo-elements, because the standard one cannot do hover.
   Touch keeps the native overlay bar — it appears only while scrolling and
   should stay the platform's.
   ========================================================================= */
:root { scrollbar-color: var(--sf-3) transparent; scrollbar-width: thin; }
@media (pointer: fine) {
  ::-webkit-scrollbar { width: 10px; height: 10px; }
  ::-webkit-scrollbar-track { background: transparent; }
  ::-webkit-scrollbar-thumb {
    background: var(--sf-3); border-radius: var(--r-pill);
    border: 2px solid transparent; background-clip: padding-box;
  }
  ::-webkit-scrollbar-thumb:hover { background: var(--bd-control); background-clip: padding-box; }
  ::-webkit-scrollbar-thumb:active { background: var(--bd-control-hi); background-clip: padding-box; }
  ::-webkit-scrollbar-corner { background: transparent; }
  
  .pool::-webkit-scrollbar, .scroll-y::-webkit-scrollbar,
  .suggest::-webkit-scrollbar, .menu::-webkit-scrollbar,
  .combo__list::-webkit-scrollbar { width: 8px; height: 8px; }
}

:root[data-density="compact"] { scrollbar-width: thin; }
:root[data-density="comfortable"] { scrollbar-width: auto; }
.scroll-y { overflow-y: auto; overscroll-behavior: contain; }
.scroll-x { overflow-x: auto; overscroll-behavior: contain; }

:root {
  --fam-0:      #8A8177;   
  --fam-species:#4FA3A0;   
  --fam-body:   #6E8FB8;   
  --fam-wear:   #A98AC8;   
  
  --fam-pose:   #C98AB0;   
  --fam-frame:  #B98AC0;   
  --fam-scene:  #93A05C;   
  --fam-nsfw:   #C4576E;   
  --fam-fetish: #D2864A;   
}

.tagbox { position: relative;
          display: grid; gap: var(--sp-4);
          background: var(--bg-1); border: 1px solid var(--bd-edge); border-radius: var(--r-3);
          padding: var(--sp-5); }
.tagbox__head { display: flex; align-items: center; gap: var(--sp-4); }
.tagbox__blocks {
  display: flex; flex-wrap: wrap; gap: var(--sp-3);
  min-height: var(--h-xl); padding: var(--sp-3);
  background: var(--sf-0); border: 1px solid var(--bd-control); border-radius: var(--r-2);
  box-shadow: var(--edge-press); align-content: flex-start;
}
.tagbox__blocks:focus-within { border-color: var(--bd-control-hi); }

.tagbox__new { flex: 1 1 12ch; min-width: 12ch; border: 0; background: transparent;
               color: var(--tx-1); font: var(--fw-normal) var(--fs-sm)/1 var(--font-ui);
               outline: 0; padding-inline: var(--sp-3); min-height: var(--h-md); }
.tagbox__text { font-family: var(--font-mono); font-size: var(--fs-xs); line-height: var(--lh-sm); min-height: 120px; }

.tag {
  --_f: var(--fam-0);
  display: inline-flex; align-items: center; gap: var(--sp-3);
  min-height: var(--h-md); padding: 0 var(--sp-2) 0 var(--sp-3);
  background: var(--sf-1); color: var(--tx-1);
  border: 1px solid var(--bd-control); border-left: 3px solid var(--_f);
  border-radius: var(--r-2);
  font: var(--fw-medium) var(--fs-sm)/1 var(--font-ui);
  cursor: grab; user-select: none; touch-action: none;
  transition: background var(--dur-1) var(--ease-out), border-color var(--dur-1) var(--ease-out);
}
.tag:hover { background: var(--sf-2); }
.tag__dot { width: 8px; height: 8px; flex: none; border-radius: var(--r-pill); background: var(--_f); }
.tag__x { display: inline-grid; place-items: center; width: 18px; height: 18px; flex: none;
          border: 0; border-radius: var(--r-1); background: transparent; color: var(--tx-3);
          cursor: pointer; font-size: 10px; }
.tag__x:hover { background: rgb(255 255 255 / 0.10); color: var(--tx-1); }
.tag[data-fam="species"] { --_f: var(--fam-species); }
.tag[data-fam="body"]    { --_f: var(--fam-body); }
.tag[data-fam="wear"]    { --_f: var(--fam-wear); }
.tag[data-fam="pose"]    { --_f: var(--fam-pose); }
.tag[data-fam="frame"]   { --_f: var(--fam-frame); }
.tag[data-fam="scene"]   { --_f: var(--fam-scene); }
.tag[data-fam="nsfw"]    { --_f: var(--fam-nsfw); }
.tag[data-fam="fetish"]  { --_f: var(--fam-fetish); }
.tag[data-fam="meta"]    { border-style: dashed; border-left-style: solid; }

.tag--ghost { position: fixed; left: 0; top: 0; z-index: var(--z-modal);
              pointer-events: none; margin: 0;
              background: var(--sf-2); border-color: var(--ac);
              box-shadow: var(--sh-2); opacity: .96; cursor: grabbing; }
.tag--hole { opacity: .26; border-style: dashed; border-left-style: solid; }
.tag--new { animation: kit-fade var(--dur-2) var(--ease-out) both; }

.tagbox__blocks { touch-action: none; }

.negbox { border: 1px solid var(--bd-edge); border-radius: var(--r-3); background: var(--bg-1);
          overflow: hidden; }
.negbox__btn {
  display: flex; align-items: center; gap: var(--sp-4); cursor: pointer;
  padding: var(--sp-4) var(--sp-5); list-style: none;
  font: var(--fw-medium) var(--fs-sm)/1 var(--font-ui); color: var(--tx-2);
}
.negbox__btn::-webkit-details-marker { display: none; }
.negbox__btn:hover { background: var(--sf-1); }
.negbox__btn:focus-visible { outline: var(--focus-w) solid var(--focus); outline-offset: calc(var(--focus-offset) * -1); }
.negbox__hint { color: var(--tx-3); font-weight: var(--fw-normal); font-size: var(--fs-2xs); }

.negbox__count[hidden] { display: none; }
.negbox__chev { width: 8px; height: 8px; flex: none; margin-left: auto;
                border-right: 2px solid var(--tx-3); border-bottom: 2px solid var(--tx-3);
                transform: rotate(45deg) translate(-2px, -2px);
                transition: transform var(--dur-2) var(--ease-out); }
.negbox[open] .negbox__chev { transform: rotate(225deg) translate(-2px, -2px); }
.negbox__panel { display: grid; grid-template-rows: 0fr; transition: grid-template-rows var(--dur-3) var(--ease-out); }
.negbox[open] .negbox__panel { grid-template-rows: 1fr; }

.negbox--static .negbox__panel { grid-template-rows: 1fr; }
.negbox--static .negbox__btn { cursor: default; }
.negbox--static .negbox__btn:hover { background: transparent; }
.negbox__inner { overflow: hidden; }
.negbox__inner > div { padding: var(--sp-5); border-top: 1px solid var(--bd-edge);
                       display: grid; gap: var(--sp-4); }

.negbox .tagbox { border: 0; background: transparent; padding: 0; }
.negbox .tagbox__text { min-height: 72px; }
.negbox__keep { align-self: start; }

.tagbox__foot .negbox__keep { align-self: center; }

.tagbox__foot { display: flex; align-items: center; gap: var(--sp-5); flex-wrap: wrap; }
.tagbox__foot .taglegend { flex: 1 1 auto; }

.tagbox__foot--3 { display: grid; grid-template-columns: 1fr auto 1fr;
                   gap: var(--sp-4) var(--sp-5); align-items: center; }
.tagbox__foot--3 > :first-child { min-width: 0; }
.tagbox__foot--3 > :last-child { justify-self: end; }
.taglegend { display: flex; flex-wrap: wrap; gap: var(--sp-3) var(--sp-5); }
.taglegend span { display: inline-flex; align-items: center; gap: var(--sp-3);
                  font-size: var(--fs-2xs); color: var(--tx-3); }

.tagbox--noweight .tag__wb { display: none; }

.tagbox[data-locked] .tag {
  background: var(--sf-0); border-color: var(--bd-edge); color: var(--tx-3);
  cursor: default;
}
.tagbox[data-locked] .tag:hover { background: var(--sf-0); }

.tagbox[data-locked] .tag__dot { background: var(--tx-off); }
.tagbox[data-locked] .tag__x { display: none; }
.tagbox[data-locked] .tag__w { background: var(--sf-0); color: var(--tx-off); }
.tagbox[data-locked] .tagbox__blocks { touch-action: auto; }
.taglegend i { width: 8px; height: 8px; border-radius: var(--r-pill); background: var(--f); }

.record { display: grid; gap: 0; background: var(--bg-1); align-content: start;
          border: 1px solid var(--bd-edge); border-radius: var(--r-3); overflow: hidden; }

.record > .canvas { max-height: var(--canvas-cap); }
.record > .canvas:is(.ar-1, .ar-3-4, .ar-16-9) {
  width: min(100%, calc(var(--canvas-cap) * var(--ar-w)));
  margin-inline: auto;
}
.record__body { padding: var(--sp-4); display: grid; gap: var(--sp-4); }
.actbar { display: flex; flex-wrap: wrap; gap: var(--sp-3); }

.timing { display: flex; gap: var(--sp-5); flex-wrap: wrap;
          font-family: var(--font-mono); font-size: var(--fs-2xs); color: var(--tx-3); }
.timing b { color: var(--tx-2); font-weight: var(--fw-normal); }

.passport { display: grid; grid-template-columns: max-content 1fr; gap: var(--sp-2) var(--sp-5); margin: 0; }
.passport dt { font-size: var(--fs-2xs); color: var(--tx-3); text-transform: uppercase;
               letter-spacing: var(--ls-caps); }
.passport dd { margin: 0; font-family: var(--font-mono); font-size: var(--fs-xs); color: var(--tx-1);
               overflow-wrap: anywhere; }

.passport dd.passport__prose { font-family: var(--font-ui);
                               display: flex; flex-wrap: wrap; gap: var(--sp-2); align-items: center; }

.queue { display: flex; align-items: center; gap: var(--sp-4); flex-wrap: wrap;
         padding: var(--sp-4); border: 1px solid var(--bd-edge); border-radius: var(--r-2);
         background: var(--sf-0); }
.queue .progress { flex: 1 1 180px; }
.queue__eta { font-family: var(--font-mono); font-size: var(--fs-xs); color: var(--tx-1); }

.optlist { display: grid; gap: var(--sp-3); }
.opt { display: grid; grid-template-columns: 18px 1fr auto; gap: var(--sp-3) var(--sp-4);
       align-items: start; padding: var(--sp-4); cursor: pointer;
       background: var(--sf-0); border: 1px solid var(--bd-edge); border-radius: var(--r-2);
       transition: border-color var(--dur-1) var(--ease-out), background var(--dur-1) var(--ease-out); }
.opt:hover { border-color: var(--bd-control); background: var(--sf-1); }
.opt input { margin: 2px 0 0; accent-color: var(--ac); }

.opt__name { grid-column: 2; font-weight: var(--fw-bold); color: var(--tx-1); }
.opt__desc { grid-column: 2; font-size: var(--fs-2xs); color: var(--tx-3); }
.opt__limit { grid-column: 2; font-size: var(--fs-2xs); color: var(--warn); }
.opt__price { grid-row: 1; grid-column: 3; font-family: var(--font-mono); font-size: var(--fs-xs); color: var(--tx-2); }
/* THE NUMBER IS THE CHOICE. In the arena the three options differ by one thing — how big the
   number is — and at 12px in the corner it read as a price tag on a menu. It is the menu. */
.optlist--lead .opt { align-items: center; }
.optlist--lead .opt__price { grid-row: 1 / span 2; align-self: center;
  font: var(--fw-bold) var(--fs-2xl)/1 var(--font-mono); color: var(--tx-1);
  font-variant-numeric: tabular-nums; }
.optlist--lead .opt:has(input:checked) .opt__price { color: var(--ac); }

/* THE INVENTORY IS A GRID, NOT A LIST. Four things, two by two: at a glance that reads as a small
   set of objects rather than a menu, which is what it is. Each square carries the NAME and nothing
   else — two to a column there is no room for a description, and a wrapped one would leave the
   four squares at four different heights. The description moved up under the heading, where one
   line speaks for whatever is currently chosen.

   The tick has a column of its own that is always there and merely empty when nothing is picked,
   so choosing a different square never moves a word sideways. */
.invgrid { display: grid; grid-template-columns: 1fr 1fr; gap: var(--sp-3); }
.invcell { display: grid; grid-template-columns: 16px 1fr; align-items: center;
  gap: var(--sp-3); min-height: 54px; padding: var(--sp-4);
  border: 1px solid var(--bd-edge); border-radius: var(--r-3);
  background: var(--sf-0); cursor: pointer; }
.invcell:hover { border-color: var(--bd-control); background: var(--sf-1); }
.invcell input { position: absolute; width: 1px; height: 1px; opacity: 0; margin: 0; }
.invcell .ico--check { color: var(--ok); visibility: hidden; }
.invcell__name { color: var(--tx-1); font-weight: var(--fw-medium); line-height: var(--lh-sm); }
.invcell:has(input:checked) { border-color: var(--ac); background: var(--ac-tint-1); }
.invcell:has(input:checked) .ico--check { visibility: visible; }
.invcell:has(input:focus-visible) { outline: var(--focus-w) solid var(--focus);
  outline-offset: var(--focus-offset); }

/* Two lines' worth of height whatever is in it. The tick was given a column of its own so that
   picking a square never moves the words; without this the caption above does the same damage
   from the other direction — "Nothing" runs to two lines, the coffee to one, and the whole grid
   walks up and down by a line as you point at things. */
/* Three slots rather than four: the pack is a row, not a square. */
.invgrid--3 { grid-template-columns: repeat(3, minmax(0, 1fr)); }

/* A rule asked for by hand, in the same hairline and with the same inset as the ones the steps
   draw between their blocks — for the places where two subjects share one step and the automatic
   separator does not reach inside. */
.hairline { display: block; height: 1px; background: var(--bd-hairline);
            margin: var(--sp-5) var(--sp-9); }

/* ---- THE MARKET ----------------------------------------------------------
   A thousand rows on the left in one scroll, your three slots on the right, and the price of the
   decision under both. The list is not virtualised — a thousand rows is about four thousand nodes,
   which the browser lays out once in a few milliseconds and then scrolls natively. */
.mk { display: grid; grid-template-columns: minmax(0, 1.7fr) minmax(0, 1fr);
      gap: var(--sp-5); align-items: start; }
.mk__list { overflow-y: auto; max-height: min(54vh, 540px);
            border: 1px solid var(--bd-edge); border-radius: var(--r-3); background: var(--sf-0); }
.mk__row { display: grid; grid-template-columns: 1fr auto; gap: 0 var(--sp-4);
           padding: var(--sp-3) var(--sp-4); border-bottom: 1px solid var(--bd-hairline);
           cursor: grab; }
.mk__row:last-child { border-bottom: 0; }
.mk__row:hover { background: var(--sf-1); }
.mk__row.is-drag { opacity: .5; }
.mk__n { color: var(--tx-1); }
.mk__d { grid-column: 1; color: var(--tx-3); font-size: var(--fs-2xs); line-height: var(--lh-2xs); }
/* The column is stated, not left to the placement algorithm. An item with a definite ROW and no
   column is placed before the auto-flow ones, so the price took column one and pushed the name
   across — the row read "5 Amphora of garum" with the number leading. */
.mk__p { grid-column: 2; grid-row: 1 / span 2; align-self: center; justify-self: end;
         font-family: var(--font-mono); font-size: var(--fs-xs); color: var(--tx-2); }
.mk__n { grid-column: 1; grid-row: 1; }
.mk__era { font-size: var(--fs-3xs); color: var(--tx-3); letter-spacing: var(--ls-caps);
           text-transform: uppercase; margin-left: var(--sp-3); }

.mk__pack { display: grid; gap: var(--sp-3); }
.mk__slot { position: relative; min-height: 76px; padding: var(--sp-4) var(--sp-7) var(--sp-4) var(--sp-4);
            display: grid; align-content: center; gap: 2px;
            border: 1px dashed var(--bd-control); border-radius: var(--r-3); background: var(--sf-0); }
.mk__slot.is-full { border-style: solid; cursor: grab; }
.mk__slot.is-over { border-color: var(--ac); background: var(--ac-tint-1); }
.mk__slot .t-hint { font-size: var(--fs-2xs); line-height: var(--lh-2xs); }
.mk__x { position: absolute; top: var(--sp-2); right: var(--sp-2); }

@media (max-width: 900px) { .mk { grid-template-columns: minmax(0, 1fr); } }

.invnote { border: 1px solid var(--bd-edge); border-radius: var(--r-3);
  padding: var(--sp-3) var(--sp-4); color: var(--tx-2);
  font-size: var(--fs-xs); line-height: var(--lh-xs);
  min-height: calc(var(--lh-xs) * 2 + var(--sp-3) * 2 + 2px); }

.opt:has(input:checked) { border-color: var(--ac); background: var(--ac-tint-1); }
.opt:has(input:focus-visible) { outline: var(--focus-w) solid var(--focus); outline-offset: var(--focus-offset); }

.randbar { display: flex; align-items: center; gap: var(--sp-3); flex-wrap: wrap;
           padding: var(--sp-3); border: 1px solid var(--bd-control); border-radius: var(--r-2);
           background: var(--sf-0); }
.randbar input { flex: 1 1 12ch; min-width: 10ch; border: 0; background: transparent; outline: 0;
                 color: var(--tx-1); font: var(--fw-normal) var(--fs-sm)/1 var(--font-ui);
                 min-height: var(--h-md); padding-inline: var(--sp-3); }
.tag--excl { --_f: var(--err); }
.tag--excl .tag__label { text-decoration: line-through; text-decoration-color: var(--err); }

.tagref { display: grid; gap: var(--sp-3); }
.tagref__list { display: grid; }
.tagref__row { display: flex; align-items: center; gap: var(--sp-4); min-height: var(--h-md);
               padding-inline: var(--sp-3); border-bottom: 1px solid var(--bd-edge);
               cursor: pointer; }
.tagref__row:last-child { border-bottom: 0; }
.tagref__row:hover { background: var(--row-hover); }
.tagref__dot { width: 8px; height: 8px; flex: none; border-radius: var(--r-pill); background: var(--f, var(--fam-0)); }
.tagref__name { font-family: var(--font-mono); font-size: var(--fs-sm); color: var(--tx-1); }
.tagref__fam { font-size: var(--fs-2xs); color: var(--tx-3); }

.tagref__list--scroll { max-height: calc(var(--h-md) * 10 + 9px);
                        overflow-y: auto; overscroll-behavior: contain; }
.tagref__row[hidden] { display: none; }

.tagref__row .gauge { margin-left: auto; }

.tagref__more { color: var(--tx-3); font-size: var(--fs-2xs); cursor: default; }
.tagref__more:hover { background: transparent; }

.modal__box--tagref { height: min(595px, 88vh); grid-template-rows: auto 1fr auto; }
.modal__box--tagref .modal__body { min-height: 0; overflow-y: auto; align-content: stretch; }

.modal__box--tagref .tagref { display: flex; flex-direction: column; gap: var(--sp-3); min-height: 100%; }
.modal__box--tagref .tagpick { margin-top: auto; }

.modal__box--tagref .tagfam { flex: 1 1 auto; }

.tagfam { display: flex; flex-direction: column; gap: var(--sp-4); min-height: 0; }
.tagfam__list { display: grid; gap: var(--sp-3); }
.tagfam__row { display: grid; grid-template-columns: 10px auto 1fr; align-items: baseline;
               gap: var(--sp-3) var(--sp-4); }
.tagfam__dot { width: 8px; height: 8px; border-radius: var(--r-pill);
               background: var(--f, var(--fam-0)); transform: translateY(-1px); }
.tagfam__name { font-weight: var(--fw-bold); color: var(--tx-1); font-size: var(--fs-sm); }
.tagfam__desc { color: var(--tx-2); font-size: var(--fs-xs); line-height: var(--lh-sm); }

.tagfam__foot { margin-top: auto; display: flex; justify-content: center; padding-top: var(--sp-3); }

.tagpick { display: grid; gap: var(--sp-3); padding-top: var(--sp-4);
           border-top: 1px solid var(--bd-edge); }
.tagpick__row { display: flex; align-items: center; gap: var(--sp-3); min-width: 0; }

.tagpick__strip { flex: 1 1 auto; min-width: 0; display: flex; gap: var(--sp-3);
                  overflow-x: auto; scrollbar-width: none; }

@media (prefers-reduced-motion: no-preference) { .tagpick__strip { scroll-behavior: smooth; } }
.tagpick__strip::-webkit-scrollbar { display: none; }
.tagpick__chip {
  flex: none; display: inline-flex; align-items: center;
  height: var(--h-sm); padding-inline: var(--sp-4);
  border: 1px solid var(--bd-control); border-radius: var(--r-2);
  background: var(--sf-1); color: var(--tx-1);
  font: var(--fw-medium) var(--fs-xs)/1 var(--font-ui);
  cursor: pointer; white-space: nowrap;
  transition: background var(--dur-1) var(--ease-out), border-color var(--dur-1) var(--ease-out);
}
.tagpick__chip:hover { background: var(--sf-2); border-color: var(--ac); }
.tagpick__nav { flex: none; width: var(--h-sm); height: var(--h-sm); padding: 0;
                display: grid; place-items: center;
                border: 1px solid var(--bd-control); border-radius: var(--r-2);
                background: var(--sf-1); color: var(--tx-2); cursor: pointer;
                font: var(--fw-medium) var(--fs-sm)/1 var(--font-ui); }
.tagpick__nav:hover:not(:disabled) { background: var(--sf-2); color: var(--tx-1); border-color: var(--bd-control-hi); }
.tagpick__nav:disabled { color: var(--tx-off); border-color: var(--bd-edge); cursor: not-allowed; }
.tagpick__nav[hidden] { display: none; }
.tagpick__empty { color: var(--tx-3); font-size: var(--fs-2xs); }

.gauge { display: inline-flex; align-items: center; gap: 3px; }
.gauge i { width: 7px; height: 7px; border-radius: var(--r-pill);
           background: var(--sf-1); box-shadow: inset 0 0 0 1px var(--bd-edge); }
.gauge i.on { background: var(--gauge, var(--tx-3)); box-shadow: none; }
.gauge[data-level="1"]  { --gauge: #E4796C; }
.gauge[data-level="2"]  { --gauge: #E18764; }
.gauge[data-level="3"]  { --gauge: #DE965B; }
.gauge[data-level="4"]  { --gauge: #DBA453; }
.gauge[data-level="5"]  { --gauge: #D8B24A; }
.gauge[data-level="6"]  { --gauge: #C5B04E; }
.gauge[data-level="7"]  { --gauge: #B1AE52; }
.gauge[data-level="8"]  { --gauge: #9EAC57; }
.gauge[data-level="9"]  { --gauge: #8BAA5B; }
.gauge[data-level="10"] { --gauge: #78A85F; }

.tag__w { font-family: var(--font-mono); font-size: var(--fs-3xs); line-height: 1;
          padding: 2px 3px; border-radius: var(--r-1);
          background: var(--ac-tint-2); color: var(--ac-hover); }
.tag__w--down { background: var(--sf-0); color: var(--tx-3); }
.tag__w--over { background: var(--warn-tint); color: var(--warn); }
.tag__wb { display: inline-grid; width: 12px; flex: none; opacity: 0;
           transition: opacity var(--dur-1) var(--ease-out); }
.tag__wb button { height: 9px; padding: 0; border: 0; background: transparent;
                  color: var(--tx-3); cursor: pointer; font-size: 8px; line-height: 9px; }
.tag__wb button:hover { color: var(--tx-1); }
.tag:hover .tag__wb, .tag:focus-within .tag__wb { opacity: 1; }
@media (pointer: coarse) { .tag__wb { opacity: 1; } }

.slider--bi .slider__bi { position: relative; }
.slider--bi .slider__bi::before {
  content: ""; position: absolute; left: 0; right: 0; top: 8px; height: 4px;
  border-radius: var(--r-1); border: 1px solid var(--bd-edge);
  background: linear-gradient(var(--bd-control-hi) 0 0) center / 1px 10px no-repeat, var(--sf-0);
}
.slider--bi .slider__bi::after {
  content: ""; position: absolute; top: 9px; height: 4px;
  left: var(--l, 50%); width: var(--w, 0); background: var(--ac); border-radius: var(--r-1);
}
.slider--bi input[type="range"]::-webkit-slider-runnable-track { background: transparent; border-color: transparent; }
.slider--bi input[type="range"]::-moz-range-track { background: transparent; border-color: transparent; }
.slider--bi input[type="range"] { position: relative; }
.slider__val--neg { color: var(--tx-2); }

.snips { display: grid; }
.snip { display: grid; grid-template-columns: 14px auto 1fr auto; align-items: center;
        gap: var(--sp-2) var(--sp-4); padding: var(--sp-3) var(--sp-4);
        border-bottom: 1px solid var(--bd-edge); background: var(--bg-1); }
.snip:last-child { border-bottom: 0; }
.snip__grip { cursor: grab; color: var(--tx-3); line-height: 1; user-select: none; touch-action: none;
              font-size: var(--fs-md); justify-self: center; }
.snip__grip:active { cursor: grabbing; }
.snip__name { font-weight: var(--fw-medium); color: var(--tx-1); }
.snip__acts { display: flex; gap: var(--sp-2); opacity: 0; transition: opacity var(--dur-1) var(--ease-out); }
.snip:hover .snip__acts, .snip:focus-within .snip__acts { opacity: 1; }
@media (pointer: coarse) { .snip__acts { opacity: 1; } }
.snip__body { grid-column: 3 / -1; display: grid; gap: 2px; }
.snip__pos, .snip__neg { font-family: var(--font-mono); font-size: var(--fs-2xs); overflow-wrap: anywhere; }
.snip__pos { color: var(--tx-2); }
.snip__neg { color: var(--tx-3); }
.snip__neg::before { content: "− "; color: var(--err); }
.snip__pos::before { content: "+ "; color: var(--ok); }
.snip--off { opacity: .5; }
.snip--drag { opacity: .45; border-color: var(--ac); }

.phrase { font-family: var(--font-mono); font-size: var(--fs-xs); color: var(--tx-1);
          padding: var(--sp-3) var(--sp-4); border-radius: var(--r-2);
          background: var(--sf-0); border: 1px solid var(--bd-edge); }

.series { position: absolute; left: var(--sp-3); bottom: var(--sp-3);
          display: inline-flex; align-items: center; height: var(--h-xs); padding-inline: var(--sp-3);
          border-radius: var(--r-1); background: rgb(0 0 0 / 0.62);
          border: 1px solid var(--bd-control); color: var(--tx-1);
          font: var(--fw-medium) var(--fs-2xs)/1 var(--font-ui); }

.pose { position: relative; background: var(--bg-canvas); border: 1px solid var(--bd-edge);
        border-radius: var(--r-3); overflow: hidden; touch-action: none; }
.pose svg { display: block; width: 100%; height: auto; }
.pose__joint { cursor: grab; }
.pose__joint:active { cursor: grabbing; }
.pose__joint circle { transition: r var(--dur-1) var(--ease-out); }
.pose__joint:hover circle, .pose__joint:focus-visible circle { r: 7; }
.pose__joint:focus-visible { outline: var(--focus-w) solid var(--focus); outline-offset: 2px; border-radius: var(--r-pill); }
.pose__hint { position: absolute; left: var(--sp-4); bottom: var(--sp-4);
              color: var(--tx-off); font-size: var(--fs-2xs); pointer-events: none; }

.masker { position: relative; background: var(--bg-canvas); border: 1px solid var(--bd-edge);
          border-radius: var(--r-3); overflow: hidden; touch-action: none; }
.masker canvas { display: block; width: 100%; height: auto; cursor: crosshair; }
.masker__under { position: absolute; inset: 0; display: grid; place-items: center;
                 color: var(--tx-off); font-size: var(--fs-xs); pointer-events: none; }
.masker__tools { display: flex; align-items: center; gap: var(--sp-4); flex-wrap: wrap; }

.lock { position: relative; }
.lock__over { display: none; }

.lock--off > .lock__body,
.statesw__in:checked ~ .lock > .lock__body {
  filter: blur(7px);
  pointer-events: none;
  user-select: none;
}
.lock--off > .lock__over,
.statesw__in:checked ~ .lock > .lock__over {
  display: grid; position: absolute; inset: 0; z-index: var(--z-modal);
  place-items: start center; padding-block: var(--sp-10);
}

.lock__win { width: min(460px, calc(100% - var(--sp-8)));
             position: sticky; top: clamp(var(--sp-10), 28vh, 40vh); }

.statesw__in { position: absolute; opacity: 0; width: 0; height: 0; }
.statesw { display: inline-flex; align-items: center; gap: var(--sp-3); cursor: pointer; }
.statesw__in:focus-visible ~ .statesw { outline: var(--focus-w) solid var(--focus); outline-offset: var(--focus-offset); }

/* A NOTICE THAT HAS TO BE READ BEFORE THE THING IT IS ABOUT. Boxed and centred rather than set
   as another hint: the screen underneath looks finished, and anything in the voice of the rest of
   the page would be read as part of it. It wraps like ordinary prose — a one-line rule that
   clipped its own warning would be its own joke. */
.earlybox {
  border: 2px solid var(--warn); border-radius: var(--r-3);
  background: var(--warn-tint);
  padding: var(--sp-5) var(--sp-6); margin-inline: auto; max-width: 68ch;
  text-align: center; text-wrap: balance;
  font: var(--fw-bold) var(--fs-sm)/1.45 var(--font-ui); color: var(--tx-1);
}

.juice__score { font-variant-numeric: tabular-nums; }

.juice__row { opacity: 0; transform: translateY(6px); }
.juice__row--in { opacity: 1; transform: none;
                  transition: opacity var(--dur-3) var(--ease-out), transform var(--dur-3) var(--ease-out); }
.juice__row--hit { animation: kit-pop var(--dur-4) var(--ease-out); }
@keyframes kit-pop { 0% { transform: scale(1); } 45% { transform: scale(1.06); } 100% { transform: scale(1); } }

/* A MODIFIER THAT DOES NOTHING STILL SHOWS. Each card carries its own three stats, so an arena
   favours an AXIS rather than a stat name — send a body stat into a place that pays for talk and
   the row reads ×1. It is dimmed and it does not pop, but hiding it would hide the rule: the
   next roll is played against exactly this. */
.juice__row[data-idle] { opacity: .45; }
.juice__row[data-idle].juice__row--in { opacity: .45; }

/* The line the arena answered with. It arrives after the notification, so it gets the accent
   edge the first three rows do not have — this is the row that decided it. */
.juice__row--said { box-shadow: inset 2px 0 0 var(--ac); }

.juice--won { animation: kit-shake 320ms var(--ease-out); }
@keyframes kit-shake {
  0%, 100% { transform: translateX(0); }
  15% { transform: translateX(-4px); }  30% { transform: translateX(4px); }
  45% { transform: translateX(-3px); }  60% { transform: translateX(3px); }
  80% { transform: translateX(-1px); }
}

/* A LOSS NEEDS ITS OWN ENDING, and it must not be the winning one played backwards. The rule of
   this screen is that losing costs nothing, so the feedback settles rather than punishes: the
   score sinks a few pixels and comes back, once. No shake, no red, no sound of failure — a
   defeat that stings is a defeat people avoid by not playing. */
.juice--lost .juice__score { animation: kit-settle 420ms var(--ease-out); }
@keyframes kit-settle {
  0% { transform: translateY(0); opacity: 1; }
  40% { transform: translateY(5px); opacity: .55; }
  100% { transform: translateY(0); opacity: 1; }
}

@media (prefers-reduced-motion: reduce) {
  .juice__row { opacity: 1; transform: none; }
  .juice__row--hit, .juice--won { animation: none; }
  .juice--lost .juice__score { animation: none; }
}

.rate__panel { display: none; }
.rate:has(.rate__in:checked) .rate__panel { display: grid; gap: var(--sp-5); }

.qa__in { position: absolute; opacity: 0; width: 0; height: 0; }
.qa__btn { cursor: pointer; }
.qa__in:checked ~ .qa__btn { background: var(--ac); border-color: var(--ac); color: var(--ac-ink); }
.qa__in:focus-visible ~ .qa__btn { outline: var(--focus-w) solid var(--focus); outline-offset: var(--focus-offset); }

.qa__x { display: none; }
.qa:has(.qa__in:checked) .qa__x { display: inline; }

.scroll-cap { max-height: min(38vh, 320px); overflow-y: auto; overscroll-behavior: contain; }

.check { display: inline-flex; align-items: center; gap: var(--sp-3); cursor: pointer; user-select: none; }
.check input { accent-color: var(--ac); width: 16px; height: 16px; margin: 0; flex: none; }
.check input:focus-visible { outline: var(--focus-w) solid var(--focus); outline-offset: var(--focus-offset); }
.check input:disabled ~ * { color: var(--tx-off); }

.checks { display: grid; gap: var(--sp-3); }
.checks--row { display: flex; flex-wrap: wrap; gap: var(--sp-3) var(--sp-5); }

.pager__go { display: flex; align-items: center; gap: var(--sp-2); margin-left: var(--sp-4); }
.pager__go .input { width: 4.5rem; text-align: center; }

body.prod .note,
body.prod .devline,
body.prod .fork,
body.prod .statesw { display: none !important; }

.prodsw { display: inline-flex; align-items: center; gap: var(--sp-3); cursor: pointer; user-select: none;
          padding-inline: var(--sp-3); border: 1px dashed var(--bd-edge); border-radius: var(--r-2); }
.prodsw input { accent-color: var(--ac); margin: 0; }
.prodsw .t-hint { white-space: nowrap; }

.cpage { position: relative;
         display: grid; gap: var(--cf-gap, 6px); padding: var(--cf-gap, 6px);
         background: var(--cf-ink, var(--tx-2)); border-radius: var(--r-3);
         outline: 1px solid var(--bd-edge); outline-offset: -1px;
         aspect-ratio: 3 / 4; }

.cmark { position: absolute; left: var(--sp-3); bottom: var(--sp-3); z-index: 2;
         display: inline-flex; align-items: center; height: var(--h-xs);
         padding-inline: var(--sp-3); border-radius: var(--r-1);
         background: var(--tx-1); color: var(--bg-0);
         border: 1px solid var(--bd-control);
         font: var(--fw-bold) var(--fs-2xs)/1 var(--font-ui);
         white-space: nowrap; pointer-events: none; }

.ccard__thumb:has(.cmark) .series,
.canvas:has(.cmark) .series { bottom: calc(var(--sp-3) + var(--h-xs) + 4px); }

.cpick .cpage--strip { aspect-ratio: 3 / 4; overflow: hidden; }
.cpick .cpage--strip .cpanel { min-height: 44px; }

.cpick .cpage--strip .cpanel:last-of-type {
  -webkit-mask-image: linear-gradient(#000 55%, transparent 100%);
          mask-image: linear-gradient(#000 55%, transparent 100%);
}

.cpage--sm { width: 120px; flex: none; }
.cpanel { background: var(--bg-canvas); display: grid; place-items: center;
          color: var(--tx-off); font-size: var(--fs-xs); text-align: center;
          padding: var(--sp-3); min-width: 0; min-height: 0; overflow: hidden; }

.cpanel--set   { background: var(--bg-2); color: var(--tx-2); }
.cpanel--empty { background: var(--bg-1); border: 1px dashed var(--bd-edge); }

.cpanel--sel  { outline: var(--focus-w) solid var(--ac); outline-offset: -2px; color: var(--tx-1); }
.cpanel--busy { background: var(--sf-0); color: var(--tx-2); }
.cpanel--done { background: var(--bg-canvas); color: var(--tx-3); }

.cpanel--pick { cursor: pointer; }
.cpanel--pick:hover { background: var(--sf-1); }

.cpage--2x2      { grid-template-columns: 1fr 1fr; grid-template-rows: 1fr 1fr; }
.cpage--3row     { grid-template-columns: 1fr; grid-template-rows: repeat(3, 1fr); }
.cpage--widetop  { grid-template-columns: 1fr 1fr; grid-template-rows: 1.25fr 1fr;
                   grid-template-areas: "a a" "b c"; }
.cpage--widetop > :nth-child(1) { grid-area: a; }
.cpage--widetop > :nth-child(2) { grid-area: b; }
.cpage--widetop > :nth-child(3) { grid-area: c; }
.cpage--z        { grid-template-columns: 1fr 1fr; grid-template-rows: 1fr 1.1fr 1fr;
                   grid-template-areas: "a a" "b c" "d d"; }
.cpage--z > :nth-child(1) { grid-area: a; }
.cpage--z > :nth-child(2) { grid-area: b; }
.cpage--z > :nth-child(3) { grid-area: c; }
.cpage--z > :nth-child(4) { grid-area: d; }

.cpage--strip    { grid-template-columns: 1fr; aspect-ratio: auto; }
.cpage--strip .cpanel { min-height: 92px; }

.cpage--thin  { --cf-gap: 3px; }
.cpage--thick { --cf-gap: 14px; }
.cpage--none  { --cf-gap: 0px; }
.cpage--white { --cf-ink: var(--tx-1); }
.cpage--dark  { --cf-ink: var(--bg-0); }

.cpage--soft .cpanel { -webkit-mask-image: radial-gradient(closest-side, #000 76%, transparent 100%);
                                mask-image: radial-gradient(closest-side, #000 76%, transparent 100%); }

.cpage--torn .cpanel { clip-path: polygon(1.5% 0.8%, 98.5% 0, 100% 98.6%, 0.6% 100%); }

.cpicks { display: flex; flex-wrap: wrap; gap: var(--sp-4); align-items: start; }
.cpick  { position: relative; width: 88px; cursor: pointer; display: grid; gap: var(--sp-2); }
.cpick input { position: absolute; opacity: 0; width: 0; height: 0; }
.cpick .cpage { --cf-gap: 3px; border-radius: var(--r-2); }
.cpick:has(input:checked) .cpage { outline: var(--focus-w) solid var(--ac); outline-offset: 2px; }
.cpick input:focus-visible ~ .cpage { outline: var(--focus-w) solid var(--focus); outline-offset: var(--focus-offset); }

.zoom { display: inline-flex; align-items: center; flex: none;
        border: 1px solid var(--bd-control); border-radius: var(--r-2);
        background: var(--sf-0); box-shadow: var(--edge-press); }
.zoom__b { width: var(--h-md); min-height: calc(var(--h-md) - 2px); flex: none;
           display: grid; place-items: center;
           border: 0; background: transparent; color: var(--tx-2); cursor: pointer;
           font: var(--fw-bold) var(--fs-md)/1 var(--font-ui);
           transition: background var(--dur-1) var(--ease-out), color var(--dur-1) var(--ease-out); }
.zoom__b:hover { background: var(--sf-1); color: var(--tx-1); }
.zoom__b:first-child { border-radius: var(--r-2) 0 0 var(--r-2); }
.zoom__b:last-child  { border-radius: 0 var(--r-2) var(--r-2) 0; }

/* place-items, not text-align alone: the number is centred across the full control height as
   well as across its width. Without it the reading sits on the top edge of the min-height box
   and floats clear of the two buttons beside it. */
.zoom__v { min-width: 68px; min-height: calc(var(--h-md) - 2px); padding-inline: var(--sp-3);
           display: grid; place-items: center;
           border: 0; border-inline: 1px solid var(--bd-hairline); background: transparent;
           color: var(--tx-1); cursor: pointer; text-align: center;
           font: var(--fw-medium) var(--fs-sm)/1 var(--font-mono);
           font-variant-numeric: tabular-nums; }
.zoom__v:hover { background: var(--sf-1); }
.zoom__v[aria-expanded="true"] { background: var(--sf-2); color: var(--ac); }
.pop--zoom { width: 260px; }

/* minmax(0, 1fr) rather than the implicit auto column. The live viewer writes a pixel width onto
   the stage, and an auto column grows to whatever its child asks for — so on a narrow screen the
   column would swell to the picture width, push past the dialog, and then hand that inflated
   number back as the room available. A 1fr column is measured from the dialog and cannot. */
.viewer { display: grid; grid-template-columns: minmax(0, 1fr); gap: var(--sp-4); }
.viewer__stage { position: relative; display: grid; place-items: center;
                 min-height: min(62vh, 640px); background: var(--bg-canvas);
                 border: 1px solid var(--bd-edge); border-radius: var(--r-2);
                 overflow: hidden; }

.viewer__stage > .canvas { --canvas-cap: none; max-height: none;
                           height: min(62vh, 640px); width: auto;
                           max-width: 100%; border: 0; }
.viewer__stage > .canvas.ar-16-9 { width: 100%; height: auto; }

/* THE ARROWS BELONG TO THE DIALOG, not to the picture. Once the frame started shrinking to fit
   what is in it, rails pinned to the stage travelled inward with every portrait and ended up
   sitting on the artwork. This wrapper bleeds out to the dialog's own edges — negative margin
   out, the same padding straight back in — so the stage stays exactly where it was while the
   arrows can reach the border. The 1fr column is the same guard as on .viewer: an auto column
   would swell to the pixel width the viewer writes onto the stage. */
.viewer__frame { position: relative; display: grid; grid-template-columns: minmax(0, 1fr);
                 margin-inline: calc(-1 * var(--sp-6)); padding-inline: var(--sp-6); }

/* A thin, see-through strip under each arrow and nothing more. pointer-events follows the
   opacity: a strip you cannot see must not be swallowing a click or a drag either, which is
   what an invisible-but-live gutter was doing on touch. No hover state — the dev asked for the
   strip to stay exactly as it is when the cursor crosses it. */
.viewer__rail { position: absolute; top: 0; bottom: 0; width: 44px; z-index: 2;
                display: grid; place-items: center; border: 0; cursor: pointer;
                background: linear-gradient(90deg, rgb(0 0 0 / 0.30), transparent);
                color: var(--tx-1); opacity: 0; pointer-events: none;
                transition: opacity var(--dur-2) var(--ease-out); }
.viewer__stage:hover .viewer__rail,
.viewer__stage:focus-within .viewer__rail,
.viewer__stage.is-rails .viewer__rail,
.viewer__frame:hover .viewer__rail,
.viewer__frame:focus-within .viewer__rail,
.viewer__frame.is-rails .viewer__rail { opacity: 1; pointer-events: auto; }
/* No hover to reveal them with, so they simply stay. */
@media (pointer: coarse) {
  .viewer__stage .viewer__rail,
  .viewer__frame .viewer__rail { opacity: 1; pointer-events: auto; width: 38px; }
}
.viewer__rail--l { left: 0; }
.viewer__rail--r { right: 0;
                   background: linear-gradient(270deg, rgb(0 0 0 / 0.30), transparent); }

.viewer:has(.viewer__strip) .viewer__stage { min-height: min(46vh, 470px); }
.viewer:has(.viewer__strip) .viewer__stage > .canvas { height: min(46vh, 470px); }

/* THE LIVE VIEWER — the stage is the viewport itself.
   The picture is laid out at its natural pixel size and positioned entirely by transform, which
   is what lets 100% mean one image pixel to one CSS pixel however wide the dialog happens to be.
   The static viewers on the other screens keep the .canvas-in-a-stage shape above; this modifier
   is what separates the one that actually works from the ones that only illustrate it. */
/* The only dark left inside the frame is the two arrow strips. Beside a portrait picture you
   now see the dialog itself showing through, not a slab of canvas painted over it — which is
   the difference between a frame and a pair of black bars. */
.viewer__stage--live { display: block; overflow: hidden; touch-action: none;
                       background: transparent;
                       /* No border, and that is a measurement decision as much as a visual one:
                          with box-sizing: border-box a written width of N gives an N-2 content
                          box, so the picture would overhang its own frame by the two border
                          pixels. Nothing is lost — the frame now traces the picture exactly, so
                          the picture's own edge is the edge. */
                       border: 0;
                       /* the width is written by the viewer to match the picture, so the frame
                          has to be free to be narrower than its row, and centred in it */
                       justify-self: center; max-width: 100%; }
.viewer__stage--live.is-pannable { cursor: grab; }
.viewer__stage--live.is-pannable.is-drag { cursor: grabbing; }
.viewer__stage--live > [data-vw-img] {
  position: absolute; top: 0; left: 0;
  width: auto; height: auto; max-width: none; max-height: none;
  transform-origin: 0 0; will-change: transform;
  -webkit-user-select: none; user-select: none; -webkit-user-drag: none;
}

/* On a narrow screen the three-part bar has no room to keep the zoom cluster in a middle column
   of its own, so it drops the empty outer track and lets the controls wrap. */
@media (max-width: 560px) {
  .viewer__bar { grid-template-columns: auto minmax(0, 1fr); }
  .viewer__mid { flex-wrap: wrap; justify-content: flex-end; gap: var(--sp-2); }
}

/* ---- FULLSCREEN ----------------------------------------------------------
   One layer, built once by the kit and reused by every viewer on the site. It is appended INSIDE
   the viewer's <dialog> rather than to the body, because a modal dialog makes the rest of the
   document inert — an overlay outside it would be visible and completely unclickable.

   The two panels are the only furniture, and they are absent until the pointer goes looking for
   them: a picture you opened to look at should not be wearing a toolbar. They come back when the
   pointer enters the top or bottom strip of the screen and leave a second after it goes. */
.fsv { position: fixed; inset: 0; z-index: 5000; background: #07070a;
       display: block; overflow: hidden; }
.fsv__img { position: absolute; top: 0; left: 0; transform-origin: 0 0;
            user-select: none; -webkit-user-drag: none; will-change: transform; }
.fsv.is-pan .fsv__img { cursor: grab; }
.fsv.is-drag .fsv__img { cursor: grabbing; }
.fsv__img--fade { transition: opacity 240ms var(--ease-out); }

.fsv__top, .fsv__bar {
  position: absolute; left: 50%; transform: translateX(-50%); z-index: 2;
  display: flex; align-items: center; gap: var(--sp-3);
  padding: var(--sp-3) var(--sp-5); max-width: calc(100vw - var(--sp-8));
  background: rgb(14 13 12 / 0.72); border: 1px solid var(--bd-edge);
  border-radius: var(--r-4); backdrop-filter: blur(8px);
  opacity: 0; pointer-events: none;
  transition: opacity var(--dur-3) var(--ease-out);
}
.fsv__top { top: var(--sp-5); }
.fsv__bar { bottom: var(--sp-5); flex-wrap: wrap; justify-content: center; }
.fsv__x { position: absolute; top: var(--sp-5); right: var(--sp-5); z-index: 3;
          opacity: 0; pointer-events: none;
          transition: opacity var(--dur-3) var(--ease-out); }
.fsv.is-top .fsv__top, .fsv.is-top .fsv__x { opacity: 1; pointer-events: auto; }
.fsv.is-bar .fsv__bar { opacity: 1; pointer-events: auto; }

.fsv__name { color: var(--tx-1); font-weight: var(--fw-medium); }
.fsv__sep { width: 1px; align-self: stretch; background: var(--bd-edge); margin: 0 var(--sp-2); }
.fsv__pct { min-width: 4.2em; text-align: center; }
.fsv .btn[aria-pressed="true"] { border-color: var(--ac); color: var(--tx-1); background: var(--ac-tint-1); }

.viewer__bar { display: grid; grid-template-columns: 1fr auto 1fr; align-items: center;
               gap: var(--sp-4); min-height: var(--h-md); }
.viewer__bar > :first-child { justify-self: start; }
.viewer__bar > :last-child  { justify-self: end; }
.viewer__mid { display: flex; align-items: center; gap: var(--sp-3); }

.viewer__strip { display: flex; align-items: center; justify-content: center;
                 gap: var(--sp-3); }
.viewer__thumbs { display: flex; gap: var(--sp-3); flex: 0 0 auto; overflow: hidden; }

.viewer__thumb { width: 64px; aspect-ratio: 1; flex: none; display: grid; place-items: center;
                 background: var(--bg-canvas); border: 1px solid var(--bd-edge);
                 border-radius: var(--r-1); overflow: hidden; cursor: pointer;
                 color: var(--tx-off); font-size: var(--fs-3xs); text-align: center;
                 transition: border-color var(--dur-1) var(--ease-out); }
.viewer__thumb:hover { border-color: var(--bd-control-hi); }
.viewer__thumb[aria-current] { border-color: var(--ac);
                               outline: 2px solid var(--ac); outline-offset: -3px; }

.credit { display: inline-flex; align-items: center; gap: var(--sp-3);
          padding: 2px var(--sp-4) 2px 2px; flex: none;
          border: 1px solid var(--bd-control); border-radius: var(--r-pill);
          background: var(--sf-0); color: var(--tx-1); text-decoration: none;
          font: var(--fw-medium) var(--fs-xs)/1 var(--font-ui);
          transition: border-color var(--dur-1) var(--ease-out), background var(--dur-1) var(--ease-out); }
.credit:hover { border-color: var(--ac); background: var(--sf-1); }
.credit__what { color: var(--tx-3); font-weight: var(--fw-normal); }
.credit--char { padding-left: var(--sp-4); border-style: dashed; }

.btn--ok { --_bg: var(--ok-tint); --_bd: var(--ok); --_tx: var(--ok); }
.btn--ok:hover { --_bg: rgb(120 168 95 / 0.22); }

.pager__p--step { font-size: var(--fs-md); line-height: 1;
                  font-family: var(--font-ui); color: var(--tx-3); }
.pager__p--step:hover { color: var(--tx-1); }

.pager__more { letter-spacing: .1em; }

.pager__p--step,
.pager__more,
.pager__p[aria-label="Back"],
.pager__p[aria-label="Forward"] {
  background: transparent;
  border-width: 2px;
  border-color: var(--bd-control);
  color: var(--tx-2);
}
.pager__p--step:hover,
.pager__more:hover,
.pager__p[aria-label="Back"]:hover,
.pager__p[aria-label="Forward"]:hover,
.pager__p--step:focus-visible,
.pager__more:focus-visible,
.pager__p[aria-label="Back"]:focus-visible,
.pager__p[aria-label="Forward"]:focus-visible,
.pager__more[aria-expanded="true"] {
  background: transparent;
  border-color: var(--ac);
  color: var(--ac);
}

.pager__count { margin-left: var(--sp-3); white-space: nowrap; }

.pager__jump { width: 320px; }

.pager__jumprow { display: flex; align-items: stretch; gap: var(--sp-5); }
.pager__jumprow > .stack-s { flex: 1 1 auto; min-width: 0; }
.pager__acts { display: flex; flex-direction: column; gap: var(--sp-3); justify-content: center; }
.pager__acts .btn { width: var(--h-lg); height: var(--h-lg); padding: 0; flex: none; }

.optbtns { display: flex; flex-wrap: wrap; gap: var(--sp-3); }
.optbtn {
  display: inline-flex; align-items: center; gap: var(--sp-3);
  min-height: var(--h-md); padding: var(--sp-2) var(--sp-5);
  background: var(--sf-0); border: 1px solid var(--bd-edge); border-radius: var(--r-2);
  color: var(--tx-2); font: var(--fw-medium) var(--fs-sm)/1.25 var(--font-ui);
  cursor: pointer; user-select: none;
  transition: background var(--dur-1) var(--ease-out), border-color var(--dur-1) var(--ease-out),
              color var(--dur-1) var(--ease-out);
}
.optbtn:hover { border-color: var(--bd-control); background: var(--sf-1); color: var(--tx-1); }
.optbtn input { accent-color: var(--ac); width: 16px; height: 16px; margin: 0; flex: none; cursor: pointer; }
.optbtn:has(input:checked) { border-color: var(--ac); background: var(--ac-tint-1); color: var(--tx-1); }

.optbtn:has(input:focus-visible) { outline: var(--focus-w) solid var(--focus); outline-offset: var(--focus-offset); }
.optbtn:has(input:disabled) { cursor: not-allowed; color: var(--tx-off); background: var(--sf-0); border-color: var(--bd-edge); }

.reveal {
  display: none; grid-template-rows: 0fr; opacity: 0;
  transition: grid-template-rows var(--dur-3) var(--ease-out),
              opacity var(--dur-3) var(--ease-out),
              display var(--dur-3) allow-discrete;
}
.reveal.is-open { display: grid; grid-template-rows: 1fr; opacity: 1; }
@starting-style { .reveal.is-open { grid-template-rows: 0fr; opacity: 0; } }
.reveal__inner { overflow: hidden; }

.warnbar {
  display: flex; align-items: center; gap: var(--sp-4); flex-wrap: wrap;
  padding: var(--sp-3) var(--sp-4);
  background: var(--warn-tint); border: 1px solid var(--warn);
  border-radius: var(--r-2);
  font: var(--fw-normal) var(--fs-sm)/1.35 var(--font-ui); color: var(--tx-1);
}
.warnbar[hidden] { display: none; }
.warnbar__t { flex: 1 1 20ch; min-width: 0; }
.warnbar__t b { font-weight: var(--fw-medium); }

.warnbar__x {
  flex: none; display: grid; place-items: center;
  width: var(--h-sm); height: var(--h-sm);
  background: var(--sf-0); border: 1px solid var(--bd-control); border-radius: var(--r-2);
  color: var(--tx-2); cursor: pointer;
  transition: background var(--dur-1) var(--ease-out), border-color var(--dur-1) var(--ease-out);
}
.warnbar__x:hover { background: var(--sf-1); border-color: var(--bd-control-hi); color: var(--tx-1); }
.warnbar__x:focus-visible { outline: var(--focus-w) solid var(--focus); outline-offset: var(--focus-offset); }

.modal--gate::backdrop { background: rgb(0 0 0 / 0.55); backdrop-filter: blur(10px); }
.blgate__tags { display: flex; flex-wrap: wrap; gap: var(--sp-2); }

.tag--flat {
  min-height: 22px; padding: 0 var(--sp-3); gap: var(--sp-2);
  cursor: default; touch-action: auto;
  font-size: var(--fs-2xs);
}
.tag--flat:hover { background: var(--sf-1); }
.tag--flat .tag__dot { width: 6px; height: 6px; }

.blgate__confirm { display: grid; gap: var(--sp-5); }
.blgate__confirm[hidden] { display: none; }

.blgate__acts { display: flex; gap: var(--sp-4); flex-wrap: wrap; justify-content: flex-end; }

.blpick__fam { display: grid; gap: var(--sp-3); }
.blpick__fam + .blpick__fam { margin-top: var(--sp-4); }
.blpick__row { display: flex; flex-wrap: wrap; gap: var(--sp-2); }

.tag--pick { cursor: pointer; }
.tag--pick[aria-pressed="true"] {
  border-color: var(--ac); background: var(--ac-tint-1); color: var(--tx-1);
}
.tag--pick:focus-visible { outline: var(--focus-w) solid var(--focus); outline-offset: var(--focus-offset); }

.vshield {
  position: absolute; inset: 0; z-index: 2;
  display: grid; place-items: center; gap: var(--sp-4);
  padding: var(--sp-6); text-align: center;
  background: rgb(0 0 0 / 0.55); backdrop-filter: blur(18px);
  border-radius: var(--r-2);
}
.vshield[hidden] { display: none; }
.vshield__box { display: grid; gap: var(--sp-4); justify-items: center; max-width: 46ch; }
.vshield__t { font: var(--fw-medium) var(--fs-md)/1.35 var(--font-ui); color: #fff; }
.vshield__d { font-size: var(--fs-sm); line-height: 1.45; color: rgb(255 255 255 / 0.82); }
.vshield__d a { color: #fff; }

[data-slides] { position: relative; }
.slides__fade {
  position: absolute; inset: 0; width: 100%; height: 100%;
  display: block; opacity: 0; pointer-events: none; z-index: 1;
  transition: opacity 500ms ease;
}

@media (prefers-reduced-motion: reduce) {
  .slides__fade { transition: none; }
}


/* ============================================================================
   SHOWCASE — the work-in-progress ribbon, the header badge and the age gate.
   Added 2026-09-05 for the Discord preview build; the stencil has none of this.
   ========================================================================= */

/* The ribbon keeps the striped shape of the old simulation bar, but the stripes
   are drawn from --ac, so they follow whichever accent the visitor picked
   instead of shouting a fixed warning colour over their choice. */
.sim--wip {
  background:
    repeating-linear-gradient(135deg,
      color-mix(in srgb, var(--ac) 26%, transparent) 0 10px,
      transparent 10px 20px),
    var(--bg-2);
  border-bottom: 1px solid color-mix(in srgb, var(--ac) 55%, transparent);
  /* Plain white, not a token. The stripes behind it are drawn from whichever accent the
     visitor picked, and a token colour that also moves with the accent can land too close to
     its own background — under the pale schemes the sentence was greying out. White is the one
     value that holds against all fourteen. */
  color: #fff;
  text-transform: none;              /* it is a sentence, not a label */
  letter-spacing: normal;
  font-weight: var(--fw-normal);
  line-height: 1.3;
  height: auto;
  min-height: var(--h-sm);
  padding-block: var(--sp-2);
}
.sim--wip b { color: #fff; font-weight: var(--fw-bold); }

/* Sits right of the wordmark, deliberately quiet: it states the stage of the
   product, it is not a call to action. */
.appbar__wip {
  flex: none;
  padding: 1px var(--sp-3);
  border: 1px solid var(--bd-control);
  border-radius: var(--r-1);
  color: var(--tx-3);
  font-size: var(--fs-3xs);
  line-height: 1.6;
  text-transform: lowercase;
  letter-spacing: var(--ls-caps);
  white-space: nowrap;
}

/* Rides with the wordmark itself rather than beside it, because it qualifies the NAME —
   this is Furry Rome the prototype, not Furry Rome. Inherits nothing loud: same line, one
   step down in size and two in contrast, so the name still reads first. */
.appbar__proto {
  font-family: var(--font-ui);
  font-weight: var(--fw-normal);
  font-size: var(--fs-2xs);
  color: var(--tx-3);
  letter-spacing: normal;
  white-space: nowrap;
}

/* ---- ONBOARDING STEPS ----------------------------------------------------
   One step on screen at a time. [hidden] alone is enough in every current
   browser, but the kit sets display on some of these containers elsewhere and
   a later rule would quietly win — so this one is explicit. */
.ostep[hidden] { display: none !important; }

/* ---- WHERE ONE PART OF A STEP ENDS AND THE NEXT BEGINS --------------------
   Both editors stack a lot of unlike things in one column — a palette, a list with a table of
   its own, three sliders, a tag box — and with nothing but air between them the eye loses which
   control belongs to which heading. A hairline in the middle of the gap answers that one
   question and asks nothing else of the design.

   It stops short of both ends on purpose: a rule that runs edge to edge reads as the top of a
   new BOX, and these are not boxes, they are neighbours.

   Only between two blocks that are both on screen. A Pro block hidden in Simple must not leave
   its line behind — which is why the pair is matched on :not([hidden]) at both ends rather than
   with a plain sibling combinator, and why the line is drawn ABOVE a block rather than below:
   nothing trailing can be left dangling at the foot of a step. */
/* The gap is named once and the line is placed from the same name, so the two can never drift
   apart: half of whatever the gap is, is where the middle is. It is wider than the ordinary
   stack gap because a rule needs air on both sides — pressed against the field above it, a
   separator stops reading as a separator and starts reading as that field's underline. */
[data-screen="persona"]     .ostep,
[data-screen="constructor"] .ostep { --step-gap: var(--sp-8); gap: var(--step-gap); }

[data-screen="persona"]     .ostep > *:not([hidden]) + *:not([hidden]),
[data-screen="constructor"] .ostep > *:not([hidden]) + *:not([hidden]) { position: relative; }

[data-screen="persona"]     .ostep > *:not([hidden]) + *:not([hidden])::before,
[data-screen="constructor"] .ostep > *:not([hidden]) + *:not([hidden])::before {
  content: ""; position: absolute; left: var(--sp-9); right: var(--sp-9);
  top: calc(var(--step-gap) / -2); height: 1px;
  background: var(--bd-hairline); pointer-events: none;
}

/* THE SAME RULE, ON THE SETTINGS SCREEN. Twelve sections down one column, and until now the only
   thing between two of them was space — so a hint belonging to the end of one section read as an
   introduction to the next. Identical construction to the steps above: the gap is named once, the
   line is drawn at half of it, and it stops short of both edges so it separates rather than boxes.
   Drawn ABOVE each section but the first, so nothing can dangle under the last one. */
[data-screen="settings"] .split__main { --sect-gap: var(--sp-8); gap: var(--sect-gap); }
[data-screen="settings"] .split__main > section:not([hidden]) ~ section:not([hidden]) { position: relative; }
[data-screen="settings"] .split__main > section:not([hidden]) ~ section:not([hidden])::before {
  content: ""; position: absolute; left: var(--sp-9); right: var(--sp-9);
  top: calc(var(--sect-gap) / -2); height: 1px;
  background: var(--bd-hairline); pointer-events: none;
}

/* ---- CATALOGUE PANES -----------------------------------------------------
   Characters and Media are two halves of one screen. They were drawn down one
   page in the stencil so the builder could see both; a visitor should see the
   one they asked for. Same !important, same reason as the step panels.

   ⚠️ Named `catpane`, and the first attempt at `cpanel` is why. `.cpanel` was
   already taken — section 61 uses it for a comic-page panel, with a canvas
   background, place-items:center and overflow:hidden. The catalogue inherited
   all three and every control on the screen shrank to its own words and sat in
   the middle of the column. Nothing errored; it just looked wrong. Grep the kit
   before inventing a class name.

   The pane repeats `.stack` rather than borrowing it: these are the wrap's own
   children moved one level down, and they have to keep the wrap's rhythm. */
.catpane[hidden] { display: none !important; }
.catpane { display: grid; gap: var(--sp-5); align-content: start; }

/* ---- THE AGE MARK, ON THE PICTURE ----------------------------------------
   Top-right corner of the thumbnail itself, in the error red rather than the
   warning amber. It is the one thing on a tile a visitor may need to see
   BEFORE they look at the picture, and down in the badge row it arrived after
   the picture had already been looked at. Red because on this palette amber
   reads as "careful" and red as "stop and decide" — which is the actual job.

   Solid backing, not a tint: it sits over an image whose colours are unknown,
   and a translucent chip is legible on some of the 313 and not on others. */
.agemark {
  position: absolute; top: var(--sp-3); right: var(--sp-3); z-index: 1;
  display: inline-flex; align-items: center;
  height: var(--h-xs); padding-inline: var(--sp-3);
  border-radius: var(--r-1);
  background: var(--err); color: #fff;
  border: 1px solid color-mix(in srgb, #000 25%, var(--err));
  font: var(--fw-bold) var(--fs-2xs)/1 var(--font-ui);
  letter-spacing: var(--ls-caps);
  pointer-events: none;
}

/* ---- A ROW WITH A TRUE CENTRE --------------------------------------------
   Three parts. The outer two share the slack equally (`flex: 1 1 0`), so the
   middle one lands on the line's centre and stays there as the window moves —
   which a single `push` cannot do: that pins the right-hand item and leaves
   the middle wherever the left-hand text happened to end. */
.toolrow { display: flex; align-items: center; gap: var(--sp-4); flex-wrap: wrap; }
.toolrow > * { display: flex; align-items: center; gap: var(--sp-4); }
.toolrow > :first-child,
.toolrow > :last-child { flex: 1 1 0; min-width: 0; }
.toolrow > :last-child { justify-content: flex-end; }

/* ---- THE CREATOR, MADE CLICKABLE -----------------------------------------
   Deliberately NOT `.credit`: that is the media grid's pill, with a border, a
   background and its own font, and using it here would have redesigned the
   card instead of linking it. This wrapper is invisible — it inherits colour,
   drops the underline, and repeats .ccard__meta's own gap so wrapping two
   children in one element does not shift them by a pixel.

   Anonymous creators get no link at all: there is no page behind the name. */
.byline {
  display: inline-flex; align-items: center; gap: var(--sp-3);
  color: inherit; text-decoration: none; min-width: 0;
}
.byline:hover .t-hint { color: var(--tx-1); }
/* A byline or a credit chip that is NOT an anchor must not answer the pointer like one. Both are
   styled by class, so swapping the anchor for a span keeps the shape for free — but the hover
   states are written on the class and would go on promising a page that this build no longer
   has. Taking them off by element is the whole fix. */
span.byline:hover .t-hint { color: var(--tx-3); }
span.credit { cursor: default; }
span.credit:hover { border-color: var(--bd-control); background: var(--sf-0); }
.byline:focus-visible { outline: var(--focus-w) solid var(--focus); outline-offset: 2px; }
/* The card row and the character page's fact row space their children differently. The wrapper
   has to repeat whichever one it lands in, or wrapping two elements moves them. */
.fact > .byline { gap: var(--sp-4); }

/* ---- WHAT TO SHOW: A TICK, NOT A NUMBER ----------------------------------
   The ordinal box exists so selecting never reflows the pool, and in a pool
   where order matters a number belongs in it. These two are independent
   switches — nothing is first or second about them — so the box carries a tick
   instead, and only while the chip is on. An unpressed chip keeps the box
   empty and the same size: the reason it was reserved in the first place. */
.chip[data-cat-rating][aria-pressed="true"] .chip__ord::after,
.chip[data-cat-body][aria-pressed="true"] .chip__ord::after {
  content: "✓";
  font-size: var(--fs-2xs);
  line-height: 1;
}

/* ---- START CHATTING ------------------------------------------------------
   The last thing on every card, and the one action a catalogue tile exists for.
   Deliberately borrowed from the pressed SFW/NSFW chip — the dev's own
   reference — so it reads as already-yours-to-press rather than as one more
   decision. Full width because it is the card's conclusion, not an option
   sitting beside others. */
.startchat {
  display: block;
  margin-top: auto;
  padding: 0 var(--sp-4);
  min-height: var(--h-md);
  line-height: calc(var(--h-md) - 2px);
  border: 1px solid var(--ac);
  border-radius: var(--r-2);
  background: var(--ac-tint-1);
  color: var(--tx-1);
  font: var(--fw-bold) var(--fs-sm)/calc(var(--h-md) - 2px) var(--font-ui);
  text-align: center;
  text-decoration: none;
}
.startchat:hover { background: var(--ac-tint-2); text-decoration: none; }

/* The card becomes a column so `margin-top:auto` above can push the button to
   the floor — otherwise tiles with a shorter name sit with the button halfway
   up and the grid reads as ragged. */
.ccard { display: flex; flex-direction: column; }

/* ---- SHUFFLE, WHILE IT IS ON ---------------------------------------------
   It is a mode, not a one-shot, so it has to look switched on from across the
   screen. Same accent fill as a pressed chip plus a ring, because unlike a chip
   this button sits alone with nothing beside it to compare against. */
.btn[data-cat-shuffle][aria-pressed="true"] {
  background: var(--ac-tint-2);
  border-color: var(--ac);
  color: var(--tx-1);
  box-shadow: 0 0 0 2px color-mix(in srgb, var(--ac) 38%, transparent);
}

/* ---- CARD RATING ---------------------------------------------------------
   Sits at the right end of the species line, opposite the badges, and pairs
   with the chat count at the right end of the line below it. Accent-coloured
   because it is the one number on a tile that says "people liked this" —
   everything else there is a fact about the card, not an opinion of it. */
.stars {
  color: var(--ac);
  font-size: var(--fs-2xs);
  line-height: var(--lh-2xs);
  white-space: nowrap;
  font-variant-numeric: tabular-nums;
  /* It is a button now, so the browser's own button skin has to come off — background, border,
     padding and inherited font all reset, or the rating grows a grey box around it. */
  appearance: none; -webkit-appearance: none;
  background: none; border: 0; padding: 0;
  /* ⚠️ NOT `margin: 0`. The rating carries `.push`, which is `margin-left: auto`, and this rule
     sits later in the sheet — a blanket margin reset here silently cancelled it and the star
     slid back to sit against the species badge instead of the right edge. Only the block
     margins are cleared, which is all a button actually needs. */
  margin-block: 0;
  font-family: var(--font-ui);
  cursor: pointer;
}
/* Hollow while it is not yours, solid when it is: the glyph itself carries the state, so the
   card says which it is without hover, without colour vision and at a glance across a grid. */
.stars[aria-pressed="true"] { font-weight: var(--fw-bold); }
.stars:hover { color: var(--tx-1); }
.stars:focus-visible { outline: var(--focus-w) solid var(--focus); outline-offset: 2px; }

/* ---- AGE GATE ------------------------------------------------------------
   The page underneath is BLURRED, not hidden: a visitor should see that there
   is a product behind the question, otherwise the gate reads as a dead end.
   Blur alone is not a protection and is not meant to be one — the screens
   behind it are a mock-up, and nothing here is private. */
.agegate {
  position: fixed; inset: 0; z-index: 9999;
  display: grid; place-items: center;
  padding: var(--sp-6);
  background: color-mix(in srgb, var(--bg-1) 82%, transparent);
  backdrop-filter: blur(14px) saturate(0.85);
  -webkit-backdrop-filter: blur(14px) saturate(0.85);
}
.agegate[hidden] { display: none; }
.agegate__box {
  width: min(440px, 100%);
  display: grid; gap: var(--sp-5);
  padding: var(--sp-7);
  background: var(--bg-2);
  border: 1px solid var(--bd-edge);
  border-radius: var(--r-3);
  box-shadow: var(--sh-3);
  text-align: center;
}
.agegate__t { font: var(--fw-bold) var(--fs-lg)/1.25 var(--font-ui); color: var(--tx-1); }
.agegate__d { font-size: var(--fs-sm); line-height: 1.5; color: var(--tx-2); }
.agegate__acts { display: grid; gap: var(--sp-3); }
/* No blur on a coarse pointer: mobile GPUs drop frames on a full-screen
   backdrop filter, and a stuttering gate is worse than a plain one. */
@media (pointer: coarse) {
  .agegate { backdrop-filter: none; -webkit-backdrop-filter: none;
             background: color-mix(in srgb, var(--bg-1) 96%, transparent); }
}

/* ============================================================================
   TEXT SIZE — the showcase's own lever (2026-09-05)

   The dev's plan for this build asks for colour AND font to survive in the
   local session. Colour had a picker; type never had one — section 1.12 says
   in as many words that the density lever must not touch the type scale, and
   the stencil's own note on Settings lists "font size, separate from density"
   as the thing still missing. So it is a separate lever, exactly as written.

   It redefines the SCALE rather than any rule: 87 declarations already read
   var(--fs-*), so the whole interface moves together and nothing needs a
   second definition. Line heights move with their sizes, or the larger steps
   would set solid.

   ⚠️ `:root:root[...]` and not `:root[...]`: the coarse-pointer block at 1.15
   is deliberately written `:root:root` to outrank ordinary root rules, and it
   pins --fs-sm. A single :root here would lose that one variable on every
   touch device — the lever would work on a laptop and half-work on a phone.
   ========================================================================= */
:root:root[data-textsize="lg"] {
  --fs-3xs: 11px; --lh-3xs: 13px;
  --fs-2xs: 12px; --lh-2xs: 15px;
  --fs-xs:  13px; --lh-xs:  18px;
  --fs-sm:  15px; --lh-sm:  20px;
  --fs-md:  16px; --lh-md:  23px;
  --fs-lg:  18px; --lh-lg:  25px;
  --fs-xl:  23px; --lh-xl:  29px;
  --fs-2xl: 30px; --lh-2xl: 36px;
}
:root:root[data-textsize="xl"] {
  --fs-3xs: 13px; --lh-3xs: 16px;
  --fs-2xs: 14px; --lh-2xs: 18px;
  --fs-xs:  16px; --lh-xs:  21px;
  --fs-sm:  17px; --lh-sm:  23px;
  --fs-md:  18px; --lh-md:  26px;
  --fs-lg:  21px; --lh-lg:  28px;
  --fs-xl:  26px; --lh-xl:  33px;
  --fs-2xl: 34px; --lh-2xl: 41px;
}
