/* Generated by dist/build.js — edit the sources, not this file. */

/* ── css/tokens.css ── */
/* ══════════════════════════════════════════════════════════════
   SILICONE UI — DESIGN TOKENS
   tokens.css · v1.0.0

   One light source. 135° NW. Always.
   Everything in this file derives from that single constraint.

   This is the source of truth for the entire system.
   Swap --c1 through --c5 together. Everything recolours.
   Change --r. The entire material scales.
   Change --led. Every glow adjusts simultaneously.

   Lax Pairs Limited · silicone.studio · MIT Licence
   ══════════════════════════════════════════════════════════════ */


/* ── @property declarations ──────────────────────────────────
   Registers custom properties as animatable.
   Without these, CSS cannot interpolate custom property values.
   All four are animatable via a single JS call:
   document.documentElement.style.setProperty('--r', 2)
   ──────────────────────────────────────────────────────────── */

@property --r {
  syntax: '<number>';
  inherits: true;
  initial-value: 1;
}

@property --led {
  syntax: '<number>';
  inherits: true;
  initial-value: 1;
}

@property --contrast {
  syntax: '<number>';
  inherits: true;
  initial-value: 1;
}

@property --dark {
  syntax: '<number>';
  inherits: true;
  initial-value: 0;
}

@property --light-angle {
  syntax: '<angle>';
  inherits: true;
  initial-value: 135deg;
}

/* Registered so getComputedStyle returns resolved px, not the calc() text */
@property --ox {
  syntax: '<length>';
  inherits: true;
  initial-value: -7px;
}

@property --oy {
  syntax: '<length>';
  inherits: true;
  initial-value: -7px;
}


/* ── :root token block ───────────────────────────────────────
   The complete physics in ~60 lines.
   Every shadow, highlight, depth state, LED intensity,
   font size, line height, and spacing token in the system
   derives from the values declared here.
   ──────────────────────────────────────────────────────────── */

:root {

  /* ── Palette — the colour inputs ────────────────────────────
     Palette tools (presets, the playground's derive, a builder
     import) write only these; every colour role derives from them
     (see COLOUR ROLES below). c1 = darkest (Deep Forest) · c5 =
     lightest (Mist). --hue and --chroma tint the neutrals.      */
  --c1: #013224;  /* Deep Forest */
  --c2: #026448;  /* Forest      */
  --c3: #1AFBBA;  /* Mint        */
  --c4: #9BFDE1;  /* Seafoam     */
  --c5: #CDFEF0;  /* Mist        */
  --hue:    162;
  --chroma: 1;    /* neutral saturation scale — a grey palette uses ~0.37 */

  /* ── Animatable scales ──────────────────────────────────────
     All four registered as @property above.
     Setting one value interpolates the entire dependent system. */
  --r:        1;  /* readability: 0=Short · 1=Grande · 2=Trenta  */
  --led:      1;  /* LED intensity: 0=dim · 1=full               */
  --contrast: 1;  /* contrast: 0=soft · 1=standard · 2=enhanced  */
  --dark:     0;  /* dark mode: 0=light · 1=dark                 */

  /* ── Reading-size curve (D13) ──────────────────────────────
     One multiplier on the root font size scales every text size
     in rem, so headings gain more pixels than labels, and each
     step up is bigger than the last. Body text: Short 13 · Tall
     13.8 · Grande 15 · Venti 16.6 · Trenta 18.5px.               */
  --type-scale: calc(1 + 0.0961535 * var(--r) + 0.0576925 * var(--r) * var(--r));

  /* ── Scale transitions ──────────────────────────────────────
     Spring curve: slight overshoot, fast settle.
     --dark transitions slower — surface colour change is larger. */
  transition:
    --r        .38s cubic-bezier(.34, 1.05, .64, 1),
    --led      .38s cubic-bezier(.34, 1.05, .64, 1),
    --contrast .38s cubic-bezier(.34, 1.05, .64, 1),
    --dark     .5s  cubic-bezier(.34, 1.05, .64, 1);

  /* ── Light source ───────────────────────────────────────────
     135° NW. Always. The one constraint that generates everything.
     Angle is standard maths: degrees anticlockwise from east
     (135° = NW, 90° = N). light.js is its only writer.
     --ox/--oy point toward the light; screen y runs downward,
     hence the minus on --oy. The reach is 7px·√2 at the 1280px
     reference width (exactly --ox: -7px / --oy: -7px at rest)
     and scales 0.3×–1.1× with the viewport.                     */
  --light-angle: 135deg;
  --light-reach: clamp(2.97px, 0.7734vw, 10.89px);
  --ox: calc(cos(var(--light-angle)) * var(--light-reach));
  --oy: calc(sin(var(--light-angle)) * var(--light-reach) * -1);
  /* The same direction scaled to 1 per axis at rest, for the 1px
     light-aware rims (--rim-*): calc(var(--lx) * 1px)            */
  --lx: calc(cos(var(--light-angle)) *  1.41421356);
  --ly: calc(sin(var(--light-angle)) * -1.41421356);

  /* ── Spacing ────────────────────────────────────────────────
     Three base wall tokens. All other spacing derives from these.
     vp-margin scales with viewport width in JS (updateOrientation). */
  --wall:      clamp(16px, 2.8vw, 22px);
  --wall-sm:   round(calc(var(--wall) * .67), 1px);
  --wall-lg:   round(calc(var(--wall) * 1.56), 1px);
  --vp-margin: clamp(16px, 4vw, 48px);

  /* Control padding — in rem, so it grows with the control's text
     on the reading-size curve. Values are at Short.               */
  --pad-control:    0.5625rem 1.125rem;   /* buttons: 9px 18px       */
  --pad-control-sm: 0.375rem 0.75rem;     /* small buttons: 6px 12px */
  --pad-pill:       0.1875rem 0.5625rem;  /* chips: 3px 9px          */
  --pad-pill-sm:    0.125rem 0.4375rem;   /* pills, sub-chips: 2px 7px */

  /* Micro-gaps — sub-token precision for dense components        */
  --gap-micro-1: 2px;   /* tightest: step rail, separator lines    */
  --gap-micro-2: 3px;   /* map marker rows, bump map cells         */
  --gap-micro-3: 4px;   /* chip/badge internal, icon+label         */
  --gap-micro-4: 5px;   /* chip groups, nav badge gap              */
  --gap-micro-5: 6px;   /* form control gaps, small button gap     */
  --gap-micro-6: 8px;   /* dense group gap, breadcrumb sep         */

  /* ── Typography ─────────────────────────────────────────────
     Two named sizes for component text — fields, labels, toasts.
     In rem, so both follow the reading-size curve.                */
  --fs-body:    0.8125rem;   /* 13px at Short */
  --fs-body-lg: 0.9375rem;   /* 15px at Short */

  /* ── Cursor position ────────────────────────────────────────
     Set by cursor.js on every mousemove.
     Used by proximity sensor and any CSS that responds to cursor. */
  --cursor-x: -200px;
  --cursor-y: -200px;

  /* ── Sidebar ────────────────────────────────────────────────
     Width as unitless number (for calc use) and as px value.     */
  --sidebar-w:    220;
  --sidebar-w-px: 220px;

  /* ── LED semantic colours ───────────────────────────────────
     Four named LED colours for semantic use in components.
     led-player = mint (own system, always safe)
     led-material = amber (physical material state)
     led-risk = red (error, alert, critical)
     led-reg = blue (regulatory, informational)                   */
  --led-player:   #1AFBBA;  /* = --c3 */
  --led-material: #F5A623;
  --led-risk:     #E8453C;
  --led-reg:      #4A90D9;

  /* ── Light-aware padding ────────────────────────────────────
     NW face catches light — less padding needed.
     SE face falls in shadow — more padding needed.
     Both scale with --r so readability slider affects every card.
     Rule: pad-nw < pad-se always.                               */
  --pad-nw:    calc(var(--wall-sm) + var(--r) * 1px);
  --pad-se:    calc(var(--wall)    + var(--r) * 2px);
  --pad-nw-lg: calc(var(--wall)    + var(--r) * 1px);
  --pad-se-lg: calc(var(--wall-lg) + var(--r) * 2px);

  /* ── Gap tokens ─────────────────────────────────────────────
     Minimum gap between raised elements.
     Must exceed shadow spread to prevent depth collisions.       */
  --gap-card: calc(var(--wall-sm) + var(--r) * 1px);
  --gap-deep: calc(var(--wall)    + var(--r) * 2px);

  /* ── Edge hint tokens ───────────────────────────────────────
     Controls the thickness and radius of the four breathing
     edge hints. Thin enough to be subtle, visible enough
     to signal the affordance.                                 */
  --hint-size:   3px;
  --hint-radius: 2px;

  /* ── Icon / symbol minimum (MF-ICON-01) ─────────────────────
     Minimum interactive area for icons and symbols.
     Scales with --r so readability slider affects touch targets.
     On mobile (device 0): floor is 44px (finger contact area).
     On tablet (device 1): floor is 32px.
     On desktop (device 2): governed by --icon-min directly.
     Visual size and hit area are both governed by this token.  */
  --icon-min: calc(16px + var(--r) * 6px);
  /* At --r 0.5: ~19px  |  --r 1: 22px  |  --r 2: 28px        */

  /* ── Engine tokens (Level 0-9 outputs from engine.js) ───────
     Declared here so CSS can reference them before engine.js
     runs its first frame. engine.js overwrites these at runtime.
     Values here are the Grande (--r=1), desktop defaults.      */

  /* Level 0 — Core inputs */
  --vw:  1280px;
  --vh:  800px;
  --ar:  1.600;

  /* Level 1 — Device & menus */
  --device:   2;
  --menu-l:   220px;
  --menu-r:   0px;
  --menu-t:   0px;
  --menu-b:   0px;

  /* Level 2 — Available space */
  --available-w: 1060px;
  --available-h: 800px;

  /* Level 3 — Typography */
  --fs-base:    18.08px;
  --ch-width:   9.04px;
  --line-length: 117.3;
  --line-h:     25.31px;
  --letter-sp:  0.542px;

  /* Level 4 — Layout mode */
  --layout-mode:   2;
  --max-content-w: 860px;

  /* Level 6 — Spacing & depth factors */
  --spacing-base:     16px;
  --depth-factor-n1:  0.75;
  --depth-factor-0:   1.00;
  --depth-factor-p05: 1.15;
  --depth-factor-p1:  1.30;
  --depth-factor-p2:  1.50;

  /* Level 7 — Aspect ratio bias */
  --ar-bias-lh: 1.000;
  --ar-bias-sp: 1.000;

  /* Level 9 — Menu coverage */
  --menu-coverage: 0.172;

}

/* ── Reduced motion ──────────────────────────────────────────
   A reading-size change resizes every line of text, so under
   reduced motion it lands at once. LED, contrast and theme only
   change colour and glow: they keep a quick fade.             */

@media (prefers-reduced-motion: reduce) {
  :root {
    transition:
      --led      80ms ease,
      --contrast 80ms ease,
      --dark     80ms ease;
  }
}


/* ══ COLOUR ROLES ══════════════════════════════════════════════
   Derived from the palette and two registered controls, --dark
   (0 light → 1 dark) and --contrast (0 soft · 1 standard · 2
   enhanced). Scripts change those inputs, never these roles, so
   dark mode, contrast and palette can't overwrite each other.
   Also declared on [data-theme], so a section can set its own
   theme and every role inside it re-derives.
   Light direction never changes with the theme — only brightness. */

[data-theme="light"] { --dark: 0; }
[data-theme="dark"]  { --dark: 1; }

:root,
[data-theme] {
  --_light-share:   calc(100% - var(--dark) * 100%);
  --_contrast-step: calc(var(--contrast) - 1);

  /* The mat. shadow-light = NW face · shadow-dark = SE face     */
  --surface:      color-mix(in oklab,
                    hsl(var(--hue) calc(38% * var(--chroma)) 91%) var(--_light-share),
                    hsl(var(--hue) calc(28% * var(--chroma)) 14%));
  --shadow-light: color-mix(in oklab,
                    hsl(var(--hue) calc(60% * var(--chroma)) 97%) var(--_light-share),
                    hsl(var(--hue) calc(28% * var(--chroma)) 20%));
  --shadow-dark:  color-mix(in oklab,
                    hsl(var(--hue) calc(20% * var(--chroma)) 76%) var(--_light-share),
                    hsl(var(--hue) calc(20% * var(--chroma)) 7%));

  /* Text moves away from the surface as contrast rises, in either theme */
  --text-primary:   color-mix(in oklab,
                      hsl(var(--hue) calc(38% * var(--chroma)) calc(18% - var(--_contrast-step) * 6.5%)) var(--_light-share),
                      hsl(var(--hue) calc(20% * var(--chroma)) calc(88% + var(--_contrast-step) * 6.5%)));
  --text-secondary: color-mix(in oklab,
                      hsl(var(--hue) calc(22% * var(--chroma)) calc(31% - var(--_contrast-step) * 7%)) var(--_light-share),
                      hsl(var(--hue) calc(15% * var(--chroma)) calc(70% + var(--_contrast-step) * 7%)));
  /* Text steps primary > secondary > muted at every contrast level.
     At standard, muted meets WCAG AA (4.5:1) on both surfaces —
     38.5% on light, 56% on dark — and secondary sits a clear step
     above it (31% on light: 5.95:1). ±5% / ±7% per contrast step. */
  --text-muted:     color-mix(in oklab,
                      hsl(var(--hue) calc(14% * var(--chroma)) calc(38.5% - var(--_contrast-step) * 5%)) var(--_light-share),
                      hsl(var(--hue) calc(12% * var(--chroma)) calc(56%   + var(--_contrast-step) * 5%)));

  /* Rims (WCAG 1.4.11): a 1px line at the edge of a surface, light-
     aware as an engraver's shade lines are (print/paper.css): light
     on the faces toward the light, shade on the faces away from it.
     Whichever of the two contrasts with the sheet — shade on light,
     light on dark — is drawn from muted text, so it moves with the
     contrast setting: against the surface 2.6 · 3.5 · 5.1 : 1 on
     light and 2.9 · 3.9 · 5.2 : 1 on dark (soft · standard ·
     enhanced), with room above 3:1 for gloss and other palettes.
     The other stays a soft highlight or shadow.                    */
  --_rim:      color-mix(in oklab, var(--text-muted) calc(85% + var(--_contrast-step) * 10%), var(--surface));
  --rim-shade: color-mix(in oklab, var(--_rim) var(--_light-share), var(--shadow-dark));
  --rim-lit:   color-mix(in oklab, var(--shadow-light) var(--_light-share), var(--_rim));

  /* Rim recipes, listed first in a box-shadow so they paint on top.
     An inset offset shades the edge opposite its direction, so +1px
     (away from the light) draws the NW faces and −1px the SE faces.
     Controls carry rims at every contrast level — the rim marks
     what you can operate, and flips with it when pressed. Surfaces
     that only hold or report (cards, bumps, cavities, wells, badges)
     use the -enhanced recipes, drawn only above standard contrast.  */
  --rim-raised:
    inset calc(var(--lx) * -1px) calc(var(--ly) * -1px) 0 var(--rim-lit),
    inset calc(var(--lx) *  1px) calc(var(--ly) *  1px) 0 var(--rim-shade);
  --rim-recess:
    inset calc(var(--lx) * -1px) calc(var(--ly) * -1px) 0 var(--rim-shade),
    inset calc(var(--lx) *  1px) calc(var(--ly) *  1px) 0 var(--rim-lit);
  --_rim-enhanced: calc(clamp(0, var(--contrast) - 1, 1) * 100%);
  --rim-raised-enhanced:
    inset calc(var(--lx) * -1px) calc(var(--ly) * -1px) 0 color-mix(in oklab, var(--rim-lit)   var(--_rim-enhanced), transparent),
    inset calc(var(--lx) *  1px) calc(var(--ly) *  1px) 0 color-mix(in oklab, var(--rim-shade) var(--_rim-enhanced), transparent);
  --rim-recess-enhanced:
    inset calc(var(--lx) * -1px) calc(var(--ly) * -1px) 0 color-mix(in oklab, var(--rim-shade) var(--_rim-enhanced), transparent),
    inset calc(var(--lx) *  1px) calc(var(--ly) *  1px) 0 color-mix(in oklab, var(--rim-lit)   var(--_rim-enhanced), transparent);

  /* Focus: a 2px ring in the accent, 6.1:1 on light, 12:1 on dark */
  --focus-ring: var(--accent);

  /* Accent text and marks — the palette's forest on light, seafoam on dark */
  --accent: color-mix(in oklab, var(--c2) var(--_light-share), var(--c4));

  /* LEDs */
  --led-active:   var(--c3);
  --led-inactive: color-mix(in oklab, var(--shadow-dark) 60%, var(--surface));
  /* A lit mark that is a control's only state signal — the check
     tick, the radio dot — must reach 3:1 (WCAG 1.4.11). Mint does
     on dark (10.6:1) but not on light (1.15:1), so on light the
     mark lights in the accent (6.1:1); its glow stays mint.         */
  --led-mark:     color-mix(in oklab, var(--accent) var(--_light-share), var(--led-active));
  /* Disabled words and icons: muted text pulled 30% toward an unlit
     LED — dimmed but readable (≈2.1:1 at standard contrast, light) */
  --text-disabled: color-mix(in oklab, var(--led-inactive) 30%, var(--text-muted));
  --glow:         color-mix(in srgb, var(--led-active) 18%, transparent);

  /* Status signals (status.css). Colour is never the only signal. */
  --status-idle:    var(--led-inactive);
  --status-ready:   var(--led-active);
  --status-success: hsl(142 64% 45%);
  --status-warning: var(--led-material);
  --status-error:   var(--led-risk);
}


/* ── css/reset.css ── */
/* ══════════════════════════════════════════════════════════════
   SILICONE UI — RESET & BASE
   reset.css · v1.0.0

   Box model, html/body baseline, typography scale,
   spacing utilities, text selection physics.

   Depends on: tokens.css
   Lax Pairs Limited · silicone.studio · MIT Licence
   ══════════════════════════════════════════════════════════════ */


/* ── Box model & base ────────────────────────────────────────  */

*,
*::before,
*::after {
  box-sizing: border-box;
  margin:  0;
  padding: 0;
}

html,
body {
  font-family: 'Inter', sans-serif;
  line-height: calc((1.65 + var(--r) * 0.2) * var(--ar-bias-lh));
  color:      var(--text-primary);
  background: var(--surface);
}

/* Type is in rem. The root is the reader's own text size times
   the reading-size curve (--type-scale, tokens.css), so every text
   size follows both the browser setting and the reading size.
   Body text is 13px at Short at the default browser size.        */
html {
  font-size: calc(100% * var(--type-scale));
}

body {
  font-size: 0.8125rem;
}


/* ── Page column ─────────────────────────────────────────────
   Constrains content width and centres it.                    */

.page {
  max-width: 680px;
  margin: 0 auto;
  padding-bottom: var(--wall-lg);
}


/* ── Typography ──────────────────────────────────────────────
   All headings: Inter 800. Sizes fluid via clamp().
   Each size range has two endpoints that both scale with --r
   so the fluid scale stays proportional as readability changes. */

h1 {
  font-family:     'Inter', sans-serif;
  font-weight:     800;
  font-size:       clamp(1.75rem, 5.5vw, 3.125rem);
  letter-spacing:  -0.025em;
  line-height:     1.05;
  color:           var(--text-primary);
  margin-bottom:   var(--wall);
  transition:      font-size .38s ease;
}

h2 {
  font-family:     'Inter', sans-serif;
  font-weight:     800;
  font-size:       clamp(1.1875rem, 3.5vw, 1.9375rem);
  letter-spacing:  -0.02em;
  line-height:     1.1;
  color:           var(--text-primary);
  transition:      font-size .38s ease;
}

h3 {
  font-family:     'Inter', sans-serif;
  font-weight:     800;
  font-size:       clamp(0.875rem, 2.2vw, 1.0625rem);
  letter-spacing:  -0.01em;
  line-height:     1.2;
  color:           var(--text-primary);
  transition:      font-size .38s ease;
}

/* Display — Calistoga. Brand marks and hero headings only.
   Not for body copy or section headings.                      */
.display {
  font-family:    'Calistoga', serif;
  letter-spacing: -0.01em;
}

/* Accent — forest green text. Inline emphasis.                */
.accent { color: var(--accent); }

/* Label — uppercase tracking. Category indicators, metadata.  */
.label {
  font-weight:     600;
  font-size:       0.5625rem;
  letter-spacing:  0.08em;
  text-transform:  uppercase;
  color:           var(--text-muted);
}
.label-accent { color: var(--accent); }

/* Body copy — three sizes, all scaling with --r.
   Unitless line heights: engine.js supplies only --ar-bias-lh. */
.body {
  font-size:   0.8125rem;
  line-height: calc((1.7  + var(--r) * 0.2) * var(--ar-bias-lh));
  color:       var(--text-secondary);
  transition:  font-size .38s ease, line-height .38s ease;
}

.body-lg {
  font-size:   0.875rem;
  line-height: calc((1.75 + var(--r) * 0.2) * var(--ar-bias-lh));
  color:       var(--text-secondary);
  transition:  font-size .38s ease, line-height .38s ease;
}

.body-sm {
  font-size:   0.6875rem;
  line-height: calc((1.65 + var(--r) * 0.2) * var(--ar-bias-lh));
  color:       var(--text-secondary);
  transition:  font-size .38s ease, line-height .38s ease;
}

/* Reduced motion: text growing or shrinking in place is a zoom,
   so type takes its new reading size at once.                 */
@media (prefers-reduced-motion: reduce) {
  h1, h2, h3,
  .body, .body-lg, .body-sm {
    transition: none;
  }
}


/* ── Spacing utilities ───────────────────────────────────────
   Margin-bottom and margin-top helpers.
   mb12 uses --wall-sm so it scales with the spacing system.   */

.mb4  { margin-bottom: 4px; }
.mb8  { margin-bottom: 8px; }
.mb12 { margin-bottom: var(--wall-sm); }
.mb16 { margin-bottom: 16px; }
.mb24 { margin-bottom: 24px; }
.mb28 { margin-bottom: 28px; }
.mt8  { margin-top: 8px; }
.mt16 { margin-top: 16px; }

/* Flex row with wrap and gap.                                 */
.gap8 {
  display:     flex;
  flex-wrap:   wrap;
  gap:         8px;
  align-items: center;
}


/* ── Grid utilities ──────────────────────────────────────────
   Responsive auto-fit grids. Minimum column widths chosen
   so two columns fit on mobile, three on desktop.             */

.grid-2 {
  display:               grid;
  grid-template-columns: repeat(auto-fit, minmax(min(100%, 280px), 1fr));
  gap:                   var(--wall);
  margin-bottom:         var(--wall-lg);
}

.grid-3 {
  display:               grid;
  grid-template-columns: repeat(auto-fit, minmax(min(100%, 180px), 1fr));
  gap:                   var(--wall);
  margin-bottom:         var(--wall-lg);
}


/* ── Text selection — physics ────────────────────────────────
   Selection uses mint at 0.15 alpha — maximum corner diffusion,
   surface bleeds through. NW text-shadow catches raised text.
   Heading selection inverts the shadow direction — the heading
   presses into the surface (debosses) when selected.          */

::selection {
  background: rgba(26, 251, 186, 0.15);
  color:      var(--c1);
  /* NW light catches raised text — 135° physics */
  text-shadow:
    -0.5px -0.5px 0.8px rgba(205, 254, 240, 0.85),
     0.5px  0.5px 1.2px rgba(1,   50,  36,  0.18);
}

/* Dark card selection — same physics, higher contrast         */
.card-dark ::selection {
  background: rgba(26, 251, 186, 0.12);
  color:      var(--c5);
  text-shadow:
    -0.5px -0.5px 0.8px rgba(205, 254, 240, 0.95),
     0.5px  0.5px 1.2px rgba(1,   50,  36,  0.55);
}

/* Heading selection — depth state switch.
   Light direction inverts — catching the recess.
   Heading presses into surface when selected.                 */

h1::selection {
  background: rgba(26, 251, 186, 0.12);
  color:      var(--accent);
  text-shadow:
     0.8px  0.8px 1px   rgba(205, 254, 240, 0.7),
    -0.8px -0.8px 1.5px rgba(1,   50,  36,  0.35);
}

h2::selection {
  background: rgba(26, 251, 186, 0.12);
  color:      var(--accent);
  text-shadow:
     0.6px  0.6px 0.8px rgba(205, 254, 240, 0.65),
    -0.6px -0.6px 1.2px rgba(1,   50,  36,  0.3);
}

h3::selection {
  background: rgba(26, 251, 186, 0.12);
  color:      var(--accent);
  text-shadow:
     0.5px  0.5px 0.6px rgba(205, 254, 240, 0.6),
    -0.5px -0.5px 1px   rgba(1,   50,  36,  0.28);
}


/* ── css/blocks.css ── */
/* ══════════════════════════════════════════════════════════════
   SILICONE UI — LAYOUT BLOCKS · blocks.css
   Page composition (DESIGN.md §17) as attributes, because layout
   kind is categorical: data-layout = view | section | group | row |
   grid | surface. The builder's compiler emits them (builder/
   FORMAT.md); hand-written pages can use them too.

   Siblings are spaced with gap from the wall tokens, never margins,
   so components that bring a bottom margin drop it inside a block.
   A view measures its own column with a container query and lays
   its blocks out by that width (layout modes, below).
   ══════════════════════════════════════════════════════════════ */


/* ── View: the centred page column ───────────────────────────
   A size container named column, so the blocks inside lay out by
   the width they really have. width: 100% keeps it full width in
   a flex parent, where inline-size containment would collapse it. */
[data-layout="view"] {
  container:      column / inline-size;
  width:          100%;
  max-width:      60rem;
  margin-inline:  auto;
  padding-inline: var(--vp-margin);
  padding-block:  var(--wall-lg) calc(var(--wall-lg) * 2);
  display:        grid;
  gap:            var(--wall-lg);
}

/* The page's own view sits centred on both axes of the screen
   (DESIGN.md §17): it fills the height and centres a short page;
   safe, so a long one starts at the top.                        */
body > [data-layout="view"] {
  min-height:    100vh;
  min-height:    100dvh;
  align-content: safe center;
}

/* ── Nesting levels, each with its own gap ───────────────────  */
[data-layout="section"] { display: grid; gap: var(--wall); }
[data-layout="group"]   { display: grid; gap: var(--wall-sm); }

[data-layout="row"] {
  display:     flex;
  flex-wrap:   wrap;
  align-items: center;
  gap:         var(--wall-sm);
}
[data-layout="row"][data-align="end"]     { justify-content: flex-end; }
[data-layout="row"][data-align="between"] { justify-content: space-between; }

[data-layout="grid"] {
  display: grid;
  gap:     var(--wall);
  grid-template-columns: repeat(auto-fit, minmax(min(100%, var(--grid-min, 14rem)), 1fr));
}

/* Text and wide content wrap inside a block instead of widening it */
[data-layout]:not([data-layout="row"]) > * { min-width: 0; }

/* Gaps space siblings; component margins would double them */
[data-layout] > :is(.card, .card-dark, .field-group) { margin-bottom: 0; }

/* Pills and buttons keep their own width in a stacked block */
[data-layout]:not([data-layout="row"]) > :is(.btn, .chip, .fb-badge, .field-toggle-wrap, .field-check-wrap) { justify-self: start; }

/* Running text stops at a reading measure of 65 characters,
   counted at 0.5em each, however wide its block               */
[data-layout]:not([data-layout="row"]) > p { max-width: 32.5em; }

/* ── Layout modes — the column measures itself ───────────────
   Blocks lay out by the view's content width in its own font
   size, so the viewport, the edges around the view and the
   reader's text size all count. Widths are in characters at
   0.5em each (DESIGN.md §9), the thresholds engine.js uses:

     flow        ≥ 55 characters   ≥ 27.5em   the rules above
     compressed  45–54             < 27.5em   gaps and padding give
     overlay     < 45              < 22.5em   one column

   That is layout pressure's order (§13): decorative gaps give
   first, then decorative padding, then columns. Text and targets
   never shrink. Relief, the step between gaps and padding, waits
   for --melt. Every block in a view carries its mode as
   --column-mode, for tools such as the builder to read.        */

[data-layout="view"] > * { --column-mode: flow; }

@container column (width < 27.5em) {
  [data-layout="view"] > * { --column-mode: compressed; }

  [data-layout="section"],
  [data-layout="grid"] { gap: var(--wall-sm); }

  [data-layout="surface"] {
    padding: var(--pad-nw) var(--pad-se) var(--pad-se) var(--pad-nw);
  }
}

@container column (width < 22.5em) {
  [data-layout="view"] > * { --column-mode: overlay; }

  [data-layout="grid"] { grid-template-columns: 1fr; }
}

/* ── Surface: a region at one depth ──────────────────────────
   The depth class (.cavity, .well, .bump, .card, .card.deep)
   brings the shadow; this brings padding and inner spacing where
   the class doesn't. data-depth names the depth for tools such as
   the builder's depth-map overlay.                               */
[data-layout="surface"] {
  background:    var(--surface);
  border-radius: 16px;
  padding:       var(--pad-nw-lg) var(--pad-se-lg) var(--pad-se-lg) var(--pad-nw-lg);
  display:       grid;
  gap:           var(--wall-sm);
  align-content: start;
  color:         inherit;
  text-decoration: none;
}

/* ── Single-select groups (controls.js) ──────────────────────  */
[data-select="one"] {
  display:       inline-flex;
  flex-wrap:     wrap;
  gap:           var(--gap-micro-6);
  --well-radius: 100px;
}

/* ── Controls rendered as links or buttons ───────────────────  */
a.btn  { text-decoration: none; }
button.chip,
a.chip { border: 0; font-family: inherit; text-decoration: none; cursor: pointer; }

/* ── Text tone and media ─────────────────────────────────────  */
[data-tone="primary"] { color: var(--text-primary); }
[data-tone="muted"]   { color: var(--text-muted); }

[data-layout] figure    { display: grid; gap: var(--gap-micro-6); }
[data-layout] img       { max-width: 100%; height: auto; border-radius: 12px; }
[data-layout] figcaption { font-size: 0.6875rem; color: var(--text-muted); }


/* ── css/depth.css ── */
/* ══════════════════════════════════════════════════════════════
   SILICONE UI — DEPTH SYSTEM
   depth.css · v1.0.0

   Five states on a continuous z-axis scale.
   Raised = interactive. Always.
   Debossed = recedes, receives, or is inactive.
   The surface tells you what to do before you touch it.

   Depth is not decoration. Depth is information.

   State    z-level   Shadow scale   Interactive
   ──────────────────────────────────────────────
   debossed   −1       inset          no
   well       −0.5     inset 0.25     holds interactive children
   flush        0      none           no
   bump        +0.5    0.35           context
   extrude     +1      0.75           yes
   deep        +2      1.1            yes (one per composition)

   NW gloss: all raised elements get a 135° linear-gradient
   ::before layer. Light source at 135° NW catches the top-left
   face of every raised surface.

   Depends on: tokens.css
   Lax Pairs Limited · silicone.studio · MIT Licence
   ══════════════════════════════════════════════════════════════ */


/* ── Extrude z+1 — .card ─────────────────────────────────────
   The default interactive container.
   Raised from the mat surface.
   Light-aware padding: NW face = less, SE face = more.
   Shadow scale 0.75 on both axes.                             */

.card {
  border-radius:   16px;
  background:      var(--surface);
  position:        relative;
  overflow:        hidden;
  margin-bottom:   var(--wall-lg);

  /* Extrude shadow — NW light, SE dark */
  box-shadow:
    var(--rim-raised-enhanced),
    calc(var(--ox) *  .75) calc(var(--oy) *  .75) 14px var(--shadow-light),
    calc(var(--ox) * -.75) calc(var(--oy) * -.75) 14px var(--shadow-dark);

  /* Light-aware padding — NW less, SE more */
  padding-top:    var(--pad-nw-lg);
  padding-left:   var(--pad-nw-lg);
  padding-bottom: var(--pad-se-lg);
  padding-right:  var(--pad-se-lg);

  transition:
    box-shadow .22s ease,
    transform  .22s ease,
    padding    .38s cubic-bezier(.34, 1.05, .64, 1);
}

/* NW gloss — 135° light catches the top-left face */
.card::before {
  content:        '';
  position:       absolute;
  inset:          0;
  border-radius:  16px;
  background:     linear-gradient(135deg, rgba(255,255,255,.14) 0%, transparent 55%);
  pointer-events: none;
  z-index:        1;
}


/* ── Deep z+2 — .card.deep ───────────────────────────────────
   Hero prominence. One per composition.
   Shadow scale 1.1. Slightly more SE padding.
   Stronger NW gloss (0.18 vs 0.14).                          */

.card.deep {
  box-shadow:
    var(--rim-raised-enhanced),
    calc(var(--ox) *  1.1) calc(var(--oy) *  1.1) 22px var(--shadow-light),
    calc(var(--ox) * -1.1) calc(var(--oy) * -1.1) 22px var(--shadow-dark),
    0 2px 4px rgba(1, 50, 36, .04);

  padding-bottom: calc(var(--pad-se-lg) + 4px);
  padding-right:  calc(var(--pad-se-lg) + 4px);
}

.card.deep::before {
  /* Stronger gloss — deeper element catches more light */
  background: linear-gradient(135deg, rgba(255,255,255,.18) 0%, transparent 50%);
}


/* ── Dark card — .card-dark ──────────────────────────────────
   Same physics, inverted surface.
   Background = --c1 (Deep Forest).
   Text = --c4 (Seafoam).
   Gloss = mint tint instead of white — light source is mint
   on dark surfaces.
   Shadow colours derived from deep forest palette.            */

.card-dark {
  border-radius: 16px;
  background:    var(--c1);
  color:         var(--c4);
  position:      relative;
  overflow:      hidden;
  margin-bottom: var(--wall-lg);

  box-shadow:
    calc(var(--ox) *  .9) calc(var(--oy) *  .9) 18px hsl(162, 40%, 10%),
    calc(var(--ox) * -.9) calc(var(--oy) * -.9) 18px hsl(162, 30%, 24%);
}

/* Mint gloss on dark — NW light is mint-tinted on deep forest */
.card-dark::before {
  content:        '';
  position:       absolute;
  inset:          0;
  border-radius:  16px;
  background:     linear-gradient(135deg, rgba(26,251,186,.07) 0%, transparent 55%);
  pointer-events: none;
}


/* ── Debossed z-1 — .cavity ──────────────────────────────────
   Pressed into the surface.
   Inset shadow — NW dark (shadow falls into the recess),
   SE light (light bounces off the far wall of the cavity).
   Inset offsets shade the edge opposite their direction, so
   the dark layer uses −k·--ox to land on the NW inner edge.
   No ::before gloss — recessed surfaces don't catch NW light.
   Use for: inputs, sidebar nav, inactive content, data boxes. */

.cavity {
  border-radius: 12px;
  background:    var(--surface);
  box-shadow:
    var(--rim-recess-enhanced),
    inset calc(var(--ox) * -.5) calc(var(--oy) * -.5) 10px var(--shadow-dark),
    inset calc(var(--ox) *  .5) calc(var(--oy) *  .5) 10px var(--shadow-light);
}


/* ── Well z−0.5 — .well ──────────────────────────────────────
   Shallow grouping recess, half a cavity deep, that holds raised
   controls: a button row, a tab set. The padding is the rim wall,
   so outer radius = inner radius + wall and nested corners stay
   concentric. Set --well-radius to the contents' corner radius. */

.well {
  --well-wall:   var(--wall-sm);
  --well-radius: 12px;
  padding:       var(--well-wall);
  border-radius: calc(var(--well-radius) + var(--well-wall));
  background:    var(--surface);
  box-shadow:
    var(--rim-recess-enhanced),
    inset calc(var(--ox) * -.25) calc(var(--oy) * -.25) 6px var(--shadow-dark),
    inset calc(var(--ox) *  .25) calc(var(--oy) *  .25) 6px var(--shadow-light);
}


/* ── Bump z+0.5 — utility ────────────────────────────────────
   Slightly proud of the surface.
   Smaller shadow than extrude — less height, less cast.
   Use for: secondary interactive elements, structural containers
   that aren't the primary action.                             */

.bump {
  box-shadow:
    var(--rim-raised-enhanced),
    calc(var(--ox) *  .35) calc(var(--oy) *  .35) 6px var(--shadow-light),
    calc(var(--ox) * -.35) calc(var(--oy) * -.35) 6px var(--shadow-dark);
}


/* ── Contact illumination — white LED ────────────────────────
   When the Silicone cursor approaches an interactive element,
   the cursor emits white light from below.
   The element responds by emitting white light upward.
   The two meet at the surface — completing the circuit.

   This is the link illumination system.
   Triggered by .cursor-on-link class on <html>.
   cursor.js manages this class on mousemove.                  */

/* Cards and interactive rows: white glow on contact */
/* A surface that is itself a link or a button is a control, so it
   carries the control rim at every contrast level (tokens.css)  */
:is(a[href], button):is(.card, .bump) {
  --rim-raised-enhanced: var(--rim-raised);
}

.cursor-on-link .card:hover,
.cursor-on-link .cite-row:hover,
.cursor-on-link .social-link:hover,
.cursor-on-link .timeline-item:hover,
.cursor-on-link .lh:hover {
  box-shadow:
    var(--rim-raised-enhanced),
    calc(var(--ox) *  .8) calc(var(--oy) *  .8) 16px var(--shadow-light),
    calc(var(--ox) * -.8) calc(var(--oy) * -.8) 16px var(--shadow-dark),
    0 0 10px rgba(255, 255, 255, 0.14),
    0 0 22px rgba(255, 255, 255, 0.06);
}


/* ── Gloss-active — double-tap ───────────────────────────────
   Double-tap a card fires a radial gradient at the exact
   touch/click position. Light source from that point.
   The gloss appears to emerge from where the finger contacted.
   Auto-dismisses after 1.8s.
   interactions.js manages activateGloss() / deactivateGloss(). */

.card.gloss-active::after,
.card.deep.gloss-active::after,
.card-dark.gloss-active::after {
  content:        '';
  position:       absolute;
  inset:          0;
  border-radius:  16px;
  background:     radial-gradient(
    circle at var(--gloss-x, 50%) var(--gloss-y, 30%),
    rgba(255, 255, 255, .28) 0%,
    rgba(255, 255, 255, .10) 35%,
    transparent 65%
  );
  pointer-events: none;
  z-index:        2;
  animation:      gloss-appear .22s ease forwards;
  /* @keyframes gloss-appear is defined in motion.css (canonical home) */
}


/* ── Card wiggle ─────────────────────────────────────────────
   Physical response to tap. Micro-rotation with matching scale.
   Feels like the card absorbed the tap energy.
   interactions.js adds .wiggling class on click.
   @keyframes card-wiggle is defined in motion.css.            */

.card.wiggling,
.card-dark.wiggling {
  animation: card-wiggle .3s ease;
}


/* ── Reduced motion ──────────────────────────────────────────
   Depth changes still show, as a quick shadow fade; the card no
   longer lifts or pads out. The wiggle ends at once (animationend
   still fires, so .wiggling is cleared) and the gloss appears
   without fading in.                                          */

@media (prefers-reduced-motion: reduce) {
  .card {
    transition: box-shadow 80ms ease;
  }

  .card.wiggling,
  .card-dark.wiggling,
  .card.gloss-active::after,
  .card.deep.gloss-active::after,
  .card-dark.gloss-active::after {
    animation-duration: 0.01ms;
  }
}


/* ── css/motion.css ── */
/* ══════════════════════════════════════════════════════════════
   SILICONE UI — MOTION
   motion.css · v1.0.0

   Physical consequences of interaction.
   Not decoration. Not animation choices.
   What materials do when you touch them.

   Every motion in this file has a physical explanation:

   Wiggle    — a raised surface absorbs tap energy, oscillates
   Melt      — material phase change between panel states
   Flare     — a button presses into the surface and springs back
   Ripple    — tap energy propagates outward from contact point
   Gloss     — NW light catches the surface at the touch point
   Mat drag  — cursor presses into and pulls the surface

   Spring curve: cubic-bezier(.34, 1.05, .64, 1)
   Slight overshoot. Fast settle. Physical.

   Ease-out curve: cubic-bezier(.4, 0, 1, 1)
   Used for exits — things leaving don't spring back.

   Ease-in curve: cubic-bezier(0, 0, .2, 1)
   Used for entries — things arriving decelerate to rest.

   Reduced motion — each stylesheet that defines motion carries
   its own @media (prefers-reduced-motion: reduce) block:
   — movement (transform, size, spacing, type size) lands at once
   — loops (LED pulse, breathing hints, neon, shimmer) rest
   — one-shot effects (wiggle, flare, ripple, gloss, toast) end
     at once: 0.01ms, not none, so animationend still fires and
     the scripts waiting on it still clean up
   — state changes (colour, glow, depth shadow, opacity) keep a
     quick 80ms fade, so hover, press and status still show
   — views dissolve instead of melting (see Melt)

   Depends on: tokens.css
   Lax Pairs Limited · silicone.studio · MIT Licence
   ══════════════════════════════════════════════════════════════ */


/* ── Wiggle ──────────────────────────────────────────────────
   The most important motion in the system.
   Four-step micro-rotation with matching scale compression.
   Duration: 0.38s — fast enough to feel physical, slow enough
   to read as a complete oscillation.

   The rotation sequence:
   start → -.4° (absorbs energy, slight compression)
           → +.3° (energy transfers, slight expansion)
           → -.2° (dampening)
           → +.1° (settling)
           → rest

   Two keyframe sets:
   1. wiggle      — for surfaces and general elements
   2. card-wiggle — for cards (smaller rotation, preserves physics)

   interactions.js triggers addWiggle() on click.
   Surface click detection triggers wiggle before compartment open. */

@keyframes wiggle {
  0%,  100% { transform: rotate(0deg)  scale(1); }
  20%       { transform: rotate(-.4deg) scale(.995); }
  40%       { transform: rotate(.3deg)  scale(.998); }
  60%       { transform: rotate(-.2deg) scale(.999); }
  80%       { transform: rotate(.1deg)  scale(1); }
}

.wiggling {
  animation: wiggle .38s ease-in-out;
}

@keyframes card-wiggle {
  0%,  100% { transform: scale(1)    rotate(0); }
  25%       { transform: scale(.99)  rotate(-.15deg); }
  75%       { transform: scale(.995) rotate(.1deg); }
}

/* Applied by interactions.js to .card and .card-dark on click  */
/* Also defined in depth.css for co-location with card rules    */
/* This file is the canonical @keyframes definition             */


/* ── Melt — view transitions ─────────────────────────────────
   Material phase change between views (pages, panels, steps).
   Out: .melting-out — the view dissolves downward: scale
        reduces, slight drop, blur increases. --melt-out-dur.
   In:  .melting-in — the next view emerges from below, scales
        up, sharpens. --melt-in-dur.
   Remove each class on animationend.

   The showcase times its panels by arc distance: views further
   apart on the navigation arc take longer (core.js sets
   --melt-out-dur / --melt-in-dur per transition).

   .melt-anchor: applied to the element that triggered the
   navigation. It stays static while the views transition —
   creating a visual anchor point.

   Under reduced motion the melt keeps its timing but only
   dissolves (melt-fade-out / melt-fade-in): no scale, drift or
   blur. animationend still fires, and a script that swaps views
   on a timer keeps its pacing rather than showing a blank view
   for the whole wait, as an instant melt would.               */

@keyframes melt-out {
  0% {
    opacity:   1;
    transform: scale(1)    translateY(0);
    filter:    blur(0);
  }
  100% {
    opacity:   0;
    transform: scale(.97)  translateY(8px);
    filter:    blur(2px);
  }
}

@keyframes melt-in {
  0% {
    opacity:   0;
    transform: scale(.97)  translateY(-8px);
    filter:    blur(2px);
  }
  100% {
    opacity:   1;
    transform: scale(1)    translateY(0);
    filter:    blur(0);
  }
}

.melting-out {
  animation: melt-out var(--melt-out-dur, 220ms) cubic-bezier(.4, 0, 1, 1) forwards;
}

.melting-in {
  animation: melt-in var(--melt-in-dur, 320ms) cubic-bezier(0, 0, .2, 1) forwards;
}

/* The melt without the movement — used under reduced motion   */
@keyframes melt-fade-out {
  to   { opacity: 0; }
}

@keyframes melt-fade-in {
  from { opacity: 0; }
}

/* The element that triggered navigation — stays visible during transition */
.melt-anchor {
  animation: none !important;
  opacity:   1    !important;
}


/* ── Btn flare ───────────────────────────────────────────────
   Button presses into the surface and springs back.
   Shadow inverts: extrude → debossed → extrude.
   Duration: 0.32s — slightly faster than wiggle.
   The button physically clicks.

   interactions.js triggers addFlare() on button click.        */

@keyframes btn-flare {
  0% {
    box-shadow:
      var(--rim-raised),
      calc(var(--ox) *  .55) calc(var(--oy) *  .55) 10px var(--shadow-light),
      calc(var(--ox) * -.55) calc(var(--oy) * -.55) 10px var(--shadow-dark);
  }
  40% {
    /* Inverted — pressed into surface; the rim flips with it */
    box-shadow:
      var(--rim-recess),
      inset calc(var(--ox) * -.45) calc(var(--oy) * -.45) 8px var(--shadow-dark),
      inset calc(var(--ox) *  .45) calc(var(--oy) *  .45) 8px var(--shadow-light);
  }
  100% {
    box-shadow:
      var(--rim-raised),
      calc(var(--ox) *  .55) calc(var(--oy) *  .55) 10px var(--shadow-light),
      calc(var(--ox) * -.55) calc(var(--oy) * -.55) 10px var(--shadow-dark);
  }
}

.btn-flaring {
  animation: btn-flare .32s ease;
}


/* ── Tap ripple ──────────────────────────────────────────────
   Tap energy propagates outward from the exact contact point.
   Spawned at click coordinates by cursor.js / interactions.js.
   Scales from 0 to 4× over 0.55s then removes itself.

   Two colours:
   Light surface: rgba(1, 50, 36, 0.07)   — dark forest, subtle
   Dark surface:  rgba(26, 251, 186, 0.22) — mint, visible

   Self-removes via animationend listener in interactions.js.   */

.tap-ripple {
  position:        fixed;
  border-radius:   50%;
  pointer-events:  none;
  z-index:         9997;
  transform:       translate(-50%, -50%) scale(0);
  animation:       ripple-out .55s cubic-bezier(0, .5, .5, 1) forwards;
}

@keyframes ripple-out {
  to {
    transform: translate(-50%, -50%) scale(4);
    opacity:   0;
  }
}


/* ── Gloss appear / fade ─────────────────────────────────────
   Double-tap gloss radial gradient.
   Appears at the touch point via activateGloss() in interactions.js.
   Defined here as keyframes — applied in depth.css via
   .card.gloss-active::after.

   gloss-fade is used when deactivateGloss() closes the effect. */

@keyframes gloss-appear {
  from { opacity: 0; }
  to   { opacity: 1; }
}

@keyframes gloss-fade {
  from { opacity: 1; }
  to   { opacity: 0; }
}


/* ── Reduced motion ──────────────────────────────────────────
   Wiggle, flare and ripple end at once. 0.01ms rather than none
   keeps animationend firing: interactions.js and compartment.js
   clear .wiggling and .btn-flaring on it, and each ripple
   removes itself on it. The melt dissolves in place at its
   usual timing.                                               */

@media (prefers-reduced-motion: reduce) {
  .wiggling,
  .btn-flaring,
  .tap-ripple {
    animation-duration: 0.01ms;
  }

  .melting-out { animation-name: melt-fade-out; }
  .melting-in  { animation-name: melt-fade-in; }
}




/* ── css/buttons.css ── */
/* ══════════════════════════════════════════════════════════════
   SILICONE UI — BUTTONS
   buttons.css · v1.0.0

   The primary interactive element. Bump z+0.5 at rest.
   Presses into the surface on click (flare animation).
   White LED contact glow when cursor approaches.
   Proximity glow fires before contact.

   Variants:
   .btn           — default, surface colour, forest green text
   .btn-primary   — stronger shadow, deeper gloss, bolder weight
   .btn-sm        — reduced padding and font size

   Physics checklist:
   ✓ Raised at rest (bump shadow)
   ✓ NW gloss via ::before
   ✓ Presses in on click (btn-flare keyframe in motion.css)
   ✓ White LED on cursor contact (cursor-on-link)
   ✓ Proximity glow fires before contact (--prox-glow)
   ✓ Silicone cursor takes over where present (cursor.css)

   Depends on: tokens.css, motion.css
   Lax Pairs Limited · silicone.studio · MIT Licence
   ══════════════════════════════════════════════════════════════ */


/* ── Base button ─────────────────────────────────────────────
   Pill shape (border-radius: 100px).
   Shadow scale 0.55 — slightly proud, secondary interactive.
   Forest green text on surface background.
   --prox-glow starts at 0, set by proximity.js on mousemove.  */

.btn {
  display:         inline-flex;
  align-items:     center;
  min-height:      1.5rem;   /* 24px target floor (WCAG 2.2, 2.5.8) */
  gap:             6px;
  border:          none;
  border-radius:   100px;
  font-family:     'Inter', sans-serif;
  font-weight:     600;
  /* 12px text at Short; text and padding are in rem, so the
     reading-size curve and the browser's text size scale both.    */
  font-size:       0.75rem;
  letter-spacing:  0.02em;
  padding:         var(--pad-control);
  background:      var(--surface);
  color:           var(--accent);
  position:        relative;
  overflow:        hidden;

  /* Bump shadow — slightly proud */
  box-shadow:
    var(--rim-raised),
    calc(var(--ox) *  .55) calc(var(--oy) *  .55) 10px var(--shadow-light),
    calc(var(--ox) * -.55) calc(var(--oy) * -.55) 10px var(--shadow-dark);

  transition:
    box-shadow .18s ease,
    color      .18s ease;

  /* Proximity glow — proximity.js sets --prox-glow (0→1) */
  --prox-glow: 0;
  text-shadow: 0 0 calc(8px * var(--prox-glow)) rgba(255,255,255, calc(0.6 * var(--prox-glow)));
}

/* NW gloss — 135° light catches the pill face */
.btn::before {
  content:        '';
  position:       absolute;
  inset:          0;
  border-radius:  100px;
  background:     linear-gradient(135deg, rgba(255,255,255,.14) 0%, transparent 55%);
  pointer-events: none;
}


/* ── Primary button ──────────────────────────────────────────
   Stronger shadow, stronger gloss, bolder weight.
   Same colour — the prominence comes from depth, not colour.
   Shadow inverted direction for maximum lift effect.          */

.btn-primary {
  background:  var(--surface);
  color:       var(--accent);
  font-weight: 700;

  /* Stronger lift — note inverted multiplier direction */
  box-shadow:
    var(--rim-raised),
    calc(var(--ox) * -.75) calc(var(--oy) * -.75) 14px var(--shadow-light),
    calc(var(--ox) *  .75) calc(var(--oy) *  .75) 14px var(--shadow-dark);
}

/* Stronger gloss — more prominent element catches more light  */
.btn-primary::before {
  background: linear-gradient(135deg, rgba(255,255,255,.18) 0%, transparent 55%);
}


/* ── Small button ────────────────────────────────────────────
   Reduced padding and font for tight layouts.
   Same physics — just smaller material.                       */

.btn-sm {
  padding:   var(--pad-control-sm);
  font-size: 0.6875rem;  /* 11px at Short */
}


/* ── Link illumination — white LED contact ───────────────────
   When the Silicone cursor approaches interactive elements,
   it emits white light upward. Elements respond with white
   LED glow — completing the circuit at the surface.

   cursor.js adds .cursor-on-link to <html> on hover over
   interactive elements.

   Button text turns white — the element is illuminated.
   Shadow intensifies slightly — the element rises to meet.    */

.cursor-on-link .btn:hover {
  color: rgba(255,255,255, 0.92);
  box-shadow:
    var(--rim-raised),
    calc(var(--ox) * -.7) calc(var(--oy) * -.7) 13px var(--shadow-light),
    calc(var(--ox) *  .7) calc(var(--oy) *  .7) 13px var(--shadow-dark),
    0 0 10px rgba(255,255,255, 0.18),
    0 0 20px rgba(255,255,255, 0.08);
  text-shadow: 0 0 8px rgba(255,255,255, 0.5);
}

/* Primary gets stronger illumination — more prominent element */
.cursor-on-link .btn-primary:hover {
  color: rgba(255,255,255, 0.95);
  box-shadow:
    var(--rim-raised),
    calc(var(--ox) * -.85) calc(var(--oy) * -.85) 16px var(--shadow-light),
    calc(var(--ox) *  .85) calc(var(--oy) *  .85) 16px var(--shadow-dark),
    0 0 14px rgba(255,255,255, 0.22),
    0 0 28px rgba(255,255,255, 0.10);
  text-shadow: 0 0 10px rgba(255,255,255, 0.6);
}


/* ── Reduced motion ──────────────────────────────────────────
   Contact glow and press still show, as a quick fade. The flare
   itself is handled in motion.css; proximity.css restates the
   button's transition and carries its own block.              */

@media (prefers-reduced-motion: reduce) {
  .btn {
    transition-duration: 80ms;
  }
}


/* ── css/forms.css ── */
/* ══════════════════════════════════════════════════════════════
   SILICONE UI — FORMS
   forms.css · v1.0.0

   Input fields are debossed z-1 — cavities in the surface.
   The depth communicates "put something here" without a border.
   No border needed. The physics is the affordance.

   The inset shadow direction is inverted vs raised elements:
   NW = dark (shadow falls into the recess from the light side)
   SE = light (light bounces off the far cavity wall)

   Every recess in the system does this — it doesn't break the
   physics, it expresses it. A cavity catches shadow differently
   than a bump.

   Social links use the same row pattern as cite-rows —
   defined here as they're form-adjacent (contact / connect).

   Depends on: tokens.css
   Lax Pairs Limited · silicone.studio · MIT Licence
   ══════════════════════════════════════════════════════════════ */


/* ── Field group ─────────────────────────────────────────────
   Wrapper for label + input pairs.
   Bottom margin creates breathing room between fields.        */

.field-group {
  margin-bottom: var(--wall);
}


/* ── Field label ─────────────────────────────────────────────
   Uppercase tracking label above the input.
   Same style as .label but as a <label> element.
   Muted colour — the label recedes, the input is primary.     */

.field-label {
  display:        block;
  font-weight:    600;
  font-size:      0.625rem;
  letter-spacing: 0.05em;
  text-transform: uppercase;
  color:          var(--text-muted);
  margin-bottom:  6px;
}


/* ── Input fields — debossed z-1 ─────────────────────────────
   Inset shadow — the field is pressed into the surface.
   Shadow scale 0.45 — moderate cavity depth.
   No border. No outline. The depth is the affordance.

   NW = dark (light can't reach the recessed NW corner)
   SE = light (light bounces off the SE cavity wall)           */

.field-input,
.field-textarea,
.field-select {
  width:       100%;
  border:      none;
  outline:     none;
  resize:      none;
  font-family: 'Inter', sans-serif;
  font-size:   var(--fs-body);
  color:       var(--text-primary);
  background:  var(--surface);
  border-radius: 10px;
  /* The walls, plus 2px per reading step either side of Grande   */
  padding:
    calc(var(--wall-sm) + (var(--r) - 1) * 2px)
    calc(var(--wall)    + (var(--r) - 1) * 2px);

  /* Debossed — inset shadow, NW dark, SE light */
  box-shadow:
    var(--rim-recess),
    inset calc(var(--ox) * -.45) calc(var(--oy) * -.45) 8px var(--shadow-dark),
    inset calc(var(--ox) *  .45) calc(var(--oy) *  .45) 8px var(--shadow-light);

  cursor: text;
}

/* Textarea needs minimum height and readable line spacing     */
.field-textarea {
  min-height:  100px;
  line-height: 1.65;
}


/* ── Social link rows ────────────────────────────────────────
   Flex rows for contact / social link lists.
   Same separator physics as cite-rows and value-rows:
   two-pixel debossed rule between rows (dark above, light below).
   No separator on last child.                                 */

.social-link {
  display:         flex;
  align-items:     center;
  gap:             12px;
  padding:         var(--wall-sm) var(--wall);
  text-decoration: none;
  color:           var(--text-primary);

  /* Two-pixel debossed separator */
  box-shadow:
    0 1px 0 var(--shadow-dark),
    0 2px 0 var(--shadow-light);
}

.social-link:last-child {
  box-shadow: none;
}


/* ── css/form-controls.css ── */
/* ══════════════════════════════════════════════════════════════
   SILICONE UI — FORM CONTROLS
   form-controls.css · v1.0.0

   Extended form controls beyond the basic field inputs in forms.css.
   Selects, checkboxes, radio buttons, toggles, range sliders.

   The same physics rule applies everywhere:
   — Selects are debossed z-1 (cavities — put something here)
   — Custom checkboxes and radios toggle between debossed (off)
     and extruded (on) — depth encodes state
   — Toggles are the clearest expression of this: the track is
     a cavity, the thumb extrudes from it when active
   — Range sliders: track = cavity, thumb = extrude

   Raised = active/on/selected. Debossed = inactive/off/empty.
   The physics is consistent with the rest of the system.

   No native select arrow is used — the system provides its own.
   No border. No colour-only state indicators. Depth is the signal.

   Depends on: tokens.css, forms.css
   Lax Pairs Limited · silicone.studio · MIT Licence
   ══════════════════════════════════════════════════════════════ */


/* ── Select wrapper ──────────────────────────────────────────
   Positions the custom chevron over the native select.
   The wrapper is the visual container — the select is invisible. */

.field-select-wrap {
  position: relative;
  width:     100%;
}

/* Custom chevron — debossed colour, SE corner                 */
.field-select-wrap::after {
  content:        '';
  position:       absolute;
  right:          var(--wall);
  top:            50%;
  transform:      translateY(-50%) rotate(45deg);
  width:          6px;
  height:         6px;
  border-right:   1.5px solid var(--text-muted);
  border-bottom:  1.5px solid var(--text-muted);
  pointer-events: none;
  transition:     transform .18s ease;
}

/* Open state — chevron rotates to point up                    */
.field-select-wrap:focus-within::after {
  transform: translateY(-50%) rotate(-135deg);
}

/* Native select — appearance removed, physics via forms.css   */
.field-select {
  appearance:         none;
  -webkit-appearance: none;
  padding-right:      calc(var(--wall) + 18px);
}


/* ── Checkbox ────────────────────────────────────────────────
   Debossed when unchecked — cavity signals "nothing here yet".
   Extruded when checked — the answer rises to the surface.
   Tick mark in mint — consistent with LED/active token colour. */

.field-check-wrap {
  display:     flex;
  align-items: center;
  gap:         var(--wall-sm);
  user-select: none;
}

.field-check-wrap input[type="checkbox"] {
  position: absolute;
  opacity:  0;
  width:    0;
  height:   0;
}

/* Visual box — cavity at rest                                 */
.field-check-box {
  width:         20px;
  height:        20px;
  border-radius: 6px;
  background:    var(--surface);
  flex-shrink:   0;
  position:      relative;
  transition:
    box-shadow .18s cubic-bezier(.34, 1.05, .64, 1),
    transform  .18s cubic-bezier(.34, 1.05, .64, 1);

  box-shadow:
    var(--rim-recess),
    inset calc(var(--ox) * -.45) calc(var(--oy) * -.45) 8px var(--shadow-dark),
    inset calc(var(--ox) *  .45) calc(var(--oy) *  .45) 8px var(--shadow-light);
}

/* Tick — hidden at rest, lit on check (--led-mark: mint on dark,
   the accent on light, where mint can't reach 3:1)             */
.field-check-box::after {
  content:       '';
  position:      absolute;
  top:           4px;
  left:          6px;
  width:         5px;
  height:        9px;
  border-right:  2px solid var(--led-mark);
  border-bottom: 2px solid var(--led-mark);
  transform:     rotate(45deg);
  opacity:       0;
  transition:    opacity .18s ease;
}

/* Checked — extrudes from surface, tick appears               */
.field-check-wrap input[type="checkbox"]:checked ~ .field-check-box {
  transform: translateY(-1px);
  box-shadow:
    var(--rim-raised),
    calc(var(--ox) *  .55) calc(var(--oy) *  .55) 10px var(--shadow-light),
    calc(var(--ox) * -.55) calc(var(--oy) * -.55) 10px var(--shadow-dark);
}

.field-check-wrap input[type="checkbox"]:checked ~ .field-check-box::after {
  opacity: 1;
}

/* Label — muted at rest, primary when checked                 */
.field-check-label {
  font-size:   var(--fs-body);
  color:       var(--text-secondary);
  line-height: 1.4;
  transition:  color .18s ease;
}

.field-check-wrap input[type="checkbox"]:checked ~ .field-check-label {
  color: var(--text-primary);
}


/* ── Radio button ────────────────────────────────────────────
   Same depth encoding as checkbox — cavity to extrude.
   Round geometry. Mint dot instead of tick.                   */

.field-radio-wrap {
  display:     flex;
  align-items: center;
  gap:         var(--wall-sm);
  user-select: none;
}

.field-radio-wrap input[type="radio"] {
  position: absolute;
  opacity:  0;
  width:    0;
  height:   0;
}

.field-radio-box {
  width:         20px;
  height:        20px;
  border-radius: 50%;
  background:    var(--surface);
  flex-shrink:   0;
  position:      relative;
  transition:
    box-shadow .18s cubic-bezier(.34, 1.05, .64, 1),
    transform  .18s cubic-bezier(.34, 1.05, .64, 1);

  box-shadow:
    var(--rim-recess),
    inset calc(var(--ox) * -.45) calc(var(--oy) * -.45) 8px var(--shadow-dark),
    inset calc(var(--ox) *  .45) calc(var(--oy) *  .45) 8px var(--shadow-light);
}

/* Dot — hidden at rest, springs out on select                 */
.field-radio-box::after {
  content:       '';
  position:      absolute;
  top:           50%;
  left:          50%;
  transform:     translate(-50%, -50%) scale(0);
  width:         8px;
  height:        8px;
  border-radius: 50%;
  background:    var(--led-mark);
  box-shadow:    0 0 6px rgba(26, 251, 186, .7);
  transition:    transform .18s cubic-bezier(.34, 1.05, .64, 1);
}

.field-radio-wrap input[type="radio"]:checked ~ .field-radio-box {
  transform: translateY(-1px);
  box-shadow:
    var(--rim-raised),
    calc(var(--ox) *  .55) calc(var(--oy) *  .55) 10px var(--shadow-light),
    calc(var(--ox) * -.55) calc(var(--oy) * -.55) 10px var(--shadow-dark);
}

.field-radio-wrap input[type="radio"]:checked ~ .field-radio-box::after {
  transform: translate(-50%, -50%) scale(1);
}

.field-radio-label {
  font-size:  var(--fs-body);
  color:      var(--text-secondary);
  transition: color .18s ease;
}

.field-radio-wrap input[type="radio"]:checked ~ .field-radio-label {
  color: var(--text-primary);
}

/* Radio group — vertically stacked                            */
.field-radio-group {
  display:        flex;
  flex-direction: column;
  gap:            var(--wall-sm);
}


/* ── Toggle ──────────────────────────────────────────────────
   The purest expression of depth encoding in the system.
   Track = cavity (z-1). Thumb extrudes from it when on (z+1).
   Off: thumb at left, no glow.
   On:  thumb at right, mint LED fires.                        */

.field-toggle-wrap {
  display:     flex;
  align-items: center;
  gap:         var(--wall-sm);
  user-select: none;
}

.field-toggle-wrap input[type="checkbox"] {
  position: absolute;
  opacity:  0;
  width:    0;
  height:   0;
}

/* Track — cavity channel pressed into surface                 */
.field-toggle-track {
  width:         40px;
  height:        22px;
  border-radius: 100px;
  background:    var(--surface);
  flex-shrink:   0;
  position:      relative;

  box-shadow:
    var(--rim-recess),
    inset calc(var(--ox) * -.5) calc(var(--oy) * -.5) 9px var(--shadow-dark),
    inset calc(var(--ox) *  .5) calc(var(--oy) *  .5) 9px var(--shadow-light);
}

/* Thumb — extrudes from cavity floor, slides on activation    */
.field-toggle-thumb {
  position:      absolute;
  top:           3px;
  left:          3px;
  width:         16px;
  height:        16px;
  border-radius: 50%;
  background:    var(--surface);
  transition:
    transform  .22s cubic-bezier(.34, 1.05, .64, 1),
    box-shadow .22s ease;

  box-shadow:
    var(--rim-raised),
    calc(var(--ox) *  .4) calc(var(--oy) *  .4) 7px var(--shadow-light),
    calc(var(--ox) * -.4) calc(var(--oy) * -.4) 7px var(--shadow-dark);
}

/* On — thumb slides right, mint glow fires                    */
.field-toggle-wrap input[type="checkbox"]:checked ~ .field-toggle-track .field-toggle-thumb {
  transform: translateX(18px);
  box-shadow:
    var(--rim-raised),
    calc(var(--ox) *  .4) calc(var(--oy) *  .4) 7px var(--shadow-light),
    calc(var(--ox) * -.4) calc(var(--oy) * -.4) 7px var(--shadow-dark),
    0 0  6px rgba(26, 251, 186, .7),
    0 0 12px rgba(26, 251, 186, .3);
}

.field-toggle-label {
  font-size:  var(--fs-body);
  color:      var(--text-secondary);
  transition: color .18s ease;
}

.field-toggle-wrap input[type="checkbox"]:checked ~ .field-toggle-label {
  color: var(--text-primary);
}


/* ── Range slider ────────────────────────────────────────────
   Track = cavity. Thumb = extrude.
   Thumb presses into surface when actively dragged.           */

.field-range {
  -webkit-appearance: none;
  appearance:         none;
  width:              100%;
  height:             6px;
  border-radius:      100px;
  background:         var(--surface);
  outline:            none;

  box-shadow:
    var(--rim-recess),
    inset calc(var(--ox) * -.4) calc(var(--oy) * -.4) 6px var(--shadow-dark),
    inset calc(var(--ox) *  .4) calc(var(--oy) *  .4) 6px var(--shadow-light);
}

.field-range::-webkit-slider-thumb {
  -webkit-appearance: none;
  width:              20px;
  height:             20px;
  border-radius:      50%;
  background:         var(--surface);
  transition:
    box-shadow .18s cubic-bezier(.34, 1.05, .64, 1),
    transform  .18s cubic-bezier(.34, 1.05, .64, 1);

  box-shadow:
    var(--rim-raised),
    calc(var(--ox) *  .55) calc(var(--oy) *  .55) 10px var(--shadow-light),
    calc(var(--ox) * -.55) calc(var(--oy) * -.55) 10px var(--shadow-dark);
}

.field-range::-moz-range-thumb {
  width:         20px;
  height:        20px;
  border-radius: 50%;
  border:        none;
  background:    var(--surface);

  box-shadow:
    var(--rim-raised),
    calc(var(--ox) *  .55) calc(var(--oy) *  .55) 10px var(--shadow-light),
    calc(var(--ox) * -.55) calc(var(--oy) * -.55) 10px var(--shadow-dark);
}

/* Active drag — thumb presses into surface                    */
.field-range:active::-webkit-slider-thumb {
  transform: scale(.93);
  box-shadow:
    var(--rim-recess),
    inset calc(var(--ox) * -.35) calc(var(--oy) * -.35) 6px var(--shadow-dark),
    inset calc(var(--ox) *  .35) calc(var(--oy) *  .35) 6px var(--shadow-light);
}

.field-range:active::-moz-range-thumb {
  transform: scale(.93);
  box-shadow:
    var(--rim-recess),
    inset calc(var(--ox) * -.35) calc(var(--oy) * -.35) 6px var(--shadow-dark),
    inset calc(var(--ox) *  .35) calc(var(--oy) *  .35) 6px var(--shadow-light);
}


/* ── Form row ────────────────────────────────────────────────
   Horizontal layout for inline control + label pairs.
   Standard two-pixel debossed separator between rows.         */

.form-row {
  display:         flex;
  align-items:     center;
  justify-content: space-between;
  gap:             var(--wall);
  padding:         var(--wall-sm) 0;
  box-shadow:
    0 1px 0 var(--shadow-dark),
    0 2px 0 var(--shadow-light);
}

.form-row:last-child { box-shadow: none; }

.form-row-label {
  font-size:   var(--fs-body);
  font-weight: 600;
  color:       var(--text-secondary);
  line-height: 1.4;
}

.form-row-sub {
  font-size:   calc(var(--fs-body) - 0.0625rem);
  color:       var(--text-muted);
  margin-top:  2px;
  line-height: 1.4;
}


/* ── Reduced motion ──────────────────────────────────────────
   State still reads from depth, colour and the tick or dot,
   each with a quick fade. What moved now lands at once: the
   toggle thumb, the radio dot, the checked box's lift and the
   select chevron.                                             */

@media (prefers-reduced-motion: reduce) {
  .field-select-wrap::after,
  .field-radio-box::after {
    transition: none;
  }

  .field-check-box,
  .field-radio-box,
  .field-toggle-thumb {
    transition: box-shadow 80ms ease;
  }

  .field-check-box::after,
  .field-check-label,
  .field-radio-label,
  .field-toggle-label {
    transition-duration: 80ms;
  }

  /* Own rule: a browser that doesn't know this pseudo-element
     drops the whole selector list it sits in                  */
  .field-range::-webkit-slider-thumb {
    transition: box-shadow 80ms ease;
  }
}


/* ── css/dial.css ── */
/* ══════════════════════════════════════════════════════════════
   SILICONE UI — DIAL · dial.css
   A rotary dial over a native range input (dial.js builds it from
   input[type="range"][data-dial]). A raised knob — rim, NW gloss,
   a pointer mark — seated in a recessed ring of LEDs, lit from the
   start of the sweep up to the value:

   span.dial[data-interactive]         the ring: recessed, rimmed
     svg.dial-ring                     its LEDs
       path.dial-track                 every segment, unlit
       path.dial-led                   the segments up to the value
     span.dial-knob > span.dial-mark   the knob and its pointer
     input.dial-input                  the control, drawn by the rest

   The LEDs light in --led-mark (the accent on light, where mint
   can't be read; mint on dark) and glow mint with --led. While
   what the dial drives waits (data-lit="false" on the input) they
   dim and stop glowing. The knob presses into the trench while it
   turns, as a slider's thumb does.

   --dial-size   the dial's width (default 2.5rem: 40px at Short,
                 46 at Grande, 57 at Trenta; never under 1.5rem)
   --dial-value  0–1, set by dial.js; the mark turns with it

   Disabled, focus and forced colours: states.css.
   Depends on: tokens.css (roles, --ox/--oy, --led)
   ══════════════════════════════════════════════════════════════ */

.dial {
  --dial-value:    0;
  position:        relative;
  display:         inline-block;
  flex:            none;
  vertical-align:  middle;
  width:           max(1.5rem, var(--dial-size, 2.5rem));
  height:          max(1.5rem, var(--dial-size, 2.5rem));
  border-radius:   50%;
  cursor:          grab;
  touch-action:    none;
  user-select:     none;
  -webkit-user-select: none;

  box-shadow:
    var(--rim-recess),
    inset calc(var(--ox) * -.4) calc(var(--oy) * -.4) 6px var(--shadow-dark),
    inset calc(var(--ox) *  .4) calc(var(--oy) *  .4) 6px var(--shadow-light);
}

.dial.turning { cursor: grabbing; }


/* ── The ring of LEDs ────────────────────────────────────────
   Drawn in a 40-unit box that scales with the dial. The lit arc
   is the track cut short, its pathLength scaled so its segments
   fall on the track's (dial.js).                              */

.dial-ring {
  position:       absolute;
  inset:          0;
  width:          100%;
  height:         100%;
  overflow:       visible;
  pointer-events: none;
}

.dial-track,
.dial-led {
  fill:         none;
  stroke-width: 2.6;
}

.dial-track { stroke: var(--led-inactive); }

.dial-led {
  stroke:     var(--led-mark);
  filter:     drop-shadow(0 0 calc(2.5px * var(--led)) color-mix(in srgb, var(--led-active) calc(var(--led) * 80%), transparent));
  transition: stroke .38s ease, filter .38s ease;
}

/* Waiting: what the dial drives isn't running                  */
.dial:has(> .dial-input[data-lit="false"]) .dial-led {
  stroke: color-mix(in oklab, var(--led-mark) 45%, var(--led-inactive));
  filter: none;
}


/* ── The knob ────────────────────────────────────────────────
   Raised and rimmed, with the NW gloss; it presses in while it
   turns. The gloss stays put; only the mark turns.            */

.dial-knob {
  position:      absolute;
  inset:         15%;
  border-radius: 50%;
  background:    var(--surface);
  overflow:      hidden;
  transition:    box-shadow .18s cubic-bezier(.34, 1.05, .64, 1);

  box-shadow:
    var(--rim-raised),
    calc(var(--ox) *  .45) calc(var(--oy) *  .45) 7px var(--shadow-light),
    calc(var(--ox) * -.45) calc(var(--oy) * -.45) 7px var(--shadow-dark);
}

.dial-knob::before {
  content:        '';
  position:       absolute;
  inset:          0;
  border-radius:  50%;
  background:     linear-gradient(135deg, rgba(255,255,255,.16) 0%, transparent 60%);
  pointer-events: none;
}

.dial.turning .dial-knob {
  box-shadow:
    var(--rim-recess),
    inset calc(var(--ox) * -.35) calc(var(--oy) * -.35) 6px var(--shadow-dark),
    inset calc(var(--ox) *  .35) calc(var(--oy) *  .35) 6px var(--shadow-light);
}

.dial.turning .dial-knob::before { opacity: 0; }

.dial-mark {
  position:  absolute;
  inset:     0;
  transform: rotate(calc(-135deg + 270deg * var(--dial-value)));
}

.dial-mark::after {
  content:       '';
  position:      absolute;
  left:          50%;
  top:           10%;
  width:         max(2px, 7%);
  height:        30%;
  border-radius: 1px;
  background:    var(--text-primary);
  transform:     translateX(-50%);
  transition:    background-color .38s ease;
}

.dial:has(> .dial-input[data-lit="false"]) .dial-mark::after {
  background: var(--text-muted);
}


/* ── The input ───────────────────────────────────────────────
   Still the control — label, keyboard, form value, listeners —
   but drawn by the dial: unseen, and the dial takes the pointer. */

.dial-input {
  position:       absolute;
  inset:          0;
  width:          100%;
  height:         100%;
  margin:         0;
  opacity:        0;
  pointer-events: none;
}


/* ── Reduced motion ──────────────────────────────────────────
   The press and the LEDs keep a quick fade; the mark already
   lands at once.                                              */

@media (prefers-reduced-motion: reduce) {
  .dial-knob { transition: box-shadow 80ms ease; }
  .dial-led  { transition-duration: 80ms; }
  .dial-mark::after { transition-duration: 80ms; }
}


/* ── css/help.css ── */
/* ══════════════════════════════════════════════════════════════
   SILICONE UI — HELP · help.css
   The help system's three faces (help.js): the readout card, a
   glossary term, and a term's definition pinned inline.

   The card reports, it isn't operated: a raised surface (extrude),
   rimmed only at enhanced contrast, beside what it explains. A
   glossary term is a control: a bump with its rim and a "?" mark,
   pressed into the trench while its note is open. The note is put
   in the sheet: a recessed well just after the term (D7).
   ══════════════════════════════════════════════════════════════ */

.help-card {
  position:       fixed;
  z-index:        60;
  width:          18rem;
  max-width:      calc(100vw - 24px);
  box-sizing:     border-box;
  padding:        10px 12px;
  border-radius:  12px;
  background:     var(--surface);
  color:          var(--text-primary);
  font-size:      0.78125rem;
  line-height:    1.45;
  pointer-events: none;
  box-shadow:
    var(--rim-raised-enhanced, 0 0 transparent),
    calc(var(--ox) *  .75) calc(var(--oy) *  .75) 14px var(--shadow-light),
    calc(var(--ox) * -.75) calc(var(--oy) * -.75) 14px var(--shadow-dark);
}
.help-card[hidden] { display: none; }
.help-card .help-h   { display: flex; align-items: baseline; justify-content: space-between; gap: 8px; margin-bottom: 6px; }
.help-card .help-t   { font-weight: 600; font-size: 0.8125rem; }
.help-card .help-tag { font: 0.65625rem ui-monospace, Consolas, monospace; color: var(--text-muted); white-space: nowrap; }
.help-card p         { margin: 0 0 4px; color: color-mix(in oklab, var(--text-primary) 80%, var(--text-muted)); }
.help-card p b       { margin-right: 4px; color: var(--text-primary); font-weight: 600; }
.help-card .help-sw  { display: inline-block; width: 12px; height: 12px; margin-right: 6px; border-radius: 3px; vertical-align: -1px;
                       box-shadow: inset 0 0 0 1px color-mix(in oklab, var(--text-muted) 45%, transparent); }
.help-card .help-foot { margin-top: 6px; font-size: 0.71875rem; color: var(--text-muted); }

/* ── A glossary term: a bump with a "?" mark ──────────────── */
.help-term {
  display:       inline-flex;
  align-items:   center;
  gap:           5px;
  min-height:    1.5rem;
  padding:       2px 6px 2px 9px;
  border:        0;
  border-radius: 100px;
  background:    var(--surface);
  color:         var(--accent);
  font:          700 0.65625rem system-ui, sans-serif;
  letter-spacing: .03em;
  cursor:        pointer;
  box-shadow:
    var(--rim-raised, 0 0 transparent),
    calc(var(--ox) *  .35) calc(var(--oy) *  .35) 6px var(--shadow-light),
    calc(var(--ox) * -.35) calc(var(--oy) * -.35) 6px var(--shadow-dark);
}
.help-term::after {
  content: "?"; display: grid; place-items: center; width: 13px; height: 13px; border-radius: 50%;
  background: var(--accent); color: var(--surface); font-size: 9px; font-weight: 800;
}
.help-term[aria-expanded="true"] {
  box-shadow:
    var(--rim-recess, 0 0 transparent),
    inset calc(var(--ox) * -.4) calc(var(--oy) * -.4) 6px var(--shadow-dark),
    inset calc(var(--ox) *  .4) calc(var(--oy) *  .4) 6px var(--shadow-light);
}
.help-term:focus-visible { outline: 2px solid var(--focus-ring, var(--accent)); outline-offset: 3px; }

/* ── A term's definition, pinned in the sheet ─────────────── */
.help-note {
  display:       block;
  margin:        6px 0;
  padding:       8px 10px;
  border-radius: 10px;
  color:         var(--text-muted);
  font-size:     0.71875rem;
  line-height:   1.45;
  box-shadow:
    var(--rim-recess-enhanced, 0 0 transparent),
    inset calc(var(--ox) * -.5) calc(var(--oy) * -.5) 8px var(--shadow-dark),
    inset calc(var(--ox) *  .5) calc(var(--oy) *  .5) 8px var(--shadow-light);
}

@media (forced-colors: active) {
  .help-card, .help-note { border: 1px solid CanvasText; }
  .help-term { border: 1px solid ButtonText; }
  .help-term::after { forced-color-adjust: none; background: ButtonText; color: ButtonFace; }
}


/* ── css/sitenav.css ── */
/* ══════════════════════════════════════════════════════════════
   SILICONE UI — SITE NAVIGATION · sitenav.css
   The site menu (sitenav.js): sections, each a heading and its
   destinations; and the breadcrumb.

   A destination is a control: raised from the drawer, its name in
   the text, its note muted. Where the reader is presses into the
   trench in the accent. A page that isn't there yet is locked:
   recessed, in the disabled text, its "coming" in mono, as a locked
   view or tool is everywhere else. The drawer's tokens (a site's
   room may set --surface, the text and --accent) carry through.
   ══════════════════════════════════════════════════════════════ */

.sn { display: flex; flex-direction: column; gap: 10px; }
.sn-sec { margin: 0; }
.sn-h {
  margin:         0 0 6px;
  font:           600 0.65625rem system-ui, sans-serif;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color:          var(--text-muted);
}
.sn-list { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: 4px; }
.sn-item {
  display:               grid;
  grid-template-columns: 1.25rem 1fr auto;
  align-items:           center;
  column-gap:            10px;
  row-gap:               1px;
  padding:               6px 10px;
  border-radius:         12px;
  background:            var(--surface);
  color:                 var(--text-primary, var(--text));
  text-decoration:       none;
  cursor:                pointer;
  transition:            box-shadow .18s ease, color .18s ease;
  box-shadow:
    var(--rim-raised, 0 0 transparent),
    calc(var(--ox) *  .35) calc(var(--oy) *  .35) 6px var(--shadow-light),
    calc(var(--ox) * -.35) calc(var(--oy) * -.35) 6px var(--shadow-dark);
}
.sn-item > .sn-ic   { grid-row: span 2; width: 1.125rem; height: 1.125rem; }
.sn-item:not(:has(> .sn-ic)) { grid-template-columns: 1fr auto; }
.sn-name  { font: 600 0.8125rem system-ui, sans-serif; }
.sn-about { grid-column: -3 / -2; font-size: 0.6875rem; line-height: 1.35; color: var(--text-muted); }
.sn-item:not(:has(> .sn-ic)) .sn-about { grid-column: 1; }
.sn-soon  { grid-row: 1; grid-column: -2; font: 500 0.625rem ui-monospace, Consolas, monospace; color: var(--text-disabled, var(--text-muted)); }
.sn-item:hover:not([aria-disabled="true"]) { color: var(--accent); }
.sn-item:focus-visible { outline: 2px solid var(--focus-ring, var(--accent)); outline-offset: 2px; }
.sn-item[aria-current="page"],
.sn-item[aria-disabled="true"] {
  box-shadow:
    var(--rim-recess, 0 0 transparent),
    inset calc(var(--ox) * -.45) calc(var(--oy) * -.45) 10px var(--shadow-dark),
    inset calc(var(--ox) *  .45) calc(var(--oy) *  .45) 10px var(--shadow-light);
}
.sn-item[aria-current="page"] .sn-name { color: var(--accent); }
.sn-item[aria-disabled="true"] { cursor: default; color: var(--text-disabled, var(--text-muted)); }
.sn-item[aria-disabled="true"] .sn-about { color: var(--text-disabled, var(--text-muted)); }
.sn-note { margin: 0; min-height: 1em; font-size: 0.6875rem; color: var(--text-muted); }
.sn-note:empty { display: none; }

/* ── The breadcrumb ────────────────────────────────────────── */
.sn-crumb ol { display: flex; flex-wrap: wrap; align-items: center; gap: 0 6px; margin: 0; padding: 0; list-style: none; }
.sn-crumb li { display: inline-flex; align-items: center; gap: 6px; font: 600 0.6875rem system-ui, sans-serif; letter-spacing: 0.04em; color: var(--text-muted); }
.sn-crumb li + li::before { content: "\203A" / ""; color: var(--text-disabled, var(--text-muted)); }
.sn-crumb a { color: var(--text-muted); text-decoration: none; border-radius: 6px; }
.sn-crumb a:hover { color: var(--accent); }
.sn-crumb a:focus-visible { outline: 2px solid var(--focus-ring, var(--accent)); outline-offset: 2px; }
.sn-crumb [aria-current="page"] { color: var(--text-primary, var(--text)); }

@media (prefers-reduced-motion: reduce) {
  .sn-item { transition: none; }
}
@media (forced-colors: active) {
  .sn-item { border: 1px solid ButtonText; box-shadow: none; background: Canvas; color: LinkText; forced-color-adjust: none; }
  .sn-item .sn-about { color: CanvasText; }
  .sn-item[aria-current="page"] { border: 2px solid Highlight; }
  .sn-item[aria-current="page"] .sn-name { color: Highlight; }
  .sn-item[aria-disabled="true"], .sn-item[aria-disabled="true"] :is(.sn-about, .sn-soon, .sn-name) { color: GrayText; border-color: GrayText; }
  .sn-item:focus-visible { outline: 2px solid Highlight; }
  .sn-crumb a { color: LinkText; }
  .sn-crumb [aria-current="page"] { color: CanvasText; }
}


/* ── css/message.css ── */
/* ══════════════════════════════════════════════════════════════
   SILICONE UI — MESSAGES · message.css
   The messages (message.js): success, error, empty, restricted,
   and a wait (busy), in three places.

   A toast rises above the surface (an extrude, as fb-toast), in a
   stack above the south edge (--menu-b), newest last. Inline, the
   message is put in the sheet beside its control (a well: DESIGN.md
   D7). A page message is a raised panel with its heading.

   Each kind has its icon and its LED (status.css: a lit circle,
   a square, an unlit ring, a diamond, the ready pulse), and its
   words take --status-text, which keeps AA; colour is never the
   only sign. A wait's bar is a recessed track filled in the
   status colour; indeterminate, a band that sweeps (reduced
   motion: still, hatched). Forced colours draw edges in
   CanvasText and the fill in Highlight.
   ══════════════════════════════════════════════════════════════ */

.msg-live {
  position: absolute; width: 1px; height: 1px; margin: -1px; padding: 0;
  overflow: hidden; clip: rect(0 0 0 0); clip-path: inset(50%); white-space: nowrap; border: 0;
}
.msg-kind {
  position: absolute; width: 1px; height: 1px; overflow: hidden; clip-path: inset(50%); white-space: nowrap;
}

.msg-stack {
  position:       fixed;
  left:           50%;
  bottom:         calc(var(--wall-lg, 24px) + var(--menu-b, 0px));
  transform:      translateX(-50%);
  z-index:        9000;
  display:        flex;
  flex-direction: column;
  align-items:    center;
  gap:            8px;
  width:          max-content;
  max-width:      min(460px, calc(100vw - var(--wall-lg, 24px) * 2));
  pointer-events: none;
}

.msg {
  display:     grid;
  grid-template-columns: auto minmax(0, 1fr) auto;
  align-items: start;
  column-gap:  10px;
  color:       var(--status-text, var(--text-primary));
  font-size:   calc(var(--fs-body, 1rem) - 0.0625rem);
  line-height: 1.35;
  text-align:  left;
}
.msg[hidden] { display: none; }
.msg-ic { grid-row: 1 / span 2; display: inline-flex; width: 1.25rem; height: 1.25rem; margin-top: 1px; }
.msg-ic > * { width: 100%; height: 100%; }
.msg > .led-dot { position: absolute; top: 8px; left: 8px; width: 6px; height: 6px; }
.msg-body { min-width: 0; display: flex; flex-direction: column; gap: 2px; }
.msg-body > [hidden] { display: none; }
.msg-title { margin: 0; font-weight: 600; color: var(--status-text, var(--text-primary)); }
.msg-words, .msg-next, .msg-left { margin: 0; }
.msg-next, .msg-left { color: var(--text-muted); font-size: .92em; }
.msg-left { font-variant-numeric: tabular-nums; }
.msg-acts { display: flex; gap: 4px; align-items: center; }
.msg-acts[hidden] { display: none; }
.msg-x { min-width: 1.75rem; padding-inline: 6px !important; }

/* the toast: raised above the surface */
.msg-toast {
  position:       relative;
  pointer-events: auto;
  padding:        9px 10px 9px 14px;
  border-radius:  16px;
  background:     var(--surface);
  box-shadow:
    var(--rim-raised-enhanced, 0 0 transparent),
    calc(var(--ox, 2px) *  .75) calc(var(--oy, 2px) *  .75) 16px var(--shadow-light),
    calc(var(--ox, 2px) * -.75) calc(var(--oy, 2px) * -.75) 16px var(--shadow-dark),
    0 0 16px var(--status-glow, transparent);
  animation:      msg-in .32s cubic-bezier(.34, 1.05, .64, 1) both;
}
.msg-toast.msg-out { animation: msg-out .24s cubic-bezier(.4, 0, 1, 1) forwards; }
@keyframes msg-in  { from { opacity: 0; transform: translateY(12px); } }
@keyframes msg-out { to   { opacity: 0; transform: translateY(10px); } }

/* inline: put in the sheet, beside its control */
.msg-inline {
  position:      relative;
  margin:        4px 0;
  padding:       6px 8px 6px 12px;
  border-radius: 10px;
  font-size:     .8125rem;
  box-shadow:
    var(--rim-recess, 0 0 transparent),
    inset calc(var(--ox, 2px) * -.3) calc(var(--oy, 2px) * -.3) 5px var(--shadow-dark),
    inset calc(var(--ox, 2px) *  .3) calc(var(--oy, 2px) *  .3) 5px var(--shadow-light);
  animation: msg-ease .18s ease-out both;
}
.msg-inline .msg-ic { width: 1rem; height: 1rem; }
@keyframes msg-ease { from { opacity: 0; } }

/* a page: a raised panel with its heading, for a route that can't open */
.msg-page {
  position:      relative;
  max-width:     34rem;
  margin:        12vh auto 0;
  padding:       22px 22px 20px;
  border-radius: 22px;
  background:    var(--surface);
  box-shadow:
    var(--rim-raised, 0 0 transparent),
    calc(var(--ox, 2px) *  .6) calc(var(--oy, 2px) *  .6) 14px var(--shadow-light),
    calc(var(--ox, 2px) * -.6) calc(var(--oy, 2px) * -.6) 14px var(--shadow-dark);
}
.msg-page .msg-ic { width: 2rem; height: 2rem; }
.msg-page .msg-title { font-size: 1.25rem; }
.msg-page .msg-title:focus { outline: none; }
.msg-page .msg-title:focus-visible { outline: 2px solid var(--focus-ring); outline-offset: 3px; }
.msg-page .msg-acts { grid-column: 2 / -1; margin-top: 12px; }

/* a wait's bar: a recessed track, filled in the status colour */
.msg-bar {
  position:      relative;
  height:        6px;
  margin-top:    4px;
  border-radius: 6px;
  overflow:      hidden;
  min-width:     10rem;
  box-shadow:
    inset calc(var(--ox, 2px) * -.25) calc(var(--oy, 2px) * -.25) 3px var(--shadow-dark),
    inset calc(var(--ox, 2px) *  .25) calc(var(--oy, 2px) *  .25) 3px var(--shadow-light);
}
.msg-bar[hidden] { display: none; }
.msg-bar > span {
  position: absolute; inset: 0 auto 0 0; width: 0;
  border-radius: inherit;
  background: var(--status-colour, var(--accent));
  transition: width .2s ease-out;
}
.msg-bar.msg-bar-ind > span { width: 30%; animation: msg-sweep 1.4s ease-in-out infinite; }
@keyframes msg-sweep { from { left: -30%; } to { left: 100%; } }

@media (prefers-reduced-motion: reduce) {
  .msg-toast, .msg-toast.msg-out, .msg-inline { animation: none; }
  .msg-bar > span { transition: none; }
  /* no sweep: the whole track hatched, still */
  .msg-bar.msg-bar-ind > span {
    animation: none; width: 100%; left: 0;
    background: repeating-linear-gradient(135deg, var(--status-colour, var(--accent)) 0 4px, transparent 4px 8px);
  }
}

@media (forced-colors: active) {
  .msg-toast, .msg-page, .msg-inline { border: 1px solid CanvasText; }
  .msg-bar { border: 1px solid CanvasText; }
  .msg-bar > span { forced-color-adjust: none; background: Highlight; }
  .msg-bar.msg-bar-ind > span { background: repeating-linear-gradient(135deg, Highlight 0 4px, transparent 4px 8px); }
}


/* ── css/stats.css ── */
/* ══════════════════════════════════════════════════════════════
   SILICONE UI — STATS
   stats.css · v1.0.0

   Stat pills display key metrics prominently.
   Bump z+0.5 — slightly proud, data that matters but isn't
   the primary interactive element.

   Shadow scale 0.5 — between bump and extrude.
   NW gloss via ::before at 0.12 opacity — lighter than cards,
   appropriate for the smaller surface area.

   The large number (.stat-num) carries the primary visual
   weight. Label and sub provide context at smaller scale.
   Arrow (.stat-arrow) uses --c2 to signal direction.

   Depends on: tokens.css
   Lax Pairs Limited · silicone.studio · MIT Licence
   ══════════════════════════════════════════════════════════════ */


/* ── Stat pill container ─────────────────────────────────────
   Bump z+0.5 — slightly proud, information display.
   Not as raised as an interactive card.
   Overflow hidden for the ::before gloss to clip correctly.   */

.stat-pill {
  border-radius: 12px;
  padding:       var(--wall) var(--wall-sm);
  background:    var(--surface);
  position:      relative;
  overflow:      hidden;

  /* Bump shadow — shadow scale 0.5 */
  box-shadow:
    var(--rim-raised-enhanced),
    calc(var(--ox) *  .5) calc(var(--oy) *  .5) 10px var(--shadow-light),
    calc(var(--ox) * -.5) calc(var(--oy) * -.5) 10px var(--shadow-dark);
}

/* NW gloss — lighter opacity than card (0.12 vs 0.14) */
.stat-pill::before {
  content:        '';
  position:       absolute;
  inset:          0;
  border-radius:  12px;
  background:     linear-gradient(135deg, rgba(255,255,255,.12) 0%, transparent 55%);
  pointer-events: none;
}


/* ── Stat number ─────────────────────────────────────────────
   The primary data value. Large, bold, tight tracking.
   Line-height 1 — number sits compact, no extra vertical space.
   Colour is text-primary — maximum contrast for data.         */

.stat-num {
  font-weight:    800;
  font-size:      clamp(1.25rem, 4vw, 1.875rem);   /* phones keep 20px; desktop 30px + 2px per step */
  letter-spacing: -0.02em;
  line-height:    1;
  color:          var(--text-primary);
  margin-bottom:  4px;
}


/* ── Stat arrow ──────────────────────────────────────────────
   Direction indicator. Forest green — positive signal.
   Used inline within or adjacent to .stat-num.                */

.stat-arrow {
  color: var(--accent);
}


/* ── Stat label ──────────────────────────────────────────────
   The metric name. Uppercase tracking at small size.
   Muted colour — label recedes, number is primary.            */

.stat-label {
  font-weight:    600;
  font-size:      0.625rem;
  letter-spacing: 0.04em;
  color:          var(--text-muted);
}


/* ── Stat sub ────────────────────────────────────────────────
   Secondary context line. Smallest size, most muted.
   Source attribution, units, comparison context.              */

.stat-sub {
  font-size:   0.5625rem;
  color:       var(--text-muted);
  margin-top:  3px;
}


/* ── css/rows.css ── */
/* ══════════════════════════════════════════════════════════════
   SILICONE UI — ROWS
   rows.css · v1.0.0

   Row patterns for lists of structured content.
   All use the same two-pixel separator physics:
   a debossed rule between rows — dark above, light below.
   This gives the illusion of the rows sitting in a shallow
   cavity, each one slightly inlaid in the surface.

   Value rows:   dot + title + body — for feature/value lists
   Cite rows:    icon + title + meta + count — for references
   Timeline:     vertical connector + node LED + item card
   LH rows:      minimal hover row — navigation / link lists

   The separator trick:
   box-shadow: 0 1px 0 var(--shadow-dark), 0 2px 0 var(--shadow-light);
   Two shadows, 1px apart — creates a debossed line effect
   without a border element.

   Depends on: tokens.css
   Lax Pairs Limited · silicone.studio · MIT Licence
   ══════════════════════════════════════════════════════════════ */


/* ── Value row ───────────────────────────────────────────────
   LED dot + title + body text.
   Used for feature lists, value propositions, capabilities.
   The dot is a mint LED — scales with --led and --r.          */

.value-row {
  display:     flex;
  align-items: flex-start;
  gap:         12px;
  padding:     var(--wall-sm) 0;

  /* Debossed separator */
  box-shadow:
    0 1px 0 var(--shadow-dark),
    0 2px 0 var(--shadow-light);
}

.value-row:last-child {
  box-shadow: none;
}

/* Mint LED dot — scales with --r (size) and --led (glow) */
.value-dot {
  width:       calc(5px + var(--r) * 1px);
  height:      calc(5px + var(--r) * 1px);
  border-radius: 50%;
  background:  var(--c3);
  flex-shrink: 0;
  margin-top:  7px;

  /* Glow scales with --led */
  box-shadow: 0 0 6px rgba(26,251,186,.5);
  opacity:    calc(0.4 + var(--led) * 0.6);
  transition: opacity .38s ease;
}

.value-title {
  font-weight:   600;
  font-size:     0.75rem;
  color:         var(--text-primary);
  margin-bottom: 2px;
}

.value-body {
  font-size:   0.6875rem;
  color:       var(--text-secondary);
  line-height: 1.65;
}


/* ── Cite row ────────────────────────────────────────────────
   Icon + citation info + count. For reference/bibliography lists.
   Same separator as value-row.
   Icon area is fixed 28px — consistent visual rhythm.         */

.cite-row {
  display:     flex;
  align-items: center;
  gap:         12px;
  padding:     var(--wall-sm) var(--wall);

  /* Debossed separator */
  box-shadow:
    0 1px 0 var(--shadow-dark),
    0 2px 0 var(--shadow-light);
}

.cite-row:last-child {
  box-shadow: none;
}

.cite-icon {
  width:       28px;
  height:      28px;
  color:       var(--accent);
  flex-shrink: 0;
}

.cite-icon svg {
  width:  100%;
  height: 100%;
}

.cite-info   { flex: 1; }

.cite-title {
  font-weight: 600;
  font-size:   0.75rem;
  color:       var(--text-primary);
}

.cite-meta {
  font-size:   0.625rem;
  color:       var(--text-muted);
  margin-top:  2px;
  line-height: 1.5;
}

.cite-count {
  font-weight: 700;
  font-size:   0.6875rem;
  color:       var(--accent);
  flex-shrink: 0;
}


/* ── Timeline ────────────────────────────────────────────────
   Vertical connector with node LEDs and item cards.
   The connector is a two-pixel debossed line — same shadow
   physics as row separators but vertical.
   Each node is a mint LED circle — scales with --led.
   Items are bump z+0.5 cards — raised but secondary.         */

.timeline {
  position:     relative;
  padding-left: 28px;
}

/* Vertical connector — debossed line */
.timeline::before {
  content:  '';
  position: absolute;
  left:     8px;
  top:      8px;
  bottom:   8px;
  width:    2px;

  /* Debossed vertical line — NW light, SE dark */
  box-shadow:
    -0.75px 0 0 var(--shadow-light),
     0.75px 0 0 var(--shadow-dark);
}

/* Timeline item — bump z+0.5 card */
.timeline-item {
  position:      relative;
  margin-bottom: var(--wall);
  padding:       var(--wall-sm) var(--wall);
  border-radius: 12px;
  background:    var(--surface);

  /* Bump shadow — shadow scale 0.45 */
  box-shadow:
    var(--rim-raised-enhanced),
    calc(var(--ox) *  .45) calc(var(--oy) *  .45) 8px var(--shadow-light),
    calc(var(--ox) * -.45) calc(var(--oy) * -.45) 8px var(--shadow-dark);
}

/* Node LED — mint circle on the connector */
.timeline-item::before {
  content:    '';
  position:   absolute;
  left:       calc(-28px + 4px);
  top:        50%;
  transform:  translateY(-50%);
  width:      10px;
  height:     10px;
  border-radius: 50%;
  background: var(--c3);
  box-shadow: 0 0 6px rgba(26,251,186,.5);
}

.timeline-year {
  font-weight:    700;
  font-size:      0.625rem;
  color:          var(--accent);
  letter-spacing: 0.06em;
  margin-bottom:  2px;
}

.timeline-title {
  font-weight: 600;
  font-size:   0.8125rem;
  color:       var(--text-primary);
}

.timeline-body {
  font-size:   0.6875rem;
  color:       var(--text-secondary);
  margin-top:  2px;
  line-height: 1.6;
}


/* ── LH hover row ────────────────────────────────────────────
   Minimal row for navigation / link lists.
   No separator visible at rest — appears on hover/contact.
   Used in the library for inline navigation patterns.         */

.lh {
  display:     flex;
  align-items: center;
  padding:     var(--wall-sm) 0;
  transition:  color .18s ease;
}


/* Reduced motion: LED and hover colour changes take a quick fade */
@media (prefers-reduced-motion: reduce) {
  .value-dot,
  .lh {
    transition-duration: 80ms;
  }
}


/* ── css/chips.css ── */
/* ══════════════════════════════════════════════════════════════
   SILICONE UI — CHIPS, DIVIDERS & TAGS
   chips.css · v1.0.0

   Small raised pill elements for tags, filters, categories.
   All are bump z+0.5 — slightly proud, secondary interactive.

   Chip system:
   .chip       — interactive pill, can open a drawer
   .cdot       — indicator dot inside a chip (minted on active)
   .drawer     — debossed cavity revealed when chip opens
   .sc         — sub-chip inside a drawer

   Divider system:
   .divide      — flex row container for a divider
   .divide-line — the actual debossed separator line
   .divide-pill — optional label pill in the middle

   Tag system:
   .pc          — small pill tag (page chips, category markers)
   .pf          — flag text inside a .pc

   Narrative section (nar):
   .nar         — debossed narrative/context box
   .nc          — narrative chip (small pill inside .nar)
   .nchips      — flex wrap container for .nc pills

   Depends on: tokens.css
   Lax Pairs Limited · silicone.studio · MIT Licence
   ══════════════════════════════════════════════════════════════ */


/* ── Chip ────────────────────────────────────────────────────
   Interactive pill. Bump z+0.5 at rest.
   Presses into debossed on .open (chip is selected/active).
   Scale 1.04 on hover — slight magnetic pull toward cursor.   */

.chip {
  display:        inline-flex;
  align-items:    center;
  min-height:     1.5rem;   /* 24px target floor (WCAG 2.2, 2.5.8) at every reading size */
  gap:            5px;
  padding:        var(--pad-pill);
  border-radius:  100px;
  font-size:      0.625rem;  /* 10px at Short */
  font-weight:    600;
  color:          var(--text-secondary);
  background:     var(--surface);
  cursor:         pointer;
  position:       relative;
  overflow:       hidden;

  /* Bump shadow — shadow scale 0.3 */
  box-shadow:
    var(--rim-raised),
    calc(var(--ox) *  .3) calc(var(--oy) *  .3) 5px var(--shadow-light),
    calc(var(--ox) * -.3) calc(var(--oy) * -.3) 5px var(--shadow-dark);

  transition:
    box-shadow .18s ease,
    color      .18s ease,
    transform  .18s ease;
}

/* NW gloss — even small pills catch the 135° light           */
.chip::before {
  content:        '';
  position:       absolute;
  inset:          0;
  border-radius:  100px;
  background:     linear-gradient(135deg, rgba(255,255,255,.12) 0%, transparent 55%);
  pointer-events: none;
}

/* Hover — forest green text, slight magnetic scale */
.chip:hover {
  color:     var(--accent);
  transform: scale(1.04);
}

/* Open — chip presses into surface (debossed = selected)     */
.chip.open {
  color: var(--accent);
  box-shadow:
    var(--rim-recess),
    inset calc(var(--ox) * -.4) calc(var(--oy) * -.4) 6px var(--shadow-dark),
    inset calc(var(--ox) *  .4) calc(var(--oy) *  .4) 6px var(--shadow-light);
}


/* ── Chip dot indicator ──────────────────────────────────────
   Small dot inside a chip — signals state.
   Muted at rest, mint on hover/open.
   LED glow fires when chip is open.
   has-sub variant uses seafoam — indicates nested content.    */

.cdot {
  width:         5px;
  height:        5px;
  border-radius: 50%;
  background:    var(--text-muted);
  flex-shrink:   0;
  transition:
    background  .18s ease,
    box-shadow  .18s ease;
}

/* Chip hover or open — dot becomes mint LED */
.chip:hover .cdot,
.chip.open  .cdot {
  background: var(--c3);
  box-shadow: 0 0 4px rgba(26,251,186,.5);
}

/* has-sub — chip has nested sub-chips — seafoam dot */
.chip.has-sub .cdot {
  background: var(--c4);
}


/* ── Drawer ──────────────────────────────────────────────────
   Debossed cavity revealed when a chip opens.
   Contains sub-chips (.sc) for nested filtering.
   Hidden by default, flex-wrap on .open.                      */

.drawer {
  display:       none;
  flex-wrap:     wrap;
  gap:           5px;
  margin-top:    6px;
  padding:       var(--wall-sm);
  border-radius: 10px;
  width:         100%;

  /* Debossed cavity — content lives inside the surface */
  box-shadow:
    var(--rim-recess-enhanced),
    inset calc(var(--ox) * -.5) calc(var(--oy) * -.5) 8px var(--shadow-dark),
    inset calc(var(--ox) *  .5) calc(var(--oy) *  .5) 8px var(--shadow-light);
}

.drawer.open { display: flex; }


/* ── Sub-chip ────────────────────────────────────────────────
   Smaller pill inside a drawer cavity.
   Bump z+0.5 — rises slightly from the cavity floor.
   Shadow scale 0.2 — very slight, appropriate from cavity.    */

.sc {
  display:       flex;
  align-items:   center;
  min-height:    1.5rem;   /* 24px target floor (WCAG 2.2, 2.5.8) */
  padding:       var(--pad-pill-sm);
  border-radius: 100px;
  font-size:     0.5625rem;  /* 9px at Short */
  font-weight:   600;
  color:         var(--text-muted);
  background:    var(--surface);

  /* Tiny bump from cavity floor */
  box-shadow:
    var(--rim-raised),
    calc(var(--ox) *  .2) calc(var(--oy) *  .2) 4px var(--shadow-light),
    calc(var(--ox) * -.2) calc(var(--oy) * -.2) 4px var(--shadow-dark);
}


/* ── Divider ─────────────────────────────────────────────────
   Horizontal separator with optional pill label.
   The line itself is a two-pixel debossed rule.
   Pill floats in the center of the line.                      */

.divide {
  display:     flex;
  align-items: center;
  gap:         var(--wall-sm);
  margin:      var(--wall) 0;
}

/* The debossed line — two shadows, 1.5px apart               */
.divide-line {
  flex:   1;
  height: 2px;
  box-shadow:
    0 -0.75px 0 var(--shadow-dark),
     0  0.75px 0 var(--shadow-light);
}

/* Optional label pill floating in the divider                 */
.divide-pill {
  padding:        3px 10px;
  border-radius:  100px;
  font-weight:    600;
  font-size:      0.5rem;
  letter-spacing: .08em;
  text-transform: uppercase;
  color:          var(--text-muted);
  background:     var(--surface);
  white-space:    nowrap;

  /* Bump — pill rises from the divider line */
  box-shadow:
    var(--rim-raised-enhanced),
    calc(var(--ox) *  .3) calc(var(--oy) *  .3) 5px var(--shadow-light),
    calc(var(--ox) * -.3) calc(var(--oy) * -.3) 5px var(--shadow-dark);
}


/* ── Page chip ───────────────────────────────────────────────
   Small pill tag for page categories, filters, breadcrumbs.
   Smaller than .chip — used in denser layouts.
   .pf is the flag/icon text inside the chip.                  */

.pc {
  display:        inline-flex;
  align-items:    center;
  min-height:     1.5rem;   /* 24px target floor (WCAG 2.2, 2.5.8) at every reading size */
  gap:            4px;
  padding:        var(--pad-pill-sm);
  border-radius:  100px;
  font-size:      0.5625rem;  /* 9px at Short */
  font-weight:    600;
  color:          var(--text-secondary);
  background:     var(--surface);
  cursor:         pointer;
  white-space:    nowrap;

  /* Tiny bump — very slight rise */
  box-shadow:
    var(--rim-raised),
    calc(var(--ox) *  .25) calc(var(--oy) *  .25) 4px var(--shadow-light),
    calc(var(--ox) * -.25) calc(var(--oy) * -.25) 4px var(--shadow-dark);

  transition:
    color      .18s ease,
    transform  .15s ease;
}

.pc:hover {
  color:     var(--accent);
  transform: scale(1.04);
}

/* Reduced motion: colour, the LED dot and the open press still
   fade in quickly; the hover scale snaps instead of growing.  */
@media (prefers-reduced-motion: reduce) {
  .chip {
    transition:
      box-shadow 80ms ease,
      color      80ms ease;
  }

  .cdot {
    transition-duration: 80ms;
  }

  .pc {
    transition: color 80ms ease;
  }
}

/* Flag text — emoji or icon character inside .pc             */
.pf { font-size: 1em; }


/* ── Narrative section ───────────────────────────────────────
   Debossed context box for explanatory/narrative content.
   Sits recessed — lower priority than main content.
   .nar h4 and .nar p are styled for compact narrative text.   */

.nar {
  margin-top:    var(--wall-lg);
  padding:       var(--wall);
  border-radius: 14px;
  background:    var(--surface);

  /* Debossed — content recedes into surface */
  box-shadow:
    var(--rim-recess-enhanced),
    inset calc(var(--ox) * -.5) calc(var(--oy) * -.5) 8px var(--shadow-dark),
    inset calc(var(--ox) *  .5) calc(var(--oy) *  .5) 8px var(--shadow-light);
}

.nar h4 {
  font-weight:   800;
  font-size:     0.75rem;
  color:         var(--text-primary);
  margin-bottom: 3px;
}

.nar p {
  font-size:     0.625rem;
  color:         var(--text-muted);
  margin-bottom: var(--wall-sm);
  max-width:     56ch;
  line-height:   1.6;
}

/* Chips container inside .nar */
.nchips {
  display:   flex;
  flex-wrap: wrap;
  gap:       5px;
}

/* Narrative chip — bump from cavity floor */
.nc {
  padding:       3px 9px;
  border-radius: 100px;
  font-size:     0.5625rem;
  font-weight:   600;
  color:         var(--text-secondary);
  background:    var(--surface);
  box-shadow:
    var(--rim-raised-enhanced),
    calc(var(--ox) *  .25) calc(var(--oy) *  .25) 4px var(--shadow-light),
    calc(var(--ox) * -.25) calc(var(--oy) * -.25) 4px var(--shadow-dark);
}

/* High emphasis narrative chip — forest green with LED hint  */
.nc.high {
  color: var(--accent);
  box-shadow:
    var(--rim-raised-enhanced),
    calc(var(--ox) *  .35) calc(var(--oy) *  .35) 6px var(--shadow-light),
    calc(var(--ox) * -.35) calc(var(--oy) * -.35) 6px var(--shadow-dark),
    0 0 4px rgba(26,251,186,.1);
}


/* ── Chip section label ──────────────────────────────────────
   Uppercase label above a group of chips.
   .cl = chip label, .cr = chip row (flex wrap)               */

.cl {
  font-weight:    600;
  font-size:      0.5rem;
  letter-spacing: .1em;
  text-transform: uppercase;
  color:          var(--text-muted);
  margin-bottom:  6px;
}

.cr {
  display:   flex;
  flex-wrap: wrap;
  gap:       5px;
}


/* ── Pseudo-section ──────────────────────────────────────────
   .ps — section with a top separator.
   Used to divide content areas with the same debossed rule.   */

.ps {
  margin-top:  var(--wall-sm);
  padding-top: var(--wall-sm);
  position:    relative;
}

.ps::before {
  content:  '';
  position: absolute;
  top:      0;
  left:     0;
  right:    0;
  height:   2px;
  box-shadow:
    0 -0.75px 0 var(--shadow-light),
     0  0.75px 0 var(--shadow-dark);
}


/* ── css/led.css ── */
/* ══════════════════════════════════════════════════════════════
   SILICONE UI — LED SYSTEM
   led.css · v1.0.0

   The LED system connects the physical interface to semantic
   meaning. Dots glow. Their intensity is controlled by --led.
   Their colour is controlled by the semantic LED tokens.

   Four semantic LED colours (from tokens.css):
   --led-player:   #1AFBBA  mint   — own system, always safe
   --led-material: #F5A623  amber  — physical material state
   --led-risk:     #E8453C  red    — error, alert, critical
   --led-reg:      #4A90D9  blue   — regulatory, informational

   The --led scale (0→1) dims all LED glows simultaneously.
   Users with light sensitivity can turn it down.
   Semantic information is preserved — position and typography
   still carry the meaning. The glow is extra signal, not
   primary signal. This is accessibility as enhancement.

   LED intensity CSS pattern:
   box-shadow: 0 0 calc(6px * var(--led)) rgba(26,251,186, calc(var(--led) * 0.8));
   As --led approaches 0, both radius and opacity collapse.

   Depends on: tokens.css
   Lax Pairs Limited · silicone.studio · MIT Licence
   ══════════════════════════════════════════════════════════════ */


/* ── LED dot ─────────────────────────────────────────────────
   A small circle with a glow. The base element.
   Size and colour are set by the consuming component.
   This class handles only the base geometry.

   For specific LED colours, apply inline styles or
   use the semantic colour tokens via additional classes.

   Example:
   <div class="led-dot" style="background:var(--led-player);
     box-shadow:0 0 calc(6px*var(--led)) rgba(26,251,186,calc(var(--led)*.8))">
   </div>                                                      */

.led-dot {
  width:         6px;
  height:        6px;
  border-radius: 50%;
  flex-shrink:   0;
}


/* ── Value dot ───────────────────────────────────────────────
   The mint LED dot in value-row lists.
   Opacity scales with --led — dims with LED intensity control.
   Size scales with --r — larger at higher readability settings.
   Defined in rows.css structurally, here for LED behaviour.   */

.value-dot {
  opacity:    calc(0.4 + var(--led) * 0.6);
  transition: opacity .38s ease;
}


/* ── Neon logo glow ──────────────────────────────────────────
   The neon logo glow element scales with --led.
   Slightly different range — starts at 0.3 rather than 0.4
   because the neon effect requires a higher floor to read.
   Defined in brand.css structurally, here for LED behaviour.  */

.neon-logo-glow {
  opacity:    calc(0.3 + var(--led) * 0.7);
  transition: opacity .38s ease;
}


/* ── VU meter segments ───────────────────────────────────────
   Used in the mixer analog component.
   Segments brighten via filter: brightness() scaled by --led.
   Three colours — green (nominal), amber (hot), red (clip).
   When --led = 0, segments are at 50% brightness (visible
   but clearly dimmed). At --led = 1, full brightness.         */

.vu-seg.lit-green,
.vu-seg.lit-amber,
.vu-seg.lit-red {
  filter:     brightness(calc(0.5 + var(--led) * 0.5));
  transition: filter .38s ease;
}


/* ── LED glow utility classes ────────────────────────────────
   Convenience classes for adding LED glow to any element.
   Each applies the standard glow pattern for that colour,
   scaled by --led.

   Usage: add class to any element that should glow.
   The element must have a background colour set separately.   */

.led-glow-mint {
  box-shadow:
    0 0 calc(6px  * var(--led)) rgba(26,  251, 186, calc(var(--led) * 0.9)),
    0 0 calc(14px * var(--led)) rgba(26,  251, 186, calc(var(--led) * 0.4));
}

.led-glow-amber {
  box-shadow:
    0 0 calc(6px  * var(--led)) rgba(245, 166, 35,  calc(var(--led) * 0.9)),
    0 0 calc(14px * var(--led)) rgba(245, 166, 35,  calc(var(--led) * 0.4));
}

.led-glow-risk {
  box-shadow:
    0 0 calc(6px  * var(--led)) rgba(232, 69,  60,  calc(var(--led) * 0.9)),
    0 0 calc(14px * var(--led)) rgba(232, 69,  60,  calc(var(--led) * 0.4));
}

.led-glow-reg {
  box-shadow:
    0 0 calc(6px  * var(--led)) rgba(74,  144, 217, calc(var(--led) * 0.9)),
    0 0 calc(14px * var(--led)) rgba(74,  144, 217, calc(var(--led) * 0.4));
}


/* ── LED pulse animation ─────────────────────────────────────
   Optional: apply to any LED dot for a breathing pulse.
   Duration and delay can be overridden per element.
   The pulse is gentle — opacity 1→0.28 over 2.2s.            */

@keyframes led-pulse {
  0%,  100% { opacity: 1; }
  50%        { opacity: 0.28; }
}

.led-pulse {
  animation: led-pulse 2.2s ease-in-out infinite;
}


/* ── Reduced motion ──────────────────────────────────────────
   The pulse rests: the dot stays lit, steady. LED intensity
   changes (--led) take a quick fade.                          */

@media (prefers-reduced-motion: reduce) {
  .value-dot,
  .neon-logo-glow,
  .vu-seg.lit-green,
  .vu-seg.lit-amber,
  .vu-seg.lit-red {
    transition-duration: 80ms;
  }

  .led-pulse {
    animation: none;
  }
}


/* ── css/feedback.css ── */
/* ══════════════════════════════════════════════════════════════
   SILICONE UI — FEEDBACK
   feedback.css · v1.0.0

   System response states: toasts, badges, skeletons, empty states.
   These are the fb-* classes referenced in the depth Z_CLASS_MAP
   in cel-engine-adapter.ts.

   Depth map for feedback elements:
   fb-toast     → extrude z+1  — rises above surface to be seen
   fb-badge     → bump   z+0.5 — small proud pill, secondary info
   fb-skeleton  → debossed z-1 — loading cavity, awaiting content
   fb-empty     → debossed z-1 — empty cavity, nothing here yet

   Toasts are the highest-urgency feedback — they float above the
   surface at extrude depth with a stronger shadow than a card.
   They are not deeper — one hero per composition still applies.

   Skeleton loading is debossed — it is a cavity awaiting content.
   When content arrives, the cavity extrudes to a card.
   This is the same semantic as a form field being filled.

   Depends on: tokens.css, motion.css
   Lax Pairs Limited · silicone.studio · MIT Licence
   ══════════════════════════════════════════════════════════════ */


/* ── Toast ───────────────────────────────────────────────────
   System feedback floating above the surface.
   Positioned fixed — bottom centre of the viewport.
   Rises above everything at z+1 extrude depth.
   Auto-dismisses (JS manages the .fb-toast-dismiss class).

   Status comes from data-status (status.css); the icon is a
   .led-dot. Add .fb-toast-inline for an in-flow notice.        */

.fb-toast {
  position:     fixed;
  bottom:       calc(var(--wall-lg) + var(--menu-b, 0px));
  left:         50%;
  transform:    translateX(-50%) translateY(0);
  z-index:      9000;

  display:      flex;
  align-items:  center;
  gap:          var(--wall-sm);
  max-width:    min(420px, calc(100vw - var(--wall-lg) * 2));
  padding:      var(--wall-sm) var(--wall);
  border-radius: 100px;

  background:   var(--surface);
  font-size:    calc(var(--fs-body) - 0.0625rem);
  font-weight:  600;
  color:        var(--text-primary);
  white-space:  nowrap;
  overflow:     hidden;
  text-overflow: ellipsis;

  /* Extrude — rises above surface to be seen                  */
  box-shadow:
    var(--rim-raised-enhanced),
    calc(var(--ox) *  .75) calc(var(--oy) *  .75) 16px var(--shadow-light),
    calc(var(--ox) * -.75) calc(var(--oy) * -.75) 16px var(--shadow-dark),
    0 4px 24px rgba(1, 50, 36, .12);

  /* Spring in from below                                      */
  animation: fb-toast-in .32s cubic-bezier(.34, 1.05, .64, 1) forwards;
}

@keyframes fb-toast-in {
  from {
    opacity:   0;
    transform: translateX(-50%) translateY(12px);
  }
  to {
    opacity:   1;
    transform: translateX(-50%) translateY(0);
  }
}

/* Dismiss — slides out and fades                              */
.fb-toast.fb-toast-dismiss {
  animation: fb-toast-out .24s cubic-bezier(.4, 0, 1, 1) forwards;
}

@keyframes fb-toast-out {
  to {
    opacity:   0;
    transform: translateX(-50%) translateY(10px);
  }
}

/* In-flow variant — a notice inside content rather than floating */
.fb-toast.fb-toast-inline {
  position:  static;
  transform: none;
  animation: none;
}

/* Status — LED glow, and a text tint that keeps AA contrast    */
.fb-toast[data-status] {
  color: var(--status-text);
  box-shadow:
    var(--rim-raised-enhanced),
    calc(var(--ox) *  .75) calc(var(--oy) *  .75) 16px var(--shadow-light),
    calc(var(--ox) * -.75) calc(var(--oy) * -.75) 16px var(--shadow-dark),
    0 0 16px var(--status-glow);
}

/* Toast icon — LED dot preceding the message text            */
.fb-toast-icon {
  width:         8px;
  height:        8px;
  border-radius: 50%;
  flex-shrink:   0;
  background:    var(--accent);
}


/* ── Badge ───────────────────────────────────────────────────
   Small bump z+0.5 pill for secondary info: counts, labels,
   status indicators. Sits proud of the surface — visible but
   not demanding. Status comes from data-status (status.css).  */

.fb-badge {
  display:        inline-flex;
  align-items:    center;
  gap:            4px;
  padding:        3px 8px;
  border-radius:  100px;
  font-size:      0.625rem;
  font-weight:    700;
  letter-spacing: 0.03em;
  color:          var(--text-secondary);
  background:     var(--surface);
  user-select:    none;
  flex-shrink:    0;

  /* Bump — small rise from surface                            */
  box-shadow:
    var(--rim-raised-enhanced),
    calc(var(--ox) *  .35) calc(var(--oy) *  .35) 6px var(--shadow-light),
    calc(var(--ox) * -.35) calc(var(--oy) * -.35) 6px var(--shadow-dark);
}

/* Status — LED glow, and a text tint that keeps AA contrast    */
.fb-badge[data-status] {
  color: var(--status-text);
  box-shadow:
    var(--rim-raised-enhanced),
    calc(var(--ox) *  .35) calc(var(--oy) *  .35) 6px var(--shadow-light),
    calc(var(--ox) * -.35) calc(var(--oy) * -.35) 6px var(--shadow-dark),
    0 0 8px var(--status-glow);
}

/* Neutral / default pill — no glow                           */
.fb-badge.fb-badge-neutral {
  color: var(--text-muted);
}


/* ── Skeleton loading ────────────────────────────────────────
   Debossed z-1 — a cavity awaiting content.
   The cavity breathes via a shimmer animation that tracks the
   NW light source direction.

   When content arrives, JS removes .fb-skeleton and the element
   renders normally. No transition needed — content appears.

   Width is set inline or via utility classes — skeletons need
   explicit sizing to reserve space in the layout.             */

.fb-skeleton {
  border-radius: 8px;
  background:    var(--surface);
  overflow:      hidden;
  position:      relative;

  /* Debossed — cavity waiting                                 */
  box-shadow:
    var(--rim-recess-enhanced),
    inset calc(var(--ox) * -.45) calc(var(--oy) * -.45) 8px var(--shadow-dark),
    inset calc(var(--ox) *  .45) calc(var(--oy) *  .45) 8px var(--shadow-light);
}

/* Shimmer — light sweeps across the cavity surface in the
   NW → SE direction (135° → 315°), consistent with --ox/--oy.
   Duration: 1.8s — slow enough to read as passive waiting.    */
.fb-skeleton::after {
  content:    '';
  position:   absolute;
  inset:      0;
  background: linear-gradient(
    125deg,
    transparent          30%,
    rgba(255,255,255,.08) 50%,
    transparent          70%
  );
  background-size: 200% 100%;
  animation: fb-skeleton-shimmer 1.8s ease-in-out infinite;
}

@keyframes fb-skeleton-shimmer {
  0%   { background-position: 200% 0; }
  100% { background-position: -200% 0; }
}

/* Height utility variants — reserve layout space              */
.fb-skeleton-text  { height: 1em;   margin-bottom: 6px; }
.fb-skeleton-title { height: 1.4em; margin-bottom: var(--wall-sm); }
.fb-skeleton-block { height: 80px;  margin-bottom: var(--wall); }
.fb-skeleton-card  { height: 140px; border-radius: 16px; margin-bottom: var(--wall-lg); }

/* Full-width by default. Narrow variants for inline use.      */
.fb-skeleton-w-half    { width: 50%; }
.fb-skeleton-w-third   { width: 33%; }
.fb-skeleton-w-quarter { width: 25%; }


/* ── Empty state ─────────────────────────────────────────────
   Debossed z-1 — a cavity that has been filled but is empty.
   Different semantics to skeleton: not loading, genuinely empty.
   Uses the same inset shadow but no shimmer — stillness signals
   "nothing here" rather than "wait".

   Contains an optional icon, heading, and description.
   Centred within its container.                               */

.fb-empty {
  display:        flex;
  flex-direction: column;
  align-items:    center;
  justify-content: center;
  text-align:     center;
  gap:            var(--wall-sm);
  padding:        var(--wall-lg) var(--wall);
  border-radius:  16px;
  background:     var(--surface);
  min-height:     160px;

  /* Debossed — the cavity is genuinely empty                  */
  box-shadow:
    var(--rim-recess-enhanced),
    inset calc(var(--ox) * -.45) calc(var(--oy) * -.45) 10px var(--shadow-dark),
    inset calc(var(--ox) *  .45) calc(var(--oy) *  .45) 10px var(--shadow-light);
}

/* Icon — muted, debossed feeling                              */
.fb-empty-icon {
  font-size:    1.625rem;
  opacity:      0.35;
  margin-bottom: 4px;
  line-height:  1;
}

/* Heading — larger than body, still muted                     */
.fb-empty-title {
  font-size:   calc(var(--fs-body) + 0.0625rem);
  font-weight: 700;
  color:       var(--text-secondary);
  line-height: 1.3;
}

/* Description — small, muted                                  */
.fb-empty-desc {
  font-size:  calc(var(--fs-body) - 0.0625rem);
  color:      var(--text-muted);
  max-width:  32ch;
  line-height: 1.65;
}

/* Optional CTA button sits below description                  */
.fb-empty .btn {
  margin-top: var(--wall-sm);
}


/* ── Reduced motion ──────────────────────────────────────────
   A toast appears and leaves in place, at once. Its animations
   still run for 0.01ms, so animationend fires for whatever
   removes a dismissed toast, and the dismiss still ends at
   opacity 0. A skeleton drops its sweeping shimmer: the shape
   of the cavity still says "content goes here".               */

@media (prefers-reduced-motion: reduce) {
  .fb-toast,
  .fb-toast.fb-toast-dismiss {
    animation-duration: 0.01ms;
  }

  .fb-skeleton::after {
    content: none;
  }
}


/* ── css/status.css ── */
/* ══════════════════════════════════════════════════════════════
   SILICONE UI — STATUS · status.css
   System status as its own channel, independent of depth:
     data-status="idle | ready | success | warning | error"
   on an element sets --status-colour, --status-glow and
   --status-text for it and everything inside; a .led-dot inside
   (or carrying the attribute) lights up. Colour is never the only
   signal: the dot is aria-hidden and sits beside text or an icon,
   and each status also has its own dot shape.
   ══════════════════════════════════════════════════════════════ */

[data-status] {
  --status-colour: var(--status-idle);
  --status-glow:   color-mix(in srgb, var(--status-colour) calc(var(--led) * 70%), transparent);
  /* 30% keeps every status ≥ 4.5:1 at standard and enhanced contrast */
  --status-text:   color-mix(in oklab, var(--status-colour) 30%, var(--text-primary));
  --led-fill:      var(--status-colour);
  --led-ring:      var(--status-colour);
  --led-shape:     50%;
  --led-turn:      0deg;
  --led-scale:     1;
}

[data-status="idle"]    { --status-glow: transparent; --led-fill: transparent; --led-ring: var(--text-muted); }
[data-status="ready"]   { --status-colour: var(--status-ready); }
[data-status="success"] { --status-colour: var(--status-success); }
[data-status="warning"] { --status-colour: var(--status-warning); --led-shape: 1px; --led-turn: 45deg; --led-scale: .85; }
[data-status="error"]   { --status-colour: var(--status-error);   --led-shape: 1px; }


/* ── Status LED ──────────────────────────────────────────────
   Circle · hollow ring (idle) · diamond (warning) · square (error).
   Glow scales with --led, the LED-intensity preference.         */

[data-status] .led-dot,
.led-dot[data-status] {
  background:    var(--led-fill);
  border-radius: var(--led-shape);
  transform:     rotate(var(--led-turn)) scale(var(--led-scale));
  box-shadow:
    inset 0 0 0 1px var(--led-ring),
    0 0 calc(6px * var(--led)) var(--status-glow);
}

@media (forced-colors: active) {
  [data-status] .led-dot,
  .led-dot[data-status] {
    forced-color-adjust: none;
    outline: 1px solid CanvasText;
  }
}


/* ── css/proximity.css ── */
/* ══════════════════════════════════════════════════════════════
   SILICONE UI — PROXIMITY GLOW
   proximity.css · v1.0.0

   The cursor is a light source.
   Interactive elements sense its proximity and respond
   before contact — like a physical object reacting to
   approaching warmth.

   --prox-glow (0→1) is set per element by proximity.js
   based on the distance between the cursor and the element's
   centre point. MAX_DIST = 160px by default.

   At distance 0 (cursor on element): --prox-glow = 1
   At distance 160px (cursor far):    --prox-glow = 0

   Each element type defines what --prox-glow drives:
   - Buttons: white text glow
   - Inline links: mint text glow
   (The showcase's nav items add their own rules in nav.css.)

   This is accessibility before compliance.
   Users with motor difficulties don't need to hit the exact
   pixel. The element glows toward the approaching cursor.
   The link finds the cursor.

   Depends on: tokens.css
   proximity.js sets --prox-glow on each element via rAF loop.
   Lax Pairs Limited · silicone.studio · MIT Licence
   ══════════════════════════════════════════════════════════════ */


/* ── Buttons ─────────────────────────────────────────────────
   White text glow on approach.
   Buttons also receive the full cursor-on-link illumination
   on contact (defined in buttons.css).
   Proximity glow fires before that — pre-contact signal.     */

.btn {
  --prox-glow: 0;

  text-shadow:
    0 0 calc(8px * var(--prox-glow))
    rgba(255, 255, 255, calc(0.6 * var(--prox-glow)));

  /* Proximity transition — separate from box-shadow transition */
  transition:
    text-shadow .2s ease,
    color       .15s ease,
    box-shadow  .22s ease,
    transform   .22s cubic-bezier(.34, 1.05, .64, 1);
}


/* ── Inline links ────────────────────────────────────────────
   Mint glow on approach — links use mint rather than white
   to distinguish them from buttons. Nav-style links override
   this with their own class rules.                            */

a {
  --prox-glow: 0;

  text-shadow:
    0 0 calc(6px * var(--prox-glow))
    rgba(26, 251, 186, calc(0.5 * var(--prox-glow)));

  transition: text-shadow .2s ease;
}


/* ── Reduced motion ──────────────────────────────────────────
   The glow, colour and depth still answer the cursor, as a
   quick fade; the button no longer springs.                   */

@media (prefers-reduced-motion: reduce) {
  .btn {
    transition:
      text-shadow 80ms ease,
      color       80ms ease,
      box-shadow  80ms ease;
  }

  a {
    transition-duration: 80ms;
  }
}


/* ── css/nav-extensions.css ── */
/* ══════════════════════════════════════════════════════════════
   SILICONE UI — NAV EXTENSIONS
   nav-extensions.css · v1.0.0

   Site-specific navigation patterns extending core nav.css.
   For deployments where the four-edge system and library nav
   aren't sufficient — page-level nav bars, breadcrumbs, tab
   bars, step indicators, pagination.

   These are structural navigation patterns, not the sidebar
   cavity (that lives in nav.css). They sit at the surface
   level — not inside a cavity.

   Depth rules still apply:
   — Active tab / step = extrude z+1 (it rose to answer)
   — Inactive = bump z+0.5 or flush
   — Track / rail behind tabs = debossed z-1 (the channel)
   — Breadcrumb separators = flush, inlaid in the surface

   Designed for the laxpairs.nz six-page circular nav model
   (laxpairs-events.cel @event.laxpairs.nav.*) and ThinkTank
   multi-step elicitation flows.

   Depends on: tokens.css, nav.css
   Lax Pairs Limited · silicone.studio · MIT Licence
   ══════════════════════════════════════════════════════════════ */


/* ── Horizontal nav bar ──────────────────────────────────────
   Top-level page navigation for sites without the sidebar model.
   Items are bump z+0.5 at rest, extrude z+1 when active.
   The bar itself is flush with the surface — no cavity, no float.
   Scrollable on narrow viewports.                             */

.nav-bar {
  display:        flex;
  align-items:    center;
  gap:            var(--wall-sm);
  overflow-x:     auto;
  scrollbar-width: none;
  -ms-overflow-style: none;
  padding:        var(--wall-sm) var(--wall);

  /* Two-pixel debossed separator below — bar inlaid at surface */
  box-shadow:
    0 1px 0 var(--shadow-dark),
    0 2px 0 var(--shadow-light);
}

.nav-bar::-webkit-scrollbar { display: none; }

/* Nav bar item — bump at rest, extrude on active              */
.nav-bar-item {
  display:        flex;
  align-items:    center;
  gap:            6px;
  padding:        var(--nav-item-pad-v) var(--nav-item-pad-h);
  border-radius:  calc(6px + var(--r) * 0.5px);
  font-size:      0.6875rem;
  font-weight:    600;
  color:          var(--text-secondary);
  text-decoration: none;
  white-space:    nowrap;
  user-select:    none;
  background:     var(--surface);
  position:       relative;
  flex-shrink:    0;
  transition:
    box-shadow .18s cubic-bezier(.34, 1.05, .64, 1),
    transform  .18s cubic-bezier(.34, 1.05, .64, 1),
    color      .15s ease;

  /* Bump — slightly proud of surface                          */
  box-shadow:
    var(--rim-raised),
    calc(var(--ox) *  .28) calc(var(--oy) *  .28) 5px var(--shadow-light),
    calc(var(--ox) * -.28) calc(var(--oy) * -.28) 5px var(--shadow-dark);
}

/* NW gloss — hidden at rest, fires on hover/active            */
.nav-bar-item::before {
  content:        '';
  position:       absolute;
  inset:          0;
  border-radius:  inherit;
  background:     linear-gradient(135deg, rgba(255,255,255,.12) 0%, transparent 55%);
  pointer-events: none;
  opacity:        0;
  transition:     opacity .18s ease;
}

.nav-bar-item:hover {
  color:     var(--text-primary);
  transform: translateY(-1px);
  box-shadow:
    var(--rim-raised),
    calc(var(--ox) *  .5) calc(var(--oy) *  .5) 9px var(--shadow-light),
    calc(var(--ox) * -.5) calc(var(--oy) * -.5) 9px var(--shadow-dark);
}
.nav-bar-item:hover::before { opacity: 1; }

/* Active — full extrude, primary colour                       */
.nav-bar-item.active {
  color:       var(--accent);
  font-weight: 700;
  transform:   translateY(-2px);
  box-shadow:
    var(--rim-raised),
    calc(var(--ox) *  .7) calc(var(--oy) *  .7) 13px var(--shadow-light),
    calc(var(--ox) * -.7) calc(var(--oy) * -.7) 13px var(--shadow-dark);
}
.nav-bar-item.active::before { opacity: 1; }


/* ── Tab bar ─────────────────────────────────────────────────
   Secondary navigation within a page or card.
   Track is debossed — a channel the tabs sit within.
   Active tab extrudes to card surface level.
   Used for: content sections, settings panels, data views.    */

.tab-bar {
  display:  flex;
  gap:      2px;
  padding:  3px;
  border-radius: calc(10px + var(--r) * 0.5px);
  background: var(--surface);

  /* Debossed track — the channel                              */
  box-shadow:
    var(--rim-recess-enhanced),
    inset calc(var(--ox) * -.4) calc(var(--oy) * -.4) 7px var(--shadow-dark),
    inset calc(var(--ox) *  .4) calc(var(--oy) *  .4) 7px var(--shadow-light);
}

/* Tab item — flush at rest, extrudes when active              */
.nb-tab {
  flex:           1;
  display:        flex;
  align-items:    center;
  justify-content: center;
  gap:            6px;
  padding:        var(--nav-item-pad-v) var(--nav-item-pad-h);
  border-radius:  calc(8px + var(--r) * 0.5px);
  font-size:      0.6875rem;
  font-weight:    600;
  color:          var(--text-muted);
  user-select:    none;
  white-space:    nowrap;
  background:     transparent;
  position:       relative;
  transition:
    box-shadow .18s cubic-bezier(.34, 1.05, .64, 1),
    transform  .18s cubic-bezier(.34, 1.05, .64, 1),
    color      .15s ease,
    background .15s ease;
}

/* Active tab — extrudes from the track channel                */
.nb-tab.active {
  background: var(--surface);
  color:      var(--text-primary);
  font-weight: 700;
  transform:  translateY(-1px);
  box-shadow:
    var(--rim-raised),
    calc(var(--ox) *  .55) calc(var(--oy) *  .55) 10px var(--shadow-light),
    calc(var(--ox) * -.55) calc(var(--oy) * -.55) 10px var(--shadow-dark);
}

/* NW gloss on active only                                     */
.nb-tab.active::before {
  content:        '';
  position:       absolute;
  inset:          0;
  border-radius:  inherit;
  background:     linear-gradient(135deg, rgba(255,255,255,.1) 0%, transparent 55%);
  pointer-events: none;
}

.nb-tab:hover:not(.active) {
  color:      var(--text-secondary);
  background: rgba(1, 50, 36, .04);
}


/* ── Step indicator ──────────────────────────────────────────
   Linear progress through a multi-step flow (ThinkTank
   elicitation, onboarding, setup wizards).

   Steps to the left of current = completed (extrude + mint dot)
   Current step = deep z+2 or extrude with LED glow
   Steps to the right = incomplete (debossed)

   The rail connecting steps is debossed — the channel exists
   even before steps are completed. Progress fills it.         */

.step-bar {
  display:     flex;
  align-items: center;
  gap:         0;
  padding:     var(--wall-sm) 0;
}

/* Step node — the circle for each step                        */
.step-node {
  width:         28px;
  height:        28px;
  border-radius: 50%;
  background:    var(--surface);
  display:       flex;
  align-items:   center;
  justify-content: center;
  flex-shrink:   0;
  position:      relative;
  font-size:     0.5625rem;
  font-weight:   700;
  color:         var(--text-muted);
  transition:
    box-shadow .22s cubic-bezier(.34, 1.05, .64, 1),
    transform  .22s cubic-bezier(.34, 1.05, .64, 1);

  /* Debossed by default — awaiting completion                 */
  box-shadow:
    var(--rim-recess-enhanced),
    inset calc(var(--ox) * -.4) calc(var(--oy) * -.4) 6px var(--shadow-dark),
    inset calc(var(--ox) *  .4) calc(var(--oy) *  .4) 6px var(--shadow-light);
}

/* Completed step — extrudes, mint dot fills                   */
.step-node.complete {
  color:     var(--c3);
  transform: translateY(-1px);
  box-shadow:
    var(--rim-raised-enhanced),
    calc(var(--ox) *  .5) calc(var(--oy) *  .5) 9px var(--shadow-light),
    calc(var(--ox) * -.5) calc(var(--oy) * -.5) 9px var(--shadow-dark),
    0 0 8px rgba(26, 251, 186, .25);
}

/* Current step — maximum rise, LED glow                       */
.step-node.current {
  color:     var(--text-primary);
  font-weight: 800;
  transform: translateY(-2px);
  box-shadow:
    var(--rim-raised-enhanced),
    calc(var(--ox) *  .7) calc(var(--oy) *  .7) 14px var(--shadow-light),
    calc(var(--ox) * -.7) calc(var(--oy) * -.7) 14px var(--shadow-dark),
    0 0 10px rgba(26, 251, 186, .2);
}

/* Rail segment between steps — debossed channel               */
.step-rail {
  flex:          1;
  height:        3px;
  background:    var(--surface);
  position:      relative;

  box-shadow:
    var(--rim-recess-enhanced),
    inset calc(var(--ox) * -.3) calc(var(--oy) * -.3) 4px var(--shadow-dark),
    inset calc(var(--ox) *  .3) calc(var(--oy) *  .3) 4px var(--shadow-light);
}

/* Fill — progress through completed sections                  */
.step-rail::after {
  content:       '';
  position:      absolute;
  left:          0;
  top:           0;
  bottom:        0;
  width:         var(--step-progress, 0%);
  border-radius: inherit;
  background:    var(--c3);
  box-shadow:    0 0 4px rgba(26, 251, 186, .5);
  transition:    width .38s cubic-bezier(.34, 1.05, .64, 1);
}


/* ── Breadcrumb ──────────────────────────────────────────────
   Horizontal path display. Flush with surface — inlaid, not raised.
   The path is orientation, not navigation — it tells you where
   you are rather than asking you to act.
   Separator is a debossed slash — it recedes between items.   */

.breadcrumb {
  display:     flex;
  align-items: center;
  flex-wrap:   wrap;
  gap:         4px;
  font-size:   0.6875rem;  /* 11px at Short */
  font-weight: 600;
  color:       var(--text-muted);
}

.breadcrumb-item {
  color:           var(--text-muted);
  text-decoration: none;
  transition:      color .18s ease;
  white-space:     nowrap;
}

.breadcrumb-item:hover {
  color: var(--text-secondary);
}

/* Current page — primary colour, no link behaviour            */
.breadcrumb-item.current {
  color:  var(--text-primary);
  cursor: default;
}

/* Separator — flush, recedes between items                    */
.breadcrumb-sep {
  color:          var(--text-muted);
  opacity:        0.4;
  font-size:      0.9em;
  user-select:    none;
  pointer-events: none;
}


/* ── Pagination ──────────────────────────────────────────────
   Page number navigation. Each page is a bump z+0.5 pill.
   Current page extrudes. Prev/Next are standard bump buttons.  */

.pagination {
  display:         flex;
  align-items:     center;
  justify-content: center;
  gap:             var(--wall-sm);
  padding:         var(--wall) 0;
}

.page-item {
  display:        flex;
  align-items:    center;
  justify-content: center;
  /* 34px square at Grande, 2px per reading step either side;
     min-width so a word ("Prev") widens it rather than spilling */
  min-width:      calc(32px + var(--r) * 2px);
  height:         calc(32px + var(--r) * 2px);
  padding:        0 0.6em;
  border-radius:  calc(8px + var(--r) * 0.5px);
  font-size:      0.6875rem;
  font-weight:    700;
  color:          var(--text-secondary);
  user-select:    none;
  background:     var(--surface);
  text-decoration: none;
  transition:
    box-shadow .18s cubic-bezier(.34, 1.05, .64, 1),
    transform  .18s cubic-bezier(.34, 1.05, .64, 1),
    color      .15s ease;

  /* Bump — small rise                                         */
  box-shadow:
    var(--rim-raised),
    calc(var(--ox) *  .3) calc(var(--oy) *  .3) 5px var(--shadow-light),
    calc(var(--ox) * -.3) calc(var(--oy) * -.3) 5px var(--shadow-dark);
}

.page-item:hover:not(.active):not(.disabled) {
  color:     var(--text-primary);
  transform: translateY(-1px);
  box-shadow:
    var(--rim-raised),
    calc(var(--ox) *  .5) calc(var(--oy) *  .5) 9px var(--shadow-light),
    calc(var(--ox) * -.5) calc(var(--oy) * -.5) 9px var(--shadow-dark);
}

/* Current page — extrudes, primary colour                     */
.page-item.active {
  color:       var(--accent);
  font-weight: 800;
  transform:   translateY(-2px);
  box-shadow:
    var(--rim-raised),
    calc(var(--ox) *  .65) calc(var(--oy) *  .65) 12px var(--shadow-light),
    calc(var(--ox) * -.65) calc(var(--oy) * -.65) 12px var(--shadow-dark);
}

/* Disabled (.disabled or aria-disabled) — debossed with inactive
   content, like every disabled control: see states.css          */

/* Wider prev/next — text label, not just a number             */
.page-item.page-prev,
.page-item.page-next {
  width:       auto;
  padding:     0 var(--wall-sm);
  gap:         4px;
  letter-spacing: 0.02em;
}


/* ── Reduced motion ──────────────────────────────────────────
   Active, current and hovered items still rise in depth and
   colour, as a quick fade; the lift itself lands at once. The
   step rail shows its new progress without sliding.           */

@media (prefers-reduced-motion: reduce) {
  .nav-bar-item,
  .page-item {
    transition:
      box-shadow 80ms ease,
      color      80ms ease;
  }

  .nb-tab {
    transition:
      box-shadow 80ms ease,
      color      80ms ease,
      background 80ms ease;
  }

  .step-node {
    transition: box-shadow 80ms ease;
  }

  .nav-bar-item::before,
  .breadcrumb-item {
    transition-duration: 80ms;
  }

  .step-rail::after {
    transition: none;
  }
}


/* ── css/cursor.css ── */
/* ══════════════════════════════════════════════════════════════
   SILICONE UI — CURSOR
   cursor.css · v1.0.0

   The Silicone cursor is a physical object above the mat.
   Not a pointer metaphor — a silicone form that:
   - Casts a large shadow when floating high
   - Compresses that shadow as it descends toward the surface
   - Fires a 135° NW glint when the tip makes contact
   - Emits a white LED ring around its outline on link hover

   Physics model:
   ┌─────────────────────────────────────────────────────────┐
   │ State      z-height   Shadow           LED              │
   │ ──────────────────────────────────────────────────────  │
   │ Resting    High       Large SE spread  None             │
   │ On link    Near mat   Compressed SE    White ring       │
   │ Pressing   At surface Near-gone        White + NW glint │
   │ Glint      Contact    Near-gone        Full flash       │
   │ On text    —          Hidden           System cursor    │
   └─────────────────────────────────────────────────────────┘

   The cursor never changes size. No zoom on hover.
   Only the shadow changes — depth is perceived through
   shadow physics alone, not scale.

   The NW gloss is on the body only, NOT the tip.
   The tip must stay dark so contact point is legible.
   The gradient runs: top-left (lit) → mid → tip (dark, --c1).

   clip-path defines the standard pointer shape.
   The shape is asymmetric — the hook creates a distinctive
   profile that reads as Silicone UI regardless of context.

   cursor.js manages state classes on <html>:
   .cursor-on-link   — cursor over interactive element
   .cursor-pressing  — mousedown held
   .cursor-glint     — 80ms after mousedown (flash)
   .cursor-on-text   — cursor over text input

   Depends on: tokens.css
   Lax Pairs Limited · silicone.studio · MIT Licence
   ══════════════════════════════════════════════════════════════ */


/* ── System cursor ───────────────────────────────────────────
   Hidden only on pages that include the Silicone cursor element;
   text fields keep the text cursor so the caret stays findable. */

html:has(#sil-cursor),
html:has(#sil-cursor) * {
  cursor: none;
}

html:has(#sil-cursor) :is(input, textarea, select, [contenteditable]):not(:disabled) {
  cursor: text;
}


/* ── Cursor element ──────────────────────────────────────────
   Fixed position, pointer-events: none — invisible to hit testing.
   z-index 9999 — above everything including compartments.
   Starts off-screen (-80px, -80px) — positioned by JS.
   will-change: left, top, filter — GPU compositing hint.      */

#sil-cursor {
  position:       fixed;
  pointer-events: none;
  z-index:        9999;
  top:  -80px;
  left: -80px;
  will-change: left, top, filter;

  /* Fixed size — no zoom, ever */
  width:  42px;
  height: 54px;

  /* Standard pointer shape — asymmetric hook */
  clip-path: polygon(
    0%   0%,
    0%  82%,
    26% 62%,
    42% 100%,
    56%  94%,
    40%  56%,
    70%  56%
  );

  /* Dark green body — NW gloss on face, tip stays dark.
     Gradient: top-left (lit) → mid → tip (--c1, dark).
     135° light source hits the broad face, not the tip.       */
  background: linear-gradient(
    148deg,
    hsl(162, 32%, 28%)  0%,
    hsl(162, 38%, 18%) 38%,
    var(--c1)          65%,
    var(--c1)         100%
  );

  /* Resting — floating HIGH above the mat.
     Large SE cast shadow = large z-height.
     Faint NW light-bounce = light reflecting off mat below.   */
  filter:
    drop-shadow(-1px  -2px  2px rgba(155, 253, 225, 0.20))
    drop-shadow( 5px   8px 14px rgba(1,   50,  36,  0.65))
    drop-shadow( 8px  12px 28px rgba(1,   50,  36,  0.28));

  opacity:    1;
  /* Only filter transitions — no size transitions ever */
  transition: filter .2s cubic-bezier(.34, 1.05, .64, 1);
}


/* ── On link — approaching surface ──────────────────────────
   Cursor descends toward the interactive element.
   Shadow COMPRESSES toward the tip — same size, less height.
   White LED ring blooms around the entire pointer outline.
   The ring signals: contact is imminent.                      */

.cursor-on-link #sil-cursor {
  filter:
    /* NW light-bounce — stays constant regardless of height */
    drop-shadow(-1px -2px  2px rgba(155, 253, 225, 0.20))
    /* SE shadow compresses — tip is near the mat */
    drop-shadow( 3px  5px  6px rgba(1,   50,  36,  0.80))
    drop-shadow( 4px  7px 12px rgba(1,   50,  36,  0.35))
    /* White LED ring — blooms around the whole pointer outline */
    drop-shadow( 0px  0px  4px rgba(255, 255, 255, 0.70))
    drop-shadow( 0px  0px 10px rgba(255, 255, 255, 0.35))
    drop-shadow( 0px  0px 18px rgba(255, 255, 255, 0.15));
}


/* ── Pressing — tip at surface ───────────────────────────────
   The tip has made contact with the mat.
   Shadow near-gone — cursor is at the surface.
   NW glint fires — 135° light source catches the pointer face
   at exactly the angle that produces a reflection.
   White LED still around outline — the circuit is complete.   */

.cursor-pressing #sil-cursor {
  filter:
    /* Glint — 135° NW light catches the broad face */
    drop-shadow(-3px -4px 5px rgba(205, 254, 240, 0.65))
    drop-shadow(-1px -2px 2px rgba(255, 255, 255, 0.45))
    /* Shadow near-gone — at the surface */
    drop-shadow( 1px  2px 4px rgba(1,   50,  36,  0.85))
    /* White LED ring still visible */
    drop-shadow( 0px  0px 6px rgba(255, 255, 255, 0.60))
    drop-shadow( 0px  0px 14px rgba(255, 255, 255, 0.25));
}


/* ── Glint flash — on click ──────────────────────────────────
   80ms flash on mousedown. The face catches full light briefly.
   Maximum NW glint + maximum white ring.
   cursor.js adds .cursor-glint for 80ms then .cursor-pressing.
   The sequence: glint (80ms) → pressing (held) → rest (up).   */

.cursor-glint #sil-cursor {
  filter:
    drop-shadow(-4px -5px  8px rgba(205, 254, 240, 0.90))
    drop-shadow(-2px -3px  4px rgba(255, 255, 255, 0.70))
    drop-shadow( 1px  2px  4px rgba(1,   50,  36,  0.70))
    drop-shadow( 0px  0px 12px rgba(255, 255, 255, 0.80))
    drop-shadow( 0px  0px 24px rgba(255, 255, 255, 0.40));
}


/* ── On text — restore system cursor ────────────────────────
   Inside text inputs, the Silicone cursor hides.
   System text cursor takes over for precision editing.
   cursor.js adds .cursor-on-text when over input/textarea.    */

.cursor-on-text #sil-cursor {
  opacity: 0;
}

/* Restore system cursor for all text elements */
.cursor-on-text,
.cursor-on-text * {
  cursor: text !important;
}


/* ── Tap ripple ──────────────────────────────────────────────
   Spawned at click coordinates by cursor.js.
   Defined here for co-location with cursor.
   Also in motion.css as the canonical @keyframes definition.
   Scales from 0 to 4× over 0.55s then removes itself.        */

.tap-ripple {
  position:       fixed;
  border-radius:  50%;
  pointer-events: none;
  z-index:        9997;
  transform:      translate(-50%, -50%) scale(0);
  animation:      ripple-out .55s cubic-bezier(0, .5, .5, 1) forwards;
}


/* ── Reduced motion ──────────────────────────────────────────
   The cursor's contact states switch with a quick fade. The
   ripple ends at once, unseen — its animationend still fires, so
   cursor.js removes it.                                       */

@media (prefers-reduced-motion: reduce) {
  #sil-cursor {
    transition-duration: 80ms;
  }

  .tap-ripple {
    animation-duration: 0.01ms;
  }
}


/* ── css/states.css ── */
/* ══════════════════════════════════════════════════════════════
   SILICONE UI — STATES · states.css
   Persistent interaction states. Last in the cascade so a state
   beats a component's resting style at equal specificity.
   ══════════════════════════════════════════════════════════════ */


/* ── Trench — pressed / committed ────────────────────────────
   A raised control held down into the sheet: the resting form of
   btn-flare's 40% keyframe. Set by .trench, or by a button
   reporting itself pressed. The :hover line keeps it held under
   the cursor's contact glow (.cursor-on-link .btn:hover).       */

.trench,
.btn[aria-pressed="true"],
.cursor-on-link :is(.trench, .btn[aria-pressed="true"]):hover {
  box-shadow:
    var(--rim-recess),
    inset calc(var(--ox) * -.45) calc(var(--oy) * -.45) 8px var(--shadow-dark),
    inset calc(var(--ox) *  .45) calc(var(--oy) *  .45) 8px var(--shadow-light);
}

/* Held-down surfaces don't catch the NW gloss */
.trench::before,
.btn[aria-pressed="true"]::before,
.chip[aria-pressed="true"]::before {
  opacity: 0;
}

/* A pressed chip takes the same shallow press as .chip.open,
   and its dot lights (chips.css)                                */
.chip[aria-pressed="true"] {
  color: var(--accent);
  box-shadow:
    var(--rim-recess),
    inset calc(var(--ox) * -.4) calc(var(--oy) * -.4) 6px var(--shadow-dark),
    inset calc(var(--ox) *  .4) calc(var(--oy) *  .4) 6px var(--shadow-light);
}

/* Toggles snap into the trench rather than flaring back out */
[aria-pressed].btn-flaring {
  animation: none;
}

/* ── Disabled — dropped into the sheet ───────────────────────
   A control that can't be used can't stand proud, because raised
   means interactive. It drops to debossed (the cavity recipe). Its
   words and icons dim to --text-disabled; its LED marks — dot,
   tick, toggle thumb — go unlit (--led-inactive). The unlit colour
   alone is ~1.2:1, too faint to read a label by. It loses its rim,
   which marks what you can operate. No gloss, no contact glow, no
   hover lift. Set by the
   native disabled attribute, or aria-disabled="true" where the
   element can't be natively disabled. Scripts skip it too: no
   cursor contact, proximity glow or flare (cursor.js,
   proximity.js, interactions.js).                               */

.btn:is(:disabled, [aria-disabled="true"]),
.cursor-on-link .btn:is(:disabled, [aria-disabled="true"]):hover,
.chip[aria-disabled="true"],
.pc[aria-disabled="true"],
.page-item:is(.disabled, [aria-disabled="true"]) {
  color:       var(--text-disabled);
  text-shadow: none;
  transform:   none;
  cursor:      default;
  box-shadow:
    inset calc(var(--ox) * -.5) calc(var(--oy) * -.5) 10px var(--shadow-dark),
    inset calc(var(--ox) *  .5) calc(var(--oy) *  .5) 10px var(--shadow-light);
}

.btn:is(:disabled, [aria-disabled="true"])::before,
.chip[aria-disabled="true"]::before {
  opacity: 0;
}

.chip[aria-disabled="true"] .cdot {
  background: var(--led-inactive);
  box-shadow: none;
}

/* Fields are already cavities; only their content dims        */
:is(.field-input, .field-textarea, .field-select):disabled,
:is(.field-input, .field-textarea, .field-select):disabled::placeholder {
  color: var(--text-disabled);
}

/* Fields and the range and toggle tracks keep their cavity and
   drop only the rim                                             */
:is(.field-input, .field-textarea, .field-select, .field-range):disabled,
.field-toggle-wrap input[type="checkbox"]:disabled ~ .field-toggle-track {
  --rim-recess: 0 0 #0000;
  --rim-raised: 0 0 #0000;
}

/* Checkbox and radio: a checked box stays down; tick, dot and
   label dim. Selectors mirror the :checked rules' weight.       */
.field-check-wrap input[type="checkbox"]:disabled ~ .field-check-box,
.field-radio-wrap input[type="radio"]:disabled ~ .field-radio-box {
  transform: none;
  box-shadow:
    inset calc(var(--ox) * -.45) calc(var(--oy) * -.45) 8px var(--shadow-dark),
    inset calc(var(--ox) *  .45) calc(var(--oy) *  .45) 8px var(--shadow-light);
}

.field-check-wrap input[type="checkbox"]:disabled ~ .field-check-box::after {
  border-color: var(--led-inactive);
}

.field-radio-wrap input[type="radio"]:disabled ~ .field-radio-box::after {
  background: var(--led-inactive);
  box-shadow: none;
}

.field-check-wrap  input[type="checkbox"]:disabled ~ .field-check-label,
.field-radio-wrap  input[type="radio"]:disabled    ~ .field-radio-label,
.field-toggle-wrap input[type="checkbox"]:disabled ~ .field-toggle-label {
  color: var(--text-disabled);
}

/* Toggle: the thumb stops standing proud but keeps its position,
   so the switch still says on or off                            */
.field-toggle-wrap input[type="checkbox"]:disabled ~ .field-toggle-track .field-toggle-thumb {
  background: var(--led-inactive);
  box-shadow: none;
}

/* Range: separate rules — one engine's unknown pseudo-element
   would void a shared selector list                             */
.field-range:disabled::-webkit-slider-thumb {
  background: var(--led-inactive);
  box-shadow: none;
}

.field-range:disabled::-moz-range-thumb {
  background: var(--led-inactive);
  box-shadow: none;
}

/* Dial: the ring keeps its cavity and drops its rim; the knob
   drops into the sheet; its LEDs and mark go unlit, and the mark
   keeps its angle, so the dial still says where it stands       */
.dial:has(> .dial-input:disabled) {
  --rim-recess: 0 0 #0000;
  cursor:       default;
}

.dial:has(> .dial-input:disabled) .dial-knob {
  box-shadow:
    inset calc(var(--ox) * -.5) calc(var(--oy) * -.5) 10px var(--shadow-dark),
    inset calc(var(--ox) *  .5) calc(var(--oy) *  .5) 10px var(--shadow-light);
}

.dial:has(> .dial-input:disabled) .dial-knob::before {
  opacity: 0;
}

.dial:has(> .dial-input:disabled) .dial-led {
  stroke: var(--led-inactive);
  filter: none;
}

.dial:has(> .dial-input:disabled) .dial-mark::after {
  background: var(--text-disabled);
}

/* ── Focus — a ring in the accent ────────────────────────────
   Keyboard focus shows as a 2px ring in --focus-ring (the accent:
   6.1:1 on light, 12:1 on dark), 3px outside the edge so it clears
   the soft shadows. :focus-visible, so a click doesn't ring a
   button; a text field rings whenever it has focus. Checkbox,
   radio and toggle inputs are hidden, so their ring goes on the
   box they draw, and a dial's on its ring. The page's own scroll root is left out: it takes
   focus so the keyboard scrolls (core.js), not as a control.     */

:is(a[href], button, input, select, textarea, summary, [data-interactive],
    [tabindex]:not([tabindex="-1"], [data-scroll-root])):focus-visible,
.field-check-wrap  input:focus-visible ~ .field-check-box,
.field-radio-wrap  input:focus-visible ~ .field-radio-box,
.field-toggle-wrap input:focus-visible ~ .field-toggle-track,
.dial:has(> .dial-input:focus-visible) {
  outline:        2px solid var(--focus-ring);
  outline-offset: 3px;
}

/* ── Forced colours ──────────────────────────────────────────
   A forced palette (Windows high contrast) drops every box-shadow,
   so depth and rims vanish. Controls draw a real edge in the
   system's colours instead; pressed and on states fill with the
   system highlight; focus rings in it.                          */

@media (forced-colors: active) {
  :is(.btn, .chip, .sc, .pc, .page-item, .nav-bar-item, .field-input, .field-textarea, .field-select,
      .field-check-box, .field-radio-box, .field-toggle-track, .field-toggle-thumb, .field-range,
      .dial, .dial-knob),
  :is(a[href], button):is(.card, .bump) {
    outline:        1px solid CanvasText;
    outline-offset: -1px;
  }

  :is(.btn, .chip, .pc, .page-item):is(:disabled, [aria-disabled="true"], .disabled) {
    outline-color: GrayText;
    color:         GrayText;
  }

  :is(.btn, .chip)[aria-pressed="true"],
  .chip.open,
  .field-toggle-wrap input[type="checkbox"]:checked ~ .field-toggle-track .field-toggle-thumb,
  .field-radio-box::after {
    forced-color-adjust: none;   /* which would bring the shadows back */
    background:          Highlight;
    color:               HighlightText;
    box-shadow:          none;
  }

  .field-range::-webkit-slider-thumb { border: 1px solid CanvasText; }
  .field-range::-moz-range-thumb     { border: 1px solid CanvasText; }

  /* Dial: the LEDs up to the value in the highlight (the text
     colour while what it drives waits), the rest grey           */
  .dial-track { stroke: GrayText; }
  .dial-led   { stroke: Highlight; filter: none; }
  .dial:has(> .dial-input[data-lit="false"]) .dial-led { stroke: CanvasText; }
  .dial .dial-mark::after,
  .dial:has(> .dial-input[data-lit="false"]) .dial-mark::after { forced-color-adjust: none; background: CanvasText; }
  .dial:has(> .dial-input:disabled) { outline-color: GrayText; }
  .dial:has(> .dial-input:disabled) .dial-knob { outline-color: GrayText; }
  .dial:has(> .dial-input:disabled) .dial-led { stroke: GrayText; }
  .dial:has(> .dial-input:disabled) .dial-mark::after { background: GrayText; }

  :is(a[href], button, input, select, textarea, summary, [data-interactive],
      [tabindex]:not([tabindex="-1"], [data-scroll-root])):focus-visible,
  .field-check-wrap  input:focus-visible ~ .field-check-box,
  .field-radio-wrap  input:focus-visible ~ .field-radio-box,
  .field-toggle-wrap input:focus-visible ~ .field-toggle-track,
  .dial:has(> .dial-input:focus-visible) {
    outline:        2px solid Highlight;
    outline-offset: 3px;
  }
}

/* Reduced motion: pressing in is the feedback, so the trench
   lands at once whatever the component's own transition.     */
@media (prefers-reduced-motion: reduce) {
  .trench,
  .btn[aria-pressed="true"] {
    transition: none;
  }
}
