/* ============================================================
   Magicite design system, ported from docs/brand-moodboard.html.
   Tokens · base · shell · components. Dark-first, full light.
   Box language since 2026-08-20: faceted cut corners + ruled
   ledgers, hue as identity only. Explorations: docs/box-lab.html.
   Magitek layer (same day): matte --metal carries structure
   (frames, rivets, sockets); only product hues may glow, barely.
   ============================================================ */

/* ---------- typefaces ---------- */
/* IBM Plex, self-hosted (latin subset, woff2, ~90KB total, font-display swap): the mono carries
   the site's most branded surfaces (unit plates, dot leaders, prices) and deserved a deliberate
   voice over the anonymous system stack. Sans ships as the variable font, one file for the whole
   weight axis, so the buttons' 650 renders as a true intermediate; Mono is static per weight
   because no variable cut exists. Weights are capped at Plex's real range: the old 800s say 700
   now (Plex tops out at 700, and closest-face matching rendered 700 anyway), enforced by
   font-synthesis-weight:none on body so a future 800 can never faux-bold. Glyphs outside latin
   (the arrows, the diamond) fall through to the system fallbacks exactly as before Plex. */
@font-face {
  font-family:'IBM Plex Sans'; font-style:normal; font-weight:100 700; font-display:swap;
  src:url('fonts/ibm-plex-sans-var.woff2') format('woff2');
  unicode-range:U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
@font-face {
  font-family:'IBM Plex Mono'; font-style:normal; font-weight:400; font-display:swap;
  src:url('fonts/ibm-plex-mono-400.woff2') format('woff2');
  unicode-range:U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
@font-face {
  font-family:'IBM Plex Mono'; font-style:normal; font-weight:500; font-display:swap;
  src:url('fonts/ibm-plex-mono-500.woff2') format('woff2');
  unicode-range:U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
@font-face {
  font-family:'IBM Plex Mono'; font-style:normal; font-weight:700; font-display:swap;
  src:url('fonts/ibm-plex-mono-700.woff2') format('woff2');
  unicode-range:U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}

:root {
  /* Magitek pass: the darks sit a step warmer (less blue, toward gunmetal), and --metal is the
     matte machine accent that carries structure. Metal never glows; only product hues may. */
  --ground:#0B0D12;
  --surface:#12141C;
  --surface-2:#191C26;
  --line:#2a2d3a;
  --line-soft:#20222d;
  --metal:#8a7f66;
  --ink:#EAEDF5;
  --ink-dim:#9AA3B8;
  /* WCAG 2.2 AA: --ink-faint carries small text (eyebrows, meta, field labels), so it must clear
     4.5:1 against ground, surface AND surface-2. The former #5f6982 was 3.54/3.35 and failed. */
  --ink-faint:#7c87a3;
  --cyan:#1FC6EE;
  --emerald:#3AD36B;
  --violet:#9E6BFF;
  --amber:#F5B24A;
  --rose:#F45C9C;
  --lime:#AEDB3C;
  --sapphire:#4C7DFF;
  --gold:#E4B93D;
  --cyan-ink:#63e0f7;
  --emerald-ink:#68e79a;
  --violet-ink:#b79bff;
  --amber-ink:#f8c87a;
  --rose-ink:#ff92c0;
  --lime-ink:#cfe86a;
  --sapphire-ink:#8fb0ff;
  --gold-ink:#f2d06b;
  --glow:.6;
  /* The wordmark's passing light (.wordmark): the band's edge and core. Themed, and themed for
     LUMINANCE rather than hue. The first version put #1FC6EE on near-white ink: a hue change at
     1.7:1 that was obvious in a still frame and invisible at 1.6s, which is exactly how it got
     reported. A band that only changes hue does not read as motion, so dark mode now dips to the
     gem's cyan rim and flashes its highlight (GEM.cyan b and g in magicite.js), a 3.0:1 excursion.
     Light mode was already fine: near-black ink rising to pale teal at 7.4:1. Guarded by
     ColorContrastTests.Wordmark_glint_makes_a_visible_change_against_the_ink. */
  --glint:#0E93B8; --glint-core:#8fe8fb;
  /* Focus ring, themed separately from --cyan: the brand cyan is only 1.79:1 on the light
     ground and fails 1.4.11 non-text contrast. Dark keeps cyan (9.58:1). */
  --focus:#1FC6EE;
  --maxw:1140px;
  --font-sans: 'IBM Plex Sans', system-ui, -apple-system, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
  --font-mono: 'IBM Plex Mono', ui-monospace, "Cascadia Code", "Cascadia Mono", "SF Mono", Consolas, "Liberation Mono", Menlo, monospace;

  /* Type scale. The moodboard specimen was never tokenized, so sizes were hardcoded literals
     throughout. These names match the sizes already in use rather than inventing a new ramp. */
  --text-xs:.7rem;
  /* Not a ramp step: .72rem is a distinct size the shipped design uses for tracked micro-labels
     (the eyebrow, a shard's role, a panel count, a per-unit suffix), and it appeared as a literal
     five times. Rounding it to --text-xs would change what renders, so it gets its own name. */
  --text-eyebrow:.72rem;
  --text-sm:.78rem;
  --text-ms:.85rem;
  --text-base:.95rem;
  --text-md:1.02rem;
  --text-lg:1.14rem;
  --text-xl:clamp(1.3rem,2.4vw,1.6rem);
  --text-2xl:clamp(1.6rem,3.2vw,2.1rem);
  --text-3xl:clamp(2rem,4.2vw,2.8rem);
  --text-display:clamp(2.2rem,5.5vw,3.6rem);

  /* Space scale. Every gap and pad was previously a literal, with the same magic numbers
     repeated across the sheet. */
  --space-1:4px;
  --space-2:8px;
  --space-3:12px;
  --space-4:16px;
  --space-5:22px;
  --space-6:28px;
  --space-7:40px;
  --space-8:56px;

  color-scheme: dark;
}
@media (prefers-color-scheme: light) {
  :root {
    --ground:#EEF1F8; --surface:#FFFFFF; --surface-2:#F5F8FD;
    --line:#D6DCEC; --line-soft:#E4E9F4;
    --metal:#7d7358;
    --ink:#121725; --ink-dim:#4c5468; --ink-faint:#5d6478;
    --cyan-ink:#0b6f7c; --emerald-ink:#0d7a3c; --violet-ink:#5a34c2; --amber-ink:#8a5a0e; --rose-ink:#b0356b; --lime-ink:#5e7a10; --sapphire-ink:#2b46c8; --gold-ink:#8a6410;
    --glow:.4; --focus:#0b6f7c; color-scheme: light;
    --glint:#0b6f7c; --glint-core:#79b0b7;
  }
}
:root[data-theme="dark"] {
  --ground:#0B0D12; --surface:#12141C; --surface-2:#191C26;
  --line:#2a2d3a; --line-soft:#20222d;
  --metal:#8a7f66;
  --ink:#EAEDF5; --ink-dim:#9AA3B8; --ink-faint:#7c87a3;
  --cyan-ink:#63e0f7; --emerald-ink:#68e79a; --violet-ink:#b79bff; --amber-ink:#f8c87a; --rose-ink:#ff92c0; --lime-ink:#cfe86a; --sapphire-ink:#8fb0ff; --gold-ink:#f2d06b;
  --glow:.6; --focus:#1FC6EE; color-scheme: dark;
  --glint:#0E93B8; --glint-core:#8fe8fb;
}
:root[data-theme="light"] {
  --ground:#EEF1F8; --surface:#FFFFFF; --surface-2:#F5F8FD;
  --line:#D6DCEC; --line-soft:#E4E9F4;
  --metal:#7d7358;
  --ink:#121725; --ink-dim:#4c5468; --ink-faint:#5d6478;
  --cyan-ink:#0b6f7c; --emerald-ink:#0d7a3c; --violet-ink:#5a34c2; --amber-ink:#8a5a0e; --rose-ink:#b0356b; --lime-ink:#5e7a10; --sapphire-ink:#2b46c8; --gold-ink:#8a6410;
  --glow:.4; --focus:#0b6f7c; color-scheme: light;
  --glint:#0b6f7c; --glint-core:#79b0b7;
}

/* ---------- reset ---------- */
*, *::before, *::after { box-sizing:border-box; }
html { -webkit-text-size-adjust:100%; }
body {
  margin:0; background:var(--ground); color:var(--ink);
  font-family:var(--font-sans); line-height:1.6; -webkit-font-smoothing:antialiased;
  font-synthesis-weight:none;
  overflow-x:hidden;
}
h1,h2,h3,h4 { margin:0; font-weight:700; letter-spacing:-.02em; line-height:1.1; text-wrap:balance; }
p { margin:0; }
a { color:inherit; text-decoration:none; }
img,svg { max-width:100%; }
:focus-visible { outline:2px solid var(--focus); outline-offset:3px; }
/* Suppress the ring for programmatic/mouse focus (e.g. FocusOnNavigate focusing the h1). Keyboard users keep :focus-visible. */
:focus:not(:focus-visible) { outline:none; }
[tabindex="-1"]:focus { outline:none; }

/* ---------- background lattice + stacking ---------- */
#lattice { position:fixed; inset:0; width:100%; height:100%; z-index:0; pointer-events:none; }
.site-header, main, .site-footer { position:relative; z-index:1; }

/* ---------- layout ---------- */
.container { max-width:var(--maxw); margin-inline:auto; padding-inline:clamp(20px,5vw,44px); }
.section { padding-block:clamp(56px,9vw,104px); }
main { min-height:60vh; }

/* ---------- utility type ---------- */
.eyebrow { font-family:var(--font-mono); font-size:var(--text-eyebrow); letter-spacing:.22em; text-transform:uppercase; color:var(--ink-faint); margin:0; }
.mono { font-family:var(--font-mono); }
.muted { color:var(--ink-dim); }
.lede { color:var(--ink-dim); font-size:1.05rem; max-width:60ch; }

/* ---------- header / nav ---------- */
.site-header {
  position:sticky; top:0; z-index:20;
  /* Opaque on purpose: the frosted-glass blur went out with the box redesign. */
  background:var(--ground);
  border-bottom:1px solid var(--line-soft);
}
.header-inner { display:flex; align-items:center; gap:24px; height:62px; }
.brand { display:inline-flex; align-items:center; gap:10px; font-weight:700; letter-spacing:.14em; text-transform:uppercase; font-size:.92rem; }
.brand .facet { width:18px; height:18px; color:var(--cyan-ink); }
.site-nav { display:flex; gap:24px; margin-left:var(--space-2); }
/* WCAG 2.2 AA 2.5.8 Target Size (Minimum): as plain inline links these were only as tall as their
   text (~17px) against a 24px floor. inline-flex plus min-height enlarges the hit area without
   moving anything: the 62px header already centres its children, so the text stays put. */
.site-nav a {
  display:inline-flex; align-items:center; min-height:44px;
  font-size:.92rem; color:var(--ink-dim); transition:color .18s;
}
.site-nav a:hover { color:var(--ink); }
.header-actions { margin-left:auto; display:flex; align-items:center; gap:var(--space-3); }
.account-name { color:var(--ink); font-weight:600; font-size:.9rem; }
.account-name:hover { color:var(--cyan-ink); }
.theme-toggle {
  font-size:1rem; line-height:1; width:36px; height:36px;
  color:var(--ink-dim); background:var(--surface); border:1px solid var(--line); cursor:pointer;
  display:grid; place-items:center; transition:color .18s, border-color .18s;
}
.theme-toggle:hover { color:var(--ink); border-color:var(--ink-faint); }

/* ---------- buttons ---------- */
/* Faceted, not pill. Only the primary owns the double-cut corners; the ghost stays a square
   hairline so one weight of button carries the shape. The primary's fill lives on ::before
   because clip-path on the element itself would clip the :focus-visible ring away with it.
   Hover recolors; nothing lifts, nothing glows. */
.btn {
  font:inherit; font-weight:650; font-size:.94rem; cursor:pointer;
  display:inline-flex; align-items:center; gap:9px;
  padding:var(--space-3) var(--space-5); border:1px solid transparent;
  transition:border-color .18s, background .18s, color .18s;
}
.btn-sm { padding:9px var(--space-4); font-size:var(--text-ms); }
.btn-primary { position:relative; isolation:isolate; background:transparent; color:var(--ground); }
.btn-primary::before {
  content:""; position:absolute; inset:0; z-index:-1; background:var(--ink); transition:background .18s;
  clip-path:polygon(9px 0, 100% 0, 100% calc(100% - 9px), calc(100% - 9px) 100%, 0 100%, 0 9px);
}
.btn-primary:hover::before { background:var(--cyan-ink); }
.btn-ghost { background:transparent; color:var(--ink); border-color:var(--line); }
.btn-ghost:hover { border-color:var(--ink-faint); background:var(--surface); }
.ms-glyph { display:inline-grid; grid-template-columns:1fr 1fr; gap:2px; width:14px; height:14px; }
.ms-glyph i { display:block; }
.ms-glyph i:nth-child(1){ background:#F25022; } .ms-glyph i:nth-child(2){ background:#7FBA00; }
.ms-glyph i:nth-child(3){ background:#00A4EF; } .ms-glyph i:nth-child(4){ background:#FFB900; }

/* ---------- spectrum seam ---------- */
.seam { height:2px; border:0; margin:0; background:linear-gradient(90deg, var(--cyan), var(--emerald) 34%, var(--violet) 66%, var(--amber)); opacity:.82; }

/* ---------- hero ---------- */
.hero { position:relative; overflow:hidden; border-bottom:1px solid var(--line-soft); }
/* Only the home hero (which has the ambient orbs + gem) gets an opaque ground; other heroes let the crystalline lattice show through. */
.hero:has(#ambient) { background:radial-gradient(120% 130% at 50% 0%, var(--surface) 0%, var(--ground) 58%); }
.hero-title { font-size:var(--text-display); font-weight:700; letter-spacing:-.02em; line-height:1.05; }
.hero-inner { display:flex; flex-direction:column; gap:26px; padding-block:clamp(64px,12vw,120px); max-width:52ch; }
/* The wordmark catches the light. Solid ink at rest: the animated gradient shimmer (a cyan-to-
   violet band on a 6.5s loop, the generator's wordmark) went out with the box redesign. What
   came back on 2026-08-29 is the gem's light, not a loop: magicite.js adds .lit for each
   conduit pulse after the first (~9s apart; the load-time pulse does not count, so the letters
   sit still for their first nine seconds), and one narrow cold highlight, brand cyan,
   crosses the letters once from the gem's side; then the ink is solid again. The band is
   --glint / --glint-core, themed: with --cyan-ink the dark-mode band was a pale tint on near-white
   letters (1.08:1, invisible at speed) while light mode read at 7:1, which is how Nathan spotted
   it. Built as a
   gradient clipped to the glyphs, with the fill made transparent through
   -webkit-text-fill-color rather than color, so the computed color stays --ink for contrast
   tooling and for browsers without background-clip:text, which keep solid ink. The image is
   three widths wide with the band in its middle, so background-position 0 parks the band past
   the right edge and 100% past the left: both resting frames are pixel-identical to solid ink.
   Under reduced motion the pulse block never runs and the animation blanket disables the pass
   regardless. */
.wordmark { font-weight:700; letter-spacing:.02em; line-height:.95; font-size:clamp(3rem,9vw,5.6rem); }
@supports ((-webkit-background-clip:text) or (background-clip:text)) {
  .wordmark {
    color:var(--ink);
    background:linear-gradient(104deg,
        var(--ink) 0 45.5%,
        var(--glint) 48%,
        var(--glint-core) 50%,
        var(--glint) 52%,
        var(--ink) 54.5% 100%)
      0 0 / 300% 100% no-repeat;
    -webkit-background-clip:text; background-clip:text; -webkit-text-fill-color:transparent;
  }
  .wordmark.lit { animation:wordmark-pass 1.6s cubic-bezier(.6,0,.3,1) .45s 1; }
  @keyframes wordmark-pass { from { background-position:0 0; } to { background-position:100% 0; } }
}
.hero .tagline { font-size:clamp(1.1rem,2.2vw,1.4rem); font-weight:600; letter-spacing:-.01em; max-width:28ch; }
.hero .subline { color:var(--ink-dim); max-width:46ch; }
.cta-row { display:flex; flex-wrap:wrap; gap:var(--space-3); margin-top:var(--space-1); }

/* hero crystal (home) */
#ambient { position:absolute; inset:0; width:100%; height:100%; display:block; z-index:0; }
.hero-grid { position:relative; z-index:2; display:grid; grid-template-columns:1.15fr .85fr; gap:var(--space-7); align-items:center; padding-block:clamp(64px,11vw,120px); }
.hero-copy { display:flex; flex-direction:column; gap:26px; }
.gem-stage { position:relative; display:grid; place-items:center; min-height:320px; }
#gem { width:min(100%,380px); height:min(100%,380px); display:block; }
.gem-cap { position:absolute; bottom:-2px; left:50%; transform:translateX(-50%); white-space:nowrap; }

/* ---------- section heads ---------- */
/* Neutral since the box redesign: hue means product identity, and a section number is not an
   identity, so the per-section --sh rainbow (and its gradient underline bar) is gone. */
.section-head { display:flex; flex-direction:column; gap:14px; margin-bottom:44px; max-width:64ch; }
.section-head .index { color:var(--ink-dim); font-weight:700; }
.section-head .eyebrow::before { content:"\25C6"; color:var(--metal); margin-right:11px; font-size:.82em; }

/* ---------- faceted surfaces: the cut ---------- */
/* The card language since the box redesign: one chamfered corner, and the 1px border IS the hue
   carrier, a gradient pooling at the cut and fading into the neutral line. Layered on pseudos,
   not clip-path on the element itself, because clip-path clips everything the element paints,
   including the :focus-visible ring on link cards. ::before is the edge, ::after the surface
   fill plus hairline facet lines fading out from the cut. Hue arrives as --h/--h-ink inline and
   means identity (a product) or nothing: cards with no product behind them pass no hue and fall
   back to the neutral line. */
.cut { --notch:18px; --hatch-ink:color-mix(in srgb, var(--h, var(--metal)) 30%, transparent); position:relative; }
.cut::before {
  content:""; position:absolute; inset:0; z-index:0;
  clip-path:polygon(0 0, calc(100% - var(--notch)) 0, 100% var(--notch), 100% 100%, 0 100%);
  background:linear-gradient(225deg, color-mix(in srgb, var(--h, var(--metal)) 60%, var(--line)), var(--line) 46%);
}
.cut::after {
  content:""; position:absolute; inset:1px; z-index:0;
  clip-path:polygon(0 0, calc(100% - var(--notch)) 0, 100% var(--notch), 100% 100%, 0 100%);
  /* Layers, top to bottom: the machined seam (one matte metal line parallel to the cut, the
     plate's bevel, always metal and never charged), the fade that settles the hatching into the
     surface, the facet hatching itself (--hatch-ink, brightens on link-card hover), the fill. */
  background:
    linear-gradient(225deg,
      transparent calc(var(--notch) * .71 + 7px),
      color-mix(in srgb, var(--metal) 65%, transparent) calc(var(--notch) * .71 + 7px),
      color-mix(in srgb, var(--metal) 65%, transparent) calc(var(--notch) * .71 + 8px),
      transparent calc(var(--notch) * .71 + 8px))
      top right / calc(var(--notch) + 16px) calc(var(--notch) + 16px) no-repeat,
    linear-gradient(225deg, transparent, var(--surface) 36%),
    repeating-linear-gradient(135deg, transparent 0 11px, var(--hatch-ink) 11px 12px) top right / 96px 96px no-repeat,
    var(--surface);
}
.cut > * { position:relative; z-index:1; }
/* Hover snaps the whole edge to the hue and charges the facet hatching. Nothing lifts. */
a.cut:hover::before { background:linear-gradient(225deg, var(--h, var(--metal)), color-mix(in srgb, var(--h, var(--metal)) 22%, var(--line)) 72%); }
a.cut:hover { --hatch-ink:color-mix(in srgb, var(--h, var(--metal)) 55%, transparent); }

/* ---------- product shards ---------- */
.shard-grid { display:grid; grid-template-columns:repeat(2,1fr); gap:var(--space-5); }
/* Three-card grids (the home trio) go three-up: in the default two columns the third card
   orphans beside an empty slot. Collapses to one column with the rest at 820px. */
.shard-grid.trio { grid-template-columns:repeat(3,1fr); }
.shard-card { padding:27px 30px; display:flex; flex-direction:column; gap:var(--space-4); }
.shard-card .role { font-family:var(--font-mono); font-size:var(--text-eyebrow); letter-spacing:.14em; text-transform:uppercase; color:var(--h-ink, var(--ink-faint)); }
.shard-card .name { font-size:1.5rem; font-weight:700; letter-spacing:-.015em; }
/* Hued shard cards render the socketed identity diamond (see ShardCard); its color is the
   card's own hue, so no inline color is needed. */
.shard-card .mark { color:var(--h); }
.shard-card .desc { color:var(--ink-dim); max-width:42ch; }
.shard-card .spec { margin-top:auto; display:flex; gap:var(--space-2); flex-wrap:wrap; }
.tag { font-family:var(--font-mono); font-size:var(--text-xs); letter-spacing:.04em; color:var(--ink-dim);
  border:1px solid var(--line); border-left:2px solid var(--h, var(--metal)); padding:4px 10px; background:var(--surface-2); }

/* ---------- the ledger: products as one ruled block ---------- */
/* Product collections fuse into a single table: cells share single hairlines (a 1px grid gap
   over a line-colored background), corners square, index and F&O area in the top rule, role and
   price as dot-leader meta lines. Hue appears only as the identity diamond by the name. */
.ledger { display:grid; grid-template-columns:repeat(3,1fr); gap:1px; background:var(--line); border:1px solid color-mix(in srgb, var(--metal) 55%, var(--line)); }

/* Rivets: the fused plates (ledger, stat band, price list) carry a matte rivet at each frame
   corner. Drawn on an absolutely positioned overlay pseudo so the grid or flex layout never
   sees an extra item, and z-indexed over the positioned price rows. */
.ledger, .stat-band, .price-list { position:relative; }
.ledger::after, .stat-band::after, .price-list::after {
  content:""; position:absolute; inset:-2px; z-index:1; pointer-events:none;
  background-image:
    radial-gradient(circle, var(--metal) 1.5px, transparent 1.6px),
    radial-gradient(circle, var(--metal) 1.5px, transparent 1.6px),
    radial-gradient(circle, var(--metal) 1.5px, transparent 1.6px),
    radial-gradient(circle, var(--metal) 1.5px, transparent 1.6px);
  background-size:5px 5px; background-repeat:no-repeat;
  background-position:0 0, 100% 0, 0 100%, 100% 100%;
}
.ledger-cell { background:var(--surface); padding:24px 26px; display:flex; flex-direction:column; gap:11px; transition:background .15s; }
a.ledger-cell:hover { background:var(--surface-2); }
/* The machine's own lettering. The MT index is a stamped unit plate: a metal frame with the
   system's chamfer on the top-right corner (the .cut notch at chip scale, the same two-layer
   build so the frame follows the cut), a rivet at the left, and letters debossed into it (a
   1px dark edge above and a 1px light edge below; one rule reads as an engraved groove on both
   themes because each theme only shows the half that contrasts with its plate). The F&O area
   is an etched caption ranged right against its rule, framed by two registration ticks (the
   blueprint-tick idea from the lab). Both are lettered in brass ink, --ink-dim pulled toward
   --metal: pure --metal sits at 4.65:1 on the dark cell and 4.30:1 on its hover surface, so
   the mix is pinned by ColorContrastTests.Ledger_plate_ink_meets_AA_on_both_ledger_surfaces.
   Metal stays matte: hover only firms the frame, nothing glows. The index never breaks (an
   "MT-" / "07" split read as a defect at 860px once Earmark brought the longest area label);
   a long area wraps instead. */
.ledger-top { --plate-ink:color-mix(in srgb, var(--ink-dim) 55%, var(--metal)); --plate-line:color-mix(in srgb, var(--metal) 72%, transparent);
  display:flex; align-items:baseline; justify-content:space-between; gap:var(--space-3); }
.ledger-ix { --notch:6px; position:relative; isolation:isolate; flex:none; white-space:nowrap; padding:3px 8px 2px 16px;
  font-family:var(--font-mono); font-size:var(--text-eyebrow); font-weight:500; letter-spacing:.14em; color:var(--plate-ink);
  text-shadow:0 -1px 0 rgba(0,0,0,.35), 0 1px 0 rgba(255,255,255,.12); }
.ledger-ix::before, .ledger-ix::after { content:""; position:absolute; z-index:-1; pointer-events:none;
  clip-path:polygon(0 0, calc(100% - var(--notch)) 0, 100% var(--notch), 100% 100%, 0 100%); }
.ledger-ix::before { inset:0; background:var(--plate-line); }
.ledger-ix::after { inset:1px; background:
    radial-gradient(circle at 6px 50%, var(--metal) 1.2px, transparent 1.7px),
    color-mix(in srgb, var(--metal) 9%, var(--surface)); }
.ledger-area { position:relative; padding:3px 7px; font-family:var(--font-mono); font-size:.68rem; letter-spacing:.14em; text-transform:uppercase;
  color:var(--plate-ink); text-align:right; text-shadow:0 -1px 0 rgba(0,0,0,.35), 0 1px 0 rgba(255,255,255,.12); }
.ledger-area::before, .ledger-area::after { content:""; position:absolute; width:6px; height:6px; pointer-events:none; }
.ledger-area::before { top:0; left:0; border-top:1px solid var(--plate-line); border-left:1px solid var(--plate-line); }
.ledger-area::after { bottom:0; right:0; border-bottom:1px solid var(--plate-line); border-right:1px solid var(--plate-line); }
a.ledger-cell:hover .ledger-top { --plate-line:var(--metal); }
.ledger-cell .name { font-size:1.25rem; font-weight:700; letter-spacing:-.015em; }
.ledger-cell .desc { color:var(--ink-dim); font-size:var(--text-base); }
/* The shard in its socket: the identity diamond sits inside a matte metal housing ring, and
   hovering the cell charges it, the only glow anywhere in the UI. */
.mark { display:inline-grid; place-items:center; width:1.7em; height:1.7em; font-size:.62em;
  position:relative; top:-.1em; margin-right:9px;
  border:1px solid color-mix(in srgb, var(--metal) 80%, transparent); border-radius:50%; }
a.ledger-cell:hover .mark, a.shard-card:hover .mark { text-shadow:0 0 5px; }
.ledger-meta { margin-top:auto; display:flex; flex-direction:column; gap:8px; padding-top:var(--space-2); }
.dl { display:flex; align-items:baseline; gap:9px; font-family:var(--font-mono); font-size:var(--text-eyebrow); color:var(--ink-faint); }
.dl .lead { flex:1; min-width:20px; border-bottom:1px dotted color-mix(in srgb, var(--ink-faint) 55%, transparent); transform:translateY(-4px); }
.dl b { font-weight:500; color:var(--ink-dim); }
/* The framework takes the bottom row. Seven products in three columns leave one plate alone in
   its row with two-thirds of the row showing bare hairline ground, which reads as unfinished
   (seven is prime, so no column count fixes it). Whenever the rows come out uneven, Lattice,
   the framework the other add-ons plug into, is placed last and takes whatever the last row
   leaves: the whole row when the count is 3n+1, two cells when it is 3n+2. It keeps its MT-01
   plate and its first place in the DOM, so the numbering, the JSON-LD positions and the
   screen-reader order all stay the catalog order; only the grid placement moves, which is why
   this is CSS order rather than a reordered loop (below 820px the ledger is one column, rows
   cannot be uneven, and Lattice stays first). The trade-off is that keyboard focus leaves the
   wide plate for the top-left cell, which the visible numbering explains. The catalog puts the
   framework first, so :first-child is the framework.
   The wide plate reflows as one ruled line: index and area stacked at the left, name and
   summary beside them at a readable measure, and from 1000px the full-width plate moves the
   meta lines out to a right-hand column the width of a normal cell. */
@media (min-width:821px){
  .ledger > .ledger-cell:first-child:is(:nth-last-child(3n+1), :nth-last-child(3n+2)) {
    order:1; display:grid; grid-template-columns:minmax(150px,.4fr) minmax(0,1.6fr);
    grid-template-areas:"top name" "top desc" "top meta"; column-gap:32px; row-gap:11px; align-content:start;
  }
  .ledger > .ledger-cell:first-child:nth-last-child(3n+1) { grid-column:1 / -1; }
  .ledger > .ledger-cell:first-child:nth-last-child(3n+2) { grid-column:span 2; }
  .ledger > .ledger-cell:first-child:is(:nth-last-child(3n+1), :nth-last-child(3n+2)) .ledger-top { grid-area:top; flex-direction:column; align-items:flex-start; justify-content:flex-start; gap:6px; }
  .ledger > .ledger-cell:first-child:is(:nth-last-child(3n+1), :nth-last-child(3n+2)) .name { grid-area:name; }
  .ledger > .ledger-cell:first-child:is(:nth-last-child(3n+1), :nth-last-child(3n+2)) .desc { grid-area:desc; max-width:52ch; }
  .ledger > .ledger-cell:first-child:is(:nth-last-child(3n+1), :nth-last-child(3n+2)) .ledger-area { text-align:left; }
  .ledger > .ledger-cell:first-child:is(:nth-last-child(3n+1), :nth-last-child(3n+2)) .ledger-meta { grid-area:meta; margin-top:0; padding-top:6px; align-self:start; max-width:52ch; }
}
@media (min-width:1000px){
  .ledger > .ledger-cell:first-child:nth-last-child(3n+1) {
    grid-template-columns:minmax(200px,.5fr) minmax(0,1.5fr) 300px;
    grid-template-areas:"top name meta" "top desc meta";
  }
  .ledger > .ledger-cell:first-child:nth-last-child(3n+1) .ledger-meta { max-width:none; }
}

/* ---------- pricing teaser ---------- */
/* Wears .cut in markup, with a deeper notch. Cyan here is the brand accent (links, focus), not
   a product identity; the bottom halo went out with the box redesign. */
.price-card { --h:var(--cyan); --h-ink:var(--cyan-ink); --notch:26px;
  padding:clamp(28px,4vw,44px); display:flex; flex-wrap:wrap; gap:var(--space-6);
  align-items:center; justify-content:space-between; }
.price-fig { display:flex; align-items:flex-start; gap:var(--space-1); }
.price-fig .cur { font-family:var(--font-mono); font-size:1.4rem; color:var(--ink-dim); margin-top:var(--space-2); }
.price-fig .num { font-family:var(--font-mono); font-weight:700; font-size:clamp(2.6rem,6vw,3.8rem); line-height:1; letter-spacing:-.02em; font-variant-numeric:tabular-nums; }
.price-fig .per { font-family:var(--font-mono); font-size:var(--text-sm); color:var(--ink-dim); align-self:flex-end; margin-bottom:var(--space-2); line-height:1.4; }
.price-copy { display:flex; flex-direction:column; gap:var(--space-2); max-width:40ch; }
.price-copy .big { font-weight:700; font-size:1.1rem; letter-spacing:-.01em; }

/* ---------- footer ---------- */
.site-footer { border-top:1px solid var(--line-soft); margin-top:var(--space-7); }
.footer-inner { display:flex; flex-wrap:wrap; gap:var(--space-3) 24px; align-items:center; justify-content:space-between; padding-block:34px; }
.brand-sm { font-weight:700; letter-spacing:.14em; text-transform:uppercase; font-size:var(--text-ms); }
.footer-inner .mono { font-size:var(--text-sm); color:var(--ink-faint); }
.footer-links { display:flex; flex-wrap:wrap; gap:var(--space-2) 18px; }
.footer-links a { color:var(--ink-dim); font-size:.86rem; transition:color .18s; }
.footer-links a:hover { color:var(--ink); }

/* ---------- trust band ---------- */
/* A centered mono claim over the fused stat strip. The logo wall that lived here died with the
   box redesign: its tiles showed Microsoft sample companies, which read as fabricated social
   proof (see the comment in Home.razor). */
.trust-band { padding-block:clamp(28px,5vw,52px); }
.trust-eyebrow { text-align:center; margin-bottom:var(--space-5); }

/* ---------- about page ---------- */
.about-tail { margin-top:30px; max-width:70ch; }
.area-grid { display:flex; flex-wrap:wrap; gap:var(--space-3); }
/* Squared since the box redesign; the dot is a facet (rotated square), hover recolors only.
   These keep their product hue: each chip IS a product, so the hue is identity. */
.area-chip {
  display:inline-flex; align-items:center; gap:10px; padding:11px 16px 11px 14px;
  border:1px solid color-mix(in srgb,var(--h) 38%, var(--line));
  background:color-mix(in srgb,var(--h) 8%, var(--surface));
  transition:border-color .18s, background .18s;
}
.area-chip:hover { border-color:var(--h); background:color-mix(in srgb,var(--h) 14%, var(--surface)); }
/* The dot is a shard in a diamond socket (the outline rotates with it); hover charges it. */
.area-chip .dot { width:8px; height:8px; transform:rotate(45deg); background:var(--h);
  outline:1px solid color-mix(in srgb, var(--metal) 75%, transparent); outline-offset:2px; }
.area-chip:hover .dot { box-shadow:0 0 6px var(--h); }
.area-chip .a-area { color:var(--ink-dim); font-size:.86rem; }
.area-chip .a-name { color:var(--h-ink); font-weight:700; font-size:.94rem; }
.area-chip .a-go { color:var(--h-ink); opacity:.7; margin-left:2px; }

/* Fused like the ledger: one ruled strip, shared hairlines, neutral figures. */
.stat-band { display:grid; grid-template-columns:repeat(auto-fit,minmax(184px,1fr)); gap:1px; background:var(--line); border:1px solid color-mix(in srgb, var(--metal) 55%, var(--line)); }
.stat {
  display:flex; flex-direction:column; gap:9px;
  padding:26px 26px 24px; background:var(--surface);
}
.stat-num { font-family:var(--font-mono); font-weight:700; font-size:clamp(1.9rem,4.2vw,2.7rem); line-height:1; letter-spacing:-.02em; color:var(--ink); font-variant-numeric:tabular-nums; }
.stat-label { color:var(--ink-dim); font-size:.9rem; max-width:26ch; }

/* Wears .cut in markup, deepest notch on the site. Brand cyan, no radial glow. */
.callout { --h:var(--cyan); --h-ink:var(--cyan-ink); --notch:32px; text-align:center; padding:clamp(34px,5vw,60px); }
.callout h2 { font-size:clamp(1.6rem,3.6vw,2.3rem); }
.callout .lede { margin:var(--space-4) auto 26px; max-width:52ch; }

/* ---------- hero spell FX (secondary marketing pages) ---------- */
.hero:has(.hero-fx) { background:radial-gradient(120% 130% at 85% 12%, color-mix(in srgb, var(--h,var(--cyan)) 13%, var(--surface)) 0%, var(--ground) 62%); }
.hero-fx { position:absolute; inset:0; width:100%; height:100%; z-index:0; pointer-events:none; }
.hero-inner { position:relative; z-index:1; }

/* The rig stage: the apparatus that fills the right half of a section hero. Until now those heroes
   ran a 52ch copy column down the container's left rail and left the rest of the width empty, while
   the home page had the crystal to balance it. The copy keeps its measure and its place on the rail;
   the rig takes the space beside it. Each page passes its own machine and hue through data-rig and
   data-hue on the canvas (see the hero rig section of magicite.js). */
.hero-split { display:grid; grid-template-columns:minmax(0,1.05fr) minmax(0,.95fr); gap:var(--space-7); align-items:center; }
.hero-split > * { position:relative; z-index:1; }
.hero-rig-stage { display:grid; place-items:center; min-height:280px; }
.hero-rig { width:min(100%,340px); height:280px; display:block; }

/* ---------- responsive ---------- */
@media (max-width:820px){
  .shard-grid { grid-template-columns:1fr; }
  .ledger { grid-template-columns:1fr; }
  .hero-grid { grid-template-columns:1fr; }
  .gem-stage { order:-1; min-height:230px; }

  /* The rig is decoration, so a narrow screen drops it rather than stacking it: pushing the h1
     down a phone viewport to make room for an ornament is a bad trade. The hero copy then owns
     the full width, exactly as it does on every other single-column page. */
  .hero-split { grid-template-columns:1fr; }
  .hero-rig-stage { display:none; }

  /* Narrow screens: the header wraps and the nav takes its own full-width row, instead of the
     display:none it used to get. The links keep the 44px min-height from #1609, which is what
     makes them legal touch targets on the viewport that actually has touch.

     Deliberately a wrap and not a hamburger. A <details> disclosure was tried and reverted: its
     content sits in a UA shadow slot, so the desktop "always show" override could not reach it
     and the links measured zero width on wide screens. Five short links fit two rows comfortably
     and cost nothing to reach. */
  .header-inner { flex-wrap:wrap; height:auto; min-height:62px; padding-block:var(--space-2); row-gap:var(--space-2); }
  .site-nav {
    order:3; width:100%; margin-left:0;
    gap:var(--space-2) var(--space-5); flex-wrap:wrap;
    border-top:1px solid var(--line-soft); padding-top:var(--space-1);
  }
}

/* Narrow header budget. Below 560px the row carries the brand, the menu control, the theme
   toggle and the sign-in button, which together overflow a 390px phone. Two trims, in the order
   that costs the least meaning: the sign-in label loses "with Microsoft" (the four-square glyph
   still says whose sign-in it is, and the accessible name keeps the full text because the span is
   clipped rather than removed from the tree), and the brand's uppercase tracking relaxes. */
@media (max-width:560px){
  /* The same off-screen clip as .visually-hidden, scoped to this width, so the span stays in
     the accessibility tree and the accessible name keeps its full text. */
  .signin-long { position:absolute; width:1px; height:1px; margin:-1px; padding:0; overflow:hidden; clip:rect(0 0 0 0); white-space:nowrap; border:0; }
  .header-inner { gap:var(--space-3); }
  .brand { letter-spacing:.08em; font-size:.86rem; }
  .header-actions { gap:var(--space-2); }
  /* The eyebrow's .22em tracking runs a long uppercase string past a phone's width, where
     overflow-x:hidden then clips it silently. */
  .eyebrow { letter-spacing:.14em; }
}


/* ---------- blazor system UI ---------- */
#blazor-error-ui {
  display:none; position:fixed; bottom:0; left:0; right:0; z-index:1000;
  padding:var(--space-3) 20px; background:var(--surface-2); color:var(--ink);
  border-top:1px solid var(--line); box-shadow:0 -6px 20px rgba(0,0,0,.25);
  font-size:.9rem;
}
#blazor-error-ui .reload { color:var(--cyan-ink); text-decoration:underline; }
#blazor-error-ui .dismiss { cursor:pointer; float:right; }

@media (prefers-reduced-motion: reduce){
  /* Blanket the animations too, not just transitions: the reconnect modal's scoped CSS runs an
     infinite spinner that the previous transition-only rule left playing. */
  *, *::before, *::after { transition:none !important; animation:none !important; scroll-behavior:auto !important; }
}

/* ============================================================
   Marketing pages: products, product detail, pricing
   Appended: reuses tokens/classes above. Dark + light aware.
   ============================================================ */

/* ---------- shard cards as links ---------- */
.shard-card .shard-more { margin-top:2px; font-size:var(--text-eyebrow); letter-spacing:.08em; color:var(--h-ink); opacity:.9; }

/* ---------- product detail hero ---------- */
.product-hero {
  position:relative; overflow:hidden; border-bottom:1px solid var(--line-soft);
  background:radial-gradient(120% 130% at 50% 0%, color-mix(in srgb, var(--h) 12%, var(--surface)) 0%, var(--ground) 62%);
}
.product-hero-inner {
  position:relative; z-index:1; display:flex; flex-direction:column; gap:var(--space-4);
  padding-block:clamp(52px,9vw,92px); max-width:60ch;
}
.product-hero-inner .eyebrow a { color:var(--ink-dim); transition:color .18s; }
.product-hero-inner .eyebrow a:hover { color:var(--ink); }
.product-hero-role { font-family:var(--font-mono); font-size:.74rem; letter-spacing:.14em; text-transform:uppercase; color:var(--h-ink); }
.product-hero-name { font-size:clamp(2.4rem,6vw,3.6rem); letter-spacing:-.02em; }
.product-hero-summary { font-size:clamp(1.1rem,2.2vw,1.35rem); font-weight:600; color:var(--ink); max-width:40ch; }

/* ---------- product detail body ---------- */
.product-body { display:grid; grid-template-columns:1.7fr 1fr; gap:36px; align-items:start; }
.product-main { display:flex; flex-direction:column; gap:var(--space-5); }
.product-desc { max-width:62ch; font-size:1.08rem; }
.product-spec { display:flex; gap:var(--space-2); flex-wrap:wrap; }
/* Wears .cut in markup; inherits the product's --h from the section, so the edge is identity. */
.fact-card { --notch:14px; padding:26px var(--space-6); display:flex; flex-direction:column; gap:var(--space-4); }
.fact-list { margin:0; display:flex; flex-direction:column; gap:14px; }
.fact-list > div { display:flex; flex-direction:column; gap:2px; }
.fact-list dt { font-family:var(--font-mono); font-size:var(--text-xs); letter-spacing:.1em; text-transform:uppercase; color:var(--ink-faint); }
.fact-list dd { margin:0; font-weight:600; color:var(--ink); }

/* ---------- pricing: per-product list ---------- */
/* Fused into one ruled block since the box redesign: rows share hairlines instead of floating
   apart, and the solid 3px left bar is the product's identity hue. Hover shifts the ground. */
.price-list { list-style:none; margin:0; padding:0; border:1px solid color-mix(in srgb, var(--metal) 55%, var(--line)); background:var(--surface); }
.price-row { position:relative; border-top:1px solid var(--line-soft); transition:background .15s; }
.price-row:first-child { border-top:0; }
.price-row::before { content:""; position:absolute; inset:0 auto 0 0; width:3px; background:var(--h); }
.price-row:hover { background:var(--surface-2); }
.price-row-link {
  display:flex; flex-wrap:wrap; gap:var(--space-4) var(--space-6); align-items:center; justify-content:space-between;
  padding:var(--space-5) clamp(22px,3vw,30px);
}
.price-row-main { display:flex; flex-direction:column; gap:5px; max-width:56ch; }
.price-row-main .role { font-family:var(--font-mono); font-size:var(--text-xs); letter-spacing:.14em; text-transform:uppercase; color:var(--h-ink); }
.price-row-name { font-size:1.35rem; font-weight:700; letter-spacing:-.015em; }
.price-row-summary { font-size:.96rem; }
.price-row-side { display:flex; flex-direction:column; align-items:flex-end; gap:2px; }
.price-row-fig { font-weight:700; font-size:1.5rem; letter-spacing:-.02em; font-variant-numeric:tabular-nums; color:var(--ink); }
.price-row-per { font-size:var(--text-eyebrow); color:var(--ink-dim); letter-spacing:.02em; }

/* ---------- pricing: what's included ---------- */
.included-list { list-style:none; margin:0; padding:0; display:grid; grid-template-columns:repeat(2,1fr); gap:14px var(--space-6); max-width:80ch; }
.included-list li { position:relative; padding-left:30px; color:var(--ink-dim); line-height:1.55; }
.included-list li::before {
  content:"\25C6"; position:absolute; left:0; top:.12em; font-size:.82em;
  color:var(--metal);
}

/* ---------- pricing: FAQ ---------- */
.faq-list { display:grid; grid-template-columns:repeat(2,1fr); gap:var(--space-5) 32px; margin-bottom:var(--space-7); }
.faq-item { display:flex; flex-direction:column; gap:var(--space-2); padding-top:20px; border-top:1px solid var(--line-soft); }
.faq-q { font-size:1.1rem; font-weight:700; letter-spacing:-.01em; }
.faq-a { max-width:52ch; line-height:1.6; }

/* ---------- responsive ---------- */
@media (max-width:820px){
  .product-body { grid-template-columns:1fr; }
  .included-list { grid-template-columns:1fr; }
  .faq-list { grid-template-columns:1fr; }
  .price-row-link { flex-direction:column; align-items:flex-start; }
  .price-row-side { align-items:flex-start; }
}

/* ============================================================
   Account portal: status pills + panels
   ============================================================ */

/* ---------- status pills ---------- */
/* Squared since the box redesign; the class name survives for the markup's sake. Status colors
   stay: they are semantic, which is the other legitimate job color has here. */
.pill { font-family:var(--font-mono); font-size:var(--text-xs); letter-spacing:.05em; padding:4px var(--space-3);
  display:inline-flex; align-items:center; gap:7px; border:1px solid transparent; white-space:nowrap; }
.pill .led { width:7px; height:7px; border-radius:50%; flex:none; }
.pill.ok    { color:var(--emerald-ink); background:color-mix(in srgb,var(--emerald) 14%, transparent); border-color:color-mix(in srgb,var(--emerald) 34%, transparent); }
.pill.ok .led    { background:var(--emerald); box-shadow:0 0 8px var(--emerald); }
.pill.info  { color:var(--cyan-ink); background:color-mix(in srgb,var(--cyan) 14%, transparent); border-color:color-mix(in srgb,var(--cyan) 34%, transparent); }
.pill.info .led  { background:var(--cyan); box-shadow:0 0 8px var(--cyan); }
.pill.warn  { color:var(--amber-ink); background:color-mix(in srgb,var(--amber) 15%, transparent); border-color:color-mix(in srgb,var(--amber) 36%, transparent); }
.pill.warn .led  { background:var(--amber); box-shadow:0 0 8px var(--amber); }
.pill.alert { color:var(--rose-ink); background:color-mix(in srgb,var(--rose) 15%, transparent); border-color:color-mix(in srgb,var(--rose) 36%, transparent); }
.pill.alert .led { background:var(--rose); box-shadow:0 0 8px var(--rose); }
.pill.muted { color:var(--ink-dim); background:var(--surface-2); border-color:var(--line); }
.pill.muted .led { background:var(--ink-faint); }

/* ---------- account layout ---------- */
.account-head { display:flex; flex-wrap:wrap; gap:var(--space-3) 20px; align-items:baseline; justify-content:space-between; margin-bottom:var(--space-6); }
.account-org { font-size:clamp(1.7rem,3.6vw,2.4rem); font-weight:700; letter-spacing:-.02em; }
.account-sub { color:var(--ink-dim); }
.account-grid { display:grid; grid-template-columns:1fr 1fr; gap:20px; }
.panel { background:var(--surface); border:1px solid var(--line-soft); padding:24px clamp(20px,3vw,28px); }
.panel + .panel, .account-grid + .panel { margin-top:20px; }
.panel-head { display:flex; align-items:baseline; justify-content:space-between; gap:var(--space-3); margin-bottom:14px; }
.panel-head h2 { font-size:1.15rem; letter-spacing:-.01em; }
.panel-head .count { font-family:var(--font-mono); font-size:var(--text-eyebrow); color:var(--ink-faint); }
.rows { display:flex; flex-direction:column; }
.row { display:flex; flex-wrap:wrap; gap:10px 20px; align-items:center; justify-content:space-between; padding:15px 0; border-top:1px solid var(--line-soft); }
.row:first-child { border-top:0; padding-top:0; }
.row-main { display:flex; flex-direction:column; gap:3px; min-width:0; }
.row-title { font-weight:700; letter-spacing:-.01em; }
.row-meta { font-family:var(--font-mono); font-size:.76rem; color:var(--ink-dim); }
.row-side { display:flex; align-items:center; gap:var(--space-3); flex-wrap:wrap; }
.empty { color:var(--ink-dim); font-size:var(--text-base); padding:6px 0; }
@media (max-width:820px){ .account-grid { grid-template-columns:1fr; } }

/* ============================================================
   Prose (legal / about) + forms (subscribe / consent)
   ============================================================ */

.prose { max-width:70ch; display:flex; flex-direction:column; gap:var(--space-4); }
.prose > h2 { color:var(--ink); margin-top:20px; font-size:1.3rem; letter-spacing:-.01em; }
.prose > h3 { color:var(--ink); margin-top:var(--space-2); font-size:1.05rem; letter-spacing:-.01em; }
.prose p, .prose li { color:var(--ink-dim); line-height:1.65; }
.prose a { color:var(--cyan-ink); text-decoration:underline; text-underline-offset:2px; }
.prose ul { margin:0; padding-left:var(--space-5); display:flex; flex-direction:column; gap:var(--space-2); }
.prose strong { color:var(--ink); }
.legal-meta { font-family:var(--font-mono); font-size:.74rem; letter-spacing:.04em; color:var(--ink-faint); }

.form-card { background:var(--surface); border:1px solid var(--line-soft); padding:clamp(24px,4vw,36px); max-width:640px; }
.field { display:flex; flex-direction:column; gap:6px; margin-bottom:var(--space-4); }
.field > label { font-family:var(--font-mono); font-size:var(--text-xs); letter-spacing:.12em; text-transform:uppercase; color:var(--ink-faint); }
.field input, .field textarea, .field select {
  font:inherit; color:var(--ink); background:var(--surface-2); border:1px solid var(--line);
  padding:11px 13px; width:100%;
}
.field input:focus, .field textarea:focus, .field select:focus { outline:2px solid var(--focus); outline-offset:1px; border-color:var(--focus); }
.field-row { display:grid; grid-template-columns:1fr 1fr; gap:0 var(--space-4); }
.consent { display:flex; gap:10px; align-items:flex-start; color:var(--ink-dim); font-size:.92rem; line-height:1.5; }
.consent input[type="checkbox"] { margin-top:3px; width:16px; height:16px; flex:none; accent-color:var(--cyan); }
.consent a { color:var(--cyan-ink); text-decoration:underline; text-underline-offset:2px; }
.btn:disabled, .btn[disabled] { opacity:.45; cursor:not-allowed; transform:none; box-shadow:none; }
@media (max-width:600px){ .field-row { grid-template-columns:1fr; } }

/* ============================================================
   Feedback: notices, field validation, loading skeletons
   Added for the error/loading states the portal never had (#1594).
   ============================================================ */

/* Inline banner. Role=alert in markup; colour alone never carries the meaning, the icon
   glyph and the heading text both state it. */
.notice {
  display:flex; gap:var(--space-3); align-items:flex-start;
  padding:var(--space-4);
  border:1px solid var(--line); border-left:3px solid var(--metal); background:var(--surface-2);
  color:var(--ink-dim); font-size:var(--text-base); line-height:1.55;
  margin-bottom:var(--space-4);
}
.notice .notice-glyph { flex:none; font-size:var(--text-md); line-height:1.3; }
.notice .notice-body { display:flex; flex-direction:column; gap:var(--space-1); }
.notice .notice-title { color:var(--ink); font-weight:700; font-size:var(--text-md); }
.notice-error { border-color:color-mix(in srgb,var(--rose) 40%, transparent); border-left-color:var(--rose); background:color-mix(in srgb,var(--rose) 9%, var(--surface)); }
.notice-error .notice-glyph, .notice-error .notice-title { color:var(--rose-ink); }
.notice-info  { border-color:color-mix(in srgb,var(--cyan) 38%, transparent); border-left-color:var(--cyan); background:color-mix(in srgb,var(--cyan) 9%, var(--surface)); }
.notice-info .notice-glyph, .notice-info .notice-title { color:var(--cyan-ink); }

/* Field-level validation. Blazor emits .validation-message / .invalid itself. */
.field .validation-message, .field-error {
  color:var(--rose-ink); font-size:var(--text-sm); line-height:1.4;
}
.field input.invalid, .field textarea.invalid, .field select.invalid,
.field input[aria-invalid="true"] { border-color:color-mix(in srgb,var(--rose) 55%, var(--line)); }
.validation-errors { margin:0 0 var(--space-4); padding:0; list-style:none; }
.validation-errors .validation-message { display:block; }

/* Loading skeletons. Animation is disabled wholesale under prefers-reduced-motion by the
   global rule above, which leaves a static bar rather than a flashing one. */
.skeleton {
  position:relative; overflow:hidden;
  background:var(--surface-2); min-height:1em;
}
.skeleton::after {
  content:""; position:absolute; inset:0;
  background:linear-gradient(90deg, transparent, color-mix(in srgb,var(--ink) 7%, transparent), transparent);
  transform:translateX(-100%); animation:skeleton-sweep 1.4s ease-in-out infinite;
}
@keyframes skeleton-sweep { to { transform:translateX(100%); } }
.skeleton-row { height:14px; margin:var(--space-3) 0; }
.skeleton-row.short { max-width:38%; }
.skeleton-row.medium { max-width:64%; }

/* Screen-reader-only text: used to announce loading state without rendering visible noise. */
.visually-hidden {
  position:absolute; width:1px; height:1px; padding:0; margin:-1px;
  overflow:hidden; clip:rect(0 0 0 0); white-space:nowrap; border:0;
}

/* Inline acceptance note under a submit action (trial flow). */
.consent-note { margin-top:var(--space-3); color:var(--ink-dim); font-size:var(--text-ms); line-height:1.5; }
.consent-note a { color:var(--cyan-ink); text-decoration:underline; text-underline-offset:2px; }
