/* Screen Ferry — global design system.
   This file is the sole author of every colour, type step, space step and shared
   class on the site. Module stylesheets (modules/<name>.module/<name>.css) are
   loaded and unloaded by the router; they may USE the tokens and classes below
   and may not redefine them.

   ┌───────────────────────────────────────────────────────────────────────────┐
   │ RULES FOR MODULE CSS                                                      │
   │  1. Every colour is a var(--token). A raw hex, rgb(), hsl() or named       │
   │     colour outside the TOKEN BLOCK below is a defect (build spec G1a).     │
   │  2. Do not add anything to :root. If a module needs a value the system     │
   │     does not have, that is a gap in this file — report it.                 │
   │  3. Scope every selector under .m-<module>, e.g. `.m-features__grid`.      │
   │     No bare element selectors in a module stylesheet.                      │
   │  4. Reach for the shared classes first: .wrap .section .stack .cols        │
   │     .prose .display .h1 .h2 .h3 .lede .muted .btn .card .rule .badge       │
   │  5. Text colour is --ink or --ink-2 only. --ink-ui is for non-text UI      │
   │     (icons, placeholders, hairlines) and must never carry body copy.       │
   └───────────────────────────────────────────────────────────────────────────┘
*/

/* ═══════════════════════════════════════════════════════════════════════════
   TOKEN BLOCK — the only place in the whole site where a colour function
   appears. One hue. The theme changes the saturation/lightness ramp only; the
   hue is never touched (build spec G1, G5).

   Each --sl-* holds a "<saturation> <lightness>" pair. The derived tokens below
   feed them to hsl() with the single --hue. Dark mode re-declares the pairs and
   nothing else, so there is exactly one set of hsl() calls on the site.
   ═══════════════════════════════════════════════════════════════════════════ */
:root {
  --hue: 214;

  /* ── light ramp (saturation lightness) ── */
  --sl-bg:            45% 98.5%;
  --sl-bg-sunken:     42% 96%;
  --sl-surface:      100% 100%;
  --sl-surface-2:     44% 97%;
  --sl-accent-quiet:  72% 94%;
  --sl-line:          32% 90%;
  --sl-line-strong:   30% 56%;
  --sl-ink:           46% 12%;
  --sl-ink-2:         26% 30%;
  --sl-ink-ui:        20% 44%;
  --sl-accent:        90% 36%;
  --sl-accent-hover:  92% 28%;
  --sl-on-accent:    100% 99%;
  --sl-link:          92% 33.5%;
  --sl-shadow:        45% 12%;
  --sl-scrim:         45% 14%;
  --a-shadow-1: .07;
  --a-shadow-2: .11;
  --a-scrim: .42;
  --a-focus: .26;

  /* Hero wash — the same hue, three tints, only ever painted at low alpha.
     `glow` is the core behind the headline, `halo` the wider pool beside it,
     `wash` the top-down tint the two sit in. The -soft alphas are the mid
     stops: a two-stop ramp falls off linearly and you can see where it ends.
     The ceiling on these is contrast, not taste — composited at full overlap
     the result must still carry --ink-2 (the lede) at 7:1. Measured worst case
     with every layer at peak: light 7.48:1 on #dfebf9, dark 7.74:1 on #0f2e57.
     Soften a stop before you touch that floor. */
  --sl-hero-glow:     92% 78%;
  --sl-hero-halo:     86% 86%;
  --sl-hero-wash:     72% 95%;
  --a-hero-glow: .10;   --a-hero-glow-soft: .045;
  --a-hero-halo: .09;   --a-hero-halo-soft: .04;
  --a-hero-wash: .65;   --a-hero-wash-soft: .26;
  /* Dither amplitude is arithmetic, not taste: one 8-bit step is 1/255, so an
     alpha of ~.004 against these tints perturbs the value by ~1 level — enough
     to move the quantisation boundary and dissolve a band, too little to see
     as texture. At .01 the hatch becomes faintly visible on flat areas. */
  --a-hero-dither: .004;

  /* ── derived colour (the only colour functions on the site) ── */
  --bg:            hsl(var(--hue) var(--sl-bg));
  --bg-sunken:     hsl(var(--hue) var(--sl-bg-sunken));
  --surface:       hsl(var(--hue) var(--sl-surface));
  --surface-2:     hsl(var(--hue) var(--sl-surface-2));
  --accent-quiet:  hsl(var(--hue) var(--sl-accent-quiet));
  --line:          hsl(var(--hue) var(--sl-line));
  --line-strong:   hsl(var(--hue) var(--sl-line-strong));
  --ink:           hsl(var(--hue) var(--sl-ink));
  --ink-2:         hsl(var(--hue) var(--sl-ink-2));
  --ink-ui:        hsl(var(--hue) var(--sl-ink-ui));
  --accent:        hsl(var(--hue) var(--sl-accent));
  --accent-hover:  hsl(var(--hue) var(--sl-accent-hover));
  --on-accent:     hsl(var(--hue) var(--sl-on-accent));
  --link:          hsl(var(--hue) var(--sl-link));
  --focus:         hsl(var(--hue) var(--sl-accent));
  --focus-halo:    hsl(var(--hue) var(--sl-accent) / var(--a-focus));
  --scrim:         hsl(var(--hue) var(--sl-scrim) / var(--a-scrim));
  --shadow-1: 0 4px 14px hsl(var(--hue) var(--sl-shadow) / var(--a-shadow-1));
  --shadow-2: 0 8px 28px hsl(var(--hue) var(--sl-shadow) / var(--a-shadow-2));
  /* Shared site chrome: white-to-blue in light mode and the same restrained
     progression through deep blue surfaces in dark mode. */
  --site-head-surface:
    linear-gradient(to top, var(--surface) 0%, var(--bg) 52%, var(--accent-quiet) 100%);

  /* Hero wash, derived. Each ramp ends on its OWN hue at zero alpha rather
     than on `transparent`: interpolating towards transparent black is what
     puts a grey bruise in the middle of a soft gradient. The -dither pair is
     the anti-banding speckle; it rides on --sl-ink so it flips with the theme
     (a dark speckle on the light wash, a light one on the dark glow). */
  --hero-glow:       hsl(var(--hue) var(--sl-hero-glow) / var(--a-hero-glow));
  --hero-glow-soft:  hsl(var(--hue) var(--sl-hero-glow) / var(--a-hero-glow-soft));
  --hero-glow-end:   hsl(var(--hue) var(--sl-hero-glow) / 0);
  --hero-halo:       hsl(var(--hue) var(--sl-hero-halo) / var(--a-hero-halo));
  --hero-halo-soft:  hsl(var(--hue) var(--sl-hero-halo) / var(--a-hero-halo-soft));
  --hero-halo-end:   hsl(var(--hue) var(--sl-hero-halo) / 0);
  --hero-wash:       hsl(var(--hue) var(--sl-hero-wash) / var(--a-hero-wash));
  --hero-wash-soft:  hsl(var(--hue) var(--sl-hero-wash) / var(--a-hero-wash-soft));
  --hero-wash-end:   hsl(var(--hue) var(--sl-hero-wash) / 0);
  --hero-dither:     hsl(var(--hue) var(--sl-ink) / var(--a-hero-dither));
  --hero-dither-end: hsl(var(--hue) var(--sl-ink) / 0);

  /* The hero surface, front layer first. A module applies it with
     `background-image: var(--hero-surface)`; the geometry lives here so the
     colour and the shape of the wash stay in one place.

     Four deliberate choices, all of them about not being able to see the
     gradient's edges:
      1. two overlapping radials rather than one long linear — a short, shallow
         ramp quantises into fewer, wider bands than a full-height one;
      2. a mid stop in every ramp, so the falloff eases out instead of ending
         on a straight line you can pick out;
      3. every ramp ends on its own hue at zero alpha (see above);
      4. a 1-in-3px diagonal speckle on top at ~0.4% alpha — about one 8-bit
         step, so it is invisible on its own but breaks up the flat plateaus
         where banding actually shows.
     Vertical radii are percentages of the hero, so the wash always finishes
     inside the section and never seams against what follows. Horizontal radii
     are clamped in rem: on a 2560px monitor this stays a pool of light rather
     than stretching into a stripe (G9).

     The two centres are held against the content column, not the viewport.
     .wrap caps out at --page (68rem) and centres, so past ~2100px a plain
     `24%` walks off the left of the headline and the glow stops being behind
     anything. `max(24%, 50% - 18rem)` tracks the percentage while the page is
     narrow and pins to the text column once it stops growing; the halo's
     `min(80%, 50% + 20rem)` does the same on the right.

     Nothing moves, so there is nothing for prefers-reduced-motion to gate. */
  --hero-surface:
    repeating-linear-gradient(45deg,
      var(--hero-dither) 0 1px, var(--hero-dither-end) 1px 3px),
    radial-gradient(ellipse clamp(20rem, 74vw, 58rem) 74%
      at max(24%, 50% - 18rem) 18%,
      var(--hero-glow) 0%, var(--hero-glow-soft) 40%, var(--hero-glow-end) 74%),
    radial-gradient(ellipse clamp(24rem, 98vw, 80rem) 86%
      at min(80%, 50% + 20rem) 0%,
      var(--hero-halo) 0%, var(--hero-halo-soft) 42%, var(--hero-halo-end) 76%),
    linear-gradient(180deg,
      var(--hero-wash) 0%, var(--hero-wash-soft) 54%, var(--hero-wash-end) 92%);

  /* ── sign-in wash ───────────────────────────────────────────────────────
     Declared HERE and not in signin.css, because a custom property is
     substituted where it is DECLARED, not where it is used: overriding
     --sl-hero-wash on .m-signin cannot reach back into --hero-wash, which
     :root already resolved. That mistake is why the sign-in wash changed its
     numbers and never changed its colour.

     Deeper than the hero's: the hero holds a headline at 16:1 and must stay
     pale, the sign-in page carries no body copy on the wash at all. */
  --signin-wash:      hsl(var(--hue) 74% 84%);
  --signin-wash-soft: hsl(var(--hue) 76% 92% / .55);
  --signin-wash-end:  hsl(var(--hue) 76% 92% / 0);
  --signin-glow:      hsl(var(--hue) 92% 76% / .55);
  --signin-glow-soft: hsl(var(--hue) 92% 80% / .22);
  --signin-glow-end:  hsl(var(--hue) 92% 80% / 0);

  /* ── status (toast only) ────────────────────────────────────────────────
     THE ONE DEPARTURE FROM THE SINGLE HUE, and it is deliberate: the owner
     asked for a red error toast. Colour here carries meaning, not decoration,
     and it is confined to the toast — nothing else on the site may use these.
     Each pairs a fill with the ink that clears 7:1 on it in both themes. */
  --status-error-bg:   hsl(2 68% 42%);
  --status-error-quiet: hsl(2 78% 94%);
  --status-error-fg:   hsl(2 100% 99%);
  --status-ok-bg:      hsl(152 52% 28%);
  --status-ok-fg:      hsl(152 100% 99%);
  --status-warn-bg:    hsl(38 88% 38%);
  --status-warn-fg:    hsl(38 100% 99%);

  /* inverted pair, used by the toast only */
  --inverse-bg: var(--ink);
  --inverse-fg: var(--bg);

  /* ── type ── */
  --font: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
  --font-mono: ui-monospace, SFMono-Regular, Menlo, monospace;
  /* ONE SCALE, SEVEN STEPS, RATIO ~1.25 (a major third).
     Was eight steps with four different vw coefficients, which is not a scale —
     it is a pile of numbers that cross each other at viewports nobody checked.
     --fs-lede is gone: it bottomed at 1.125rem against a 1.0625rem body, which
     is a 1px difference on a phone and reads as a mistake rather than a step.
     The lede now takes --fs-h3 at normal weight, which is a real step up.

     No crossover is possible here because every min ascends AND every max
     ascends: 1.1875 < 1.375 < 1.75 < 2.25 and 1.25 < 1.75 < 2.375 < 3.25.

     --fs-h3's FLOOR IS 1.1875rem AND NOT 1.125rem, and that is the whole point.
     Deleting --fs-lede did not fix the 1px step it was deleted for: --fs-lede
     bottomed at 1.125rem against a 1.0625rem body, and --fs-h3 bottomed at
     exactly the same 1.125rem. Pointing .lede at --fs-h3 moved the defect, it
     did not remove it — measured 18.16px against 17px at 375, and 18px against
     17px at 320. A step you cannot see is not a step; it reads as a rounding
     error. 19px against 17px is 1.118, in family with 15/13 and 17/15, and it
     is the smallest floor that survives a phone.

     The CEILING stays 1.25rem on purpose. .lede is held to --measure (36rem),
     which at 20px is exactly 60 characters — the bottom of the readable band.
     Raising this max buys a louder lede and a measure that is too short. */
  --fs-display: clamp(2.25rem,   1.75rem + 2.50vw, 3.25rem);
  --fs-h1:      clamp(1.75rem,   1.45rem + 1.50vw, 2.375rem);
  --fs-h2:      clamp(1.375rem,  1.20rem + 0.88vw, 1.75rem);
  --fs-h3:      clamp(1.1875rem, 1.122rem + 0.20vw, 1.25rem);
  --fs-body:    1.0625rem;
  --fs-sm:      .9375rem;
  --fs-xs:      .8125rem;
  --lh-tight: 1.06;
  --lh-snug: 1.24;
  --lh-body: 1.6;
  --tr-display: -.028em;   /* tight tracking on display sizes (G4) */
  --tr-head: -.017em;
  --tr-eyebrow: .09em;

  /* ── space, measure, shape ── */
  --sp-1: .25rem;  --sp-2: .5rem;   --sp-3: .75rem;  --sp-4: 1rem;
  --sp-5: 1.5rem;  --sp-6: 2rem;    --sp-7: 3rem;    --sp-8: 4rem;   --sp-9: 6rem;
  --gutter: clamp(1.25rem, 4vw, 2.5rem);
  --section-y: clamp(3rem, 7vw, 6rem);
  --section-y-lg: clamp(4rem, 10vw, 8.5rem);
  --page: 68rem;       /* max content width */
  --measure: 36rem;    /* max prose line length */
  --head-h: 4rem;
  --radius-sm: 8px;
  --radius: 12px;
  /* The Login button's corner, set by the owner at 15px. It is a token and not
     a literal because two places must agree on it: the Login button in the site
     header, and the four provider buttons on /signin that echo it. It sits
     between --radius and --radius-lg, so the sign-in card (--radius-lg) still
     reads as the larger shape containing the smaller ones. */
  /* EVERY BUTTON AND FORM CONTROL ON THE SITE. Buttons are squarer than
     surfaces here — a card can be soft, a control should not be. Change this
     one number and the header button, the hero pair, the sign-in providers and
     every field move together. */
  --radius-btn: 6px;
  --btn-h: 2.875rem;
  --radius-lg: 20px;
  --radius-pill: 999px;
  --hairline: 1px;

  /* ── motion (G7) ── */
  --dur-1: .14s;
  --dur-2: .24s;
  --ease: cubic-bezier(.22, .61, .24, 1);
}

/* Technical diagrams are readable source text before enhancement and become
   self-hosted Mermaid SVGs when their page module mounts. */
.diagram {
  margin-block: var(--sp-7);
  padding: clamp(var(--sp-4), 4vw, var(--sp-6));
  overflow-x: auto;
  border: var(--hairline) solid var(--line);
  border-radius: var(--radius-lg);
  background: var(--surface);
  box-shadow: var(--shadow-1);
}
.diagram .mermaid { min-width: 36rem; text-align: center; }
.diagram svg { display: block; width: 100%; height: auto; margin-inline: auto; }
.diagram svg :is(text, tspan),
.diagram svg foreignObject,
.diagram svg foreignObject * {
  font-family: var(--font) !important;
  font-size: var(--fs-sm) !important;
}
.diagram svg :is(.label, .edgeLabel, .edgeLabel rect, .labelBkg),
.diagram svg :is(.label, .edgeLabel) foreignObject,
.diagram svg :is(.label, .edgeLabel) foreignObject * {
  background: transparent !important;
  background-color: transparent !important;
}
.diagram svg :is(.edgeLabel rect, .labelBkg) { fill: transparent !important; }
.diagram svg :is(.label > rect, rect.background) {
  fill: transparent !important;
  stroke: none !important;
}
.diagram svg .cluster rect { fill: transparent !important; }

/* Mermaid writes a scoped <style> into each generated SVG. Production's strict
   style-src deliberately rejects runtime inline styles, so every visual rule
   required by the diagram types we ship lives here in this trusted asset. */
.diagram svg :is(text, tspan) { fill: var(--ink) !important; }
.diagram svg :is(.nodeLabel, .edgeLabel, .cluster-label) { color: var(--ink) !important; }
.diagram svg .node > :is(rect, circle, ellipse, polygon, path) {
  fill: var(--surface-2);
  stroke: var(--line-strong);
  stroke-width: 1px;
}
.diagram svg :is(.flowchart-link, .transition) {
  fill: none !important;
  stroke: var(--ink-ui) !important;
  stroke-width: 1.25px;
}
.diagram svg :is(marker path, .marker) {
  fill: var(--ink-ui) !important;
  stroke: var(--ink-ui) !important;
}
.diagram svg .cluster rect { stroke: var(--line-strong) !important; }
.diagram svg .node :is(.state-start, .fork-join) {
  fill: var(--ink) !important;
  stroke: var(--ink) !important;
}
.diagram svg .node .state-end {
  fill: var(--surface-2) !important;
  stroke: var(--ink) !important;
}
.diagram svg .statediagram-cluster rect.outer {
  fill: var(--surface-2) !important;
  stroke: var(--line-strong) !important;
}
.diagram svg .statediagram-cluster rect.inner { fill: var(--surface) !important; }
.diagram svg .statediagram-state .divider { stroke: var(--line-strong) !important; }
.diagram svg .actor {
  fill: var(--surface-2) !important;
  stroke: var(--line-strong) !important;
}
.diagram svg .actor-line {
  fill: none !important;
  stroke: var(--line-strong) !important;
  stroke-width: 1px;
  stroke-dasharray: 3 3;
}
.diagram svg :is(.messageLine0, .messageLine1) {
  fill: none !important;
  stroke: var(--ink-ui) !important;
  stroke-width: 1.25px;
}
.diagram svg .messageLine1 { stroke-dasharray: 3 3; }
.diagram svg .note {
  fill: var(--accent-quiet) !important;
  stroke: var(--line-strong) !important;
}
.diagram--compact svg { max-width:40rem; }
.diagram--compact svg :is(text, tspan),
.diagram--compact svg foreignObject,
.diagram--compact svg foreignObject * { font-size:.8125rem !important; }
.diagram figcaption { margin-block-start: var(--sp-3); color: var(--ink-2); font-size: var(--fs-body); line-height: var(--lh-body); text-align:center; }

/* Dark: the lightness ramp inverts, the hue does not move. Declared twice so the
   site is still correct if the inline theme bootstrap never ran. */
:root[data-theme="dark"] {
  --sl-hero-glow:     95% 38%;
  --sl-hero-halo:     90% 30%;
  --sl-hero-wash:     55% 14%;
  --signin-wash:      hsl(var(--hue) 60% 16%);
  --signin-wash-soft: hsl(var(--hue) 62% 12% / .62);
  --signin-wash-end:  hsl(var(--hue) 62% 12% / 0);
  --signin-glow:      hsl(var(--hue) 95% 40% / .30);
  --signin-glow-soft: hsl(var(--hue) 95% 42% / .12);
  --signin-glow-end:  hsl(var(--hue) 95% 42% / 0);
  --status-error-bg:   hsl(2 72% 62%);
  --status-error-quiet: hsl(2 45% 22%);
  --status-ok-bg:      hsl(152 46% 34%);
  --status-warn-bg:    hsl(38 78% 44%);
  --a-hero-glow: .18;   --a-hero-glow-soft: .08;
  --a-hero-halo: .16;   --a-hero-halo-soft: .07;
  --a-hero-wash: .70;   --a-hero-wash-soft: .28;
  --a-hero-dither: .006;

  --sl-bg:            42% 6%;
  --sl-bg-sunken:     44% 4%;
  --sl-surface:       34% 10%;
  --sl-surface-2:     32% 13%;
  --sl-accent-quiet:  55% 17%;
  --sl-line:          25% 20%;
  --sl-line-strong:   24% 46%;
  --sl-ink:           40% 96%;
  --sl-ink-2:         24% 78%;
  --sl-ink-ui:        16% 60%;
  --sl-accent:        92% 70%;
  --sl-accent-hover:  95% 80%;
  --sl-on-accent:     60% 8%;
  --sl-link:          95% 75%;
  --sl-shadow:        60% 2%;
  --sl-scrim:         50% 3%;
  --a-shadow-1: .5;
  --a-shadow-2: .6;
  --a-scrim: .62;
  --a-focus: .3;
}
@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) {
    --sl-hero-glow:     95% 38%;
    --sl-hero-halo:     90% 30%;
    --sl-hero-wash:     55% 14%;
    --a-hero-glow: .18;   --a-hero-glow-soft: .08;
    --a-hero-halo: .16;   --a-hero-halo-soft: .07;
    --a-hero-wash: .70;   --a-hero-wash-soft: .28;
    --a-hero-dither: .006;

    --sl-bg:            42% 6%;
    --sl-bg-sunken:     44% 4%;
    --sl-surface:       34% 10%;
    --sl-surface-2:     32% 13%;
    --sl-accent-quiet:  55% 17%;
    --sl-line:          25% 20%;
    --sl-line-strong:   24% 46%;
    --sl-ink:           40% 96%;
    --sl-ink-2:         24% 78%;
    --sl-ink-ui:        16% 60%;
    --sl-accent:        92% 70%;
    --sl-accent-hover:  95% 80%;
    --sl-on-accent:     60% 8%;
    --sl-link:          95% 75%;
    --sl-shadow:        60% 2%;
    --sl-scrim:         50% 3%;
    --a-shadow-1: .5;
    --a-shadow-2: .6;
    --a-scrim: .62;
    --a-focus: .3;
  }
}
/* ═══════════════ END TOKEN BLOCK — no colour functions past this line ═══════ */

/* Motion is a token, so honouring the preference is one override (G7). */
@media (prefers-reduced-motion: reduce) {
  :root { --dur-1: 0s; --dur-2: 0s; }
  *, *::before, *::after {
    animation-duration: .001ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: .001ms !important;
    scroll-behavior: auto !important;
  }
}

/* ── reset + base ─────────────────────────────────────────────────────────── */
*, *::before, *::after { box-sizing: border-box; }
html {
  /* G6: the page body never scrolls sideways. `clip` (not `hidden`) so the
     sticky header keeps working. */
  overflow-x: clip;
  -webkit-text-size-adjust: 100%;
}
body {
  margin: 0;
  min-height: 100vh;
  display: flex;
  flex-direction: column;
  background: var(--bg);
  color: var(--ink);
  font-family: var(--font);
  font-size: var(--fs-body);
  line-height: var(--lh-body);
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  text-rendering: optimizeLegibility;
}
img, svg, video, canvas { max-width: 100%; height: auto; display: block; }
h1, h2, h3, h4, p, ul, ol, figure, blockquote { margin: 0; }
ul, ol { padding: 0; list-style: none; }
button, input, select, textarea { font: inherit; color: inherit; }
::selection { background: var(--accent-quiet); color: var(--ink); }
:target { scroll-margin-top: calc(var(--head-h) + var(--sp-4)); }

/* Focus: always visible, always the accent, never removed. */
:focus-visible {
  outline: 2px solid var(--focus);
  outline-offset: 2px;
  border-radius: var(--radius-sm);
}
#view:focus { outline: none; }          /* programmatic focus after navigation */
#view:focus-visible { outline: 2px solid var(--focus); outline-offset: -2px; }

/* Scrollbars — hairline, tokenised. */
* { scrollbar-width: thin; scrollbar-color: var(--line-strong) transparent; }
::-webkit-scrollbar { width: 10px; height: 10px; }
::-webkit-scrollbar-track, ::-webkit-scrollbar-corner { background: transparent; }
::-webkit-scrollbar-thumb {
  background: var(--line-strong); border-radius: var(--radius-pill);
  border: 3px solid transparent; background-clip: padding-box;
}

/* ── layout primitives ────────────────────────────────────────────────────── */
.wrap {
  width: 100%; max-width: var(--page); min-width: 0;
  margin-inline: auto; padding-inline: var(--gutter);
}
.view { flex: 1 1 auto; min-width: 0; }
.section { padding-block: var(--section-y); }
.section--lg { padding-block: var(--section-y-lg); }
.section--tight { padding-block: var(--sp-7); }
.section--sunken { background: var(--bg-sunken); }
.section + .section--sunken, .section--sunken + .section { border-top: var(--hairline) solid var(--line); }

/* vertical rhythm: .stack puts space BETWEEN children, never around them */
.stack > * + * { margin-block-start: var(--sp-4); }
.stack--sm > * + * { margin-block-start: var(--sp-2); }
.stack--lg > * + * { margin-block-start: var(--sp-6); }
.stack--xl > * + * { margin-block-start: var(--sp-8); }

/* Shared editorial rhythm. Page modules can focus on their layouts while
   recurring text sequences keep the same breathing room everywhere. */
.section .eyebrow + :is(.display, .h1, .h2, .h3) {
  margin-block-start: var(--sp-3);
}
.section :is(.display, .h1, .h2, .h3) + :is(.lede, p) {
  margin-block-start: var(--sp-4);
}
.section :is(.lede, p) + .btn-row {
  margin-block-start: var(--sp-6);
}

/* responsive grid; never narrower than its content, so no sideways scroll */
.cols { display: grid; gap: var(--sp-6); grid-template-columns: repeat(auto-fit, minmax(min(100%, 17rem), 1fr)); }
.cols--2 { grid-template-columns: repeat(auto-fit, minmax(min(100%, 22rem), 1fr)); }
.cols--3 { grid-template-columns: repeat(auto-fit, minmax(min(100%, 15rem), 1fr)); }
.cols > * { min-width: 0; }

/* ── typography ───────────────────────────────────────────────────────────── */
.display, .h1, .h2, .h3 { font-weight: 640; color: var(--ink); text-wrap: balance; }
.display { font-size: var(--fs-display); line-height: var(--lh-tight); letter-spacing: var(--tr-display); }
.h1 { font-size: var(--fs-h1); line-height: var(--lh-snug); letter-spacing: var(--tr-display); }
.h2 { font-size: var(--fs-h2); line-height: var(--lh-snug); letter-spacing: var(--tr-head); }
.h3 { font-size: var(--fs-h3); line-height: 1.34; letter-spacing: var(--tr-head); font-weight: 620; }
.lede { font-size: var(--fs-h3); line-height: 1.5; color: var(--ink-2); letter-spacing: -.006em; }
.small { font-size: var(--fs-sm); }
.xs { font-size: var(--fs-xs); }
.muted { color: var(--ink-2); }
/* em-relative on purpose: .mono is inline and must track whatever it sits in,
   so a monospace run inside body copy and inside a caption both look right. */
.mono { font-family: var(--font-mono); font-size: .92em; }
.eyebrow {
  font-size: var(--fs-xs); font-weight: 600; text-transform: uppercase;
  letter-spacing: var(--tr-eyebrow); color: var(--ink-2);
}
.nowrap { white-space: nowrap; }

/* body copy: left-aligned, held to a readable measure (G4) */
.prose { max-width: var(--measure); text-align: left; overflow-wrap: break-word; }
.prose > * + * { margin-block-start: var(--sp-4); }
.prose p { color: var(--ink-2); }
.prose strong { color: var(--ink); font-weight: 620; }
.wide { max-width: 52rem; }

/* ── links ────────────────────────────────────────────────────────────────── */
a { color: var(--link); text-decoration: none; }
.view a:not(.btn), .site-foot a, a.link {
  text-underline-offset: .18em;
  text-decoration-thickness: from-font;
  transition: color var(--dur-1) var(--ease), text-decoration-color var(--dur-1) var(--ease);
}
.view a:not(.btn):hover, .site-foot a:hover, a.link:hover {
  color: var(--accent-hover);
  text-decoration: underline;
}
.view nav a:not(.btn):hover { text-decoration: none; }

/* ── buttons ──────────────────────────────────────────────────────────────── */
.btn {
  display: inline-flex; align-items: center; justify-content: center; gap: var(--sp-2);
  height: var(--btn-h); min-height: var(--btn-h);
  font-size: var(--fs-sm); font-weight: 600; line-height: 1.2; letter-spacing: -.005em;
  padding: .625rem 1.125rem;
  border: var(--hairline) solid transparent; border-radius: var(--radius-btn);
  cursor: pointer; text-decoration: none; white-space: nowrap;
  transition: background var(--dur-1) var(--ease), border-color var(--dur-1) var(--ease),
              color var(--dur-1) var(--ease), box-shadow var(--dur-1) var(--ease);
}
.btn--primary { background: var(--accent); color: var(--on-accent); }
.btn--primary:hover { background: var(--accent-hover); }
.btn--ghost { background: var(--surface); color: var(--ink); border-color: var(--line-strong); }
.btn--ghost:hover { background: var(--surface-2); border-color: var(--ink-2); }
.btn--quiet { background: transparent; color: var(--ink-2); }
.btn--quiet:hover { background: var(--surface-2); color: var(--ink); }
.btn--sm { padding-inline: .875rem; font-size: var(--fs-sm); }
.btn--lg { padding-inline: 1.5rem; font-size: var(--fs-body); }
.btn--block { width: 100%; }
.btn[aria-disabled="true"], .btn:disabled { opacity: .55; pointer-events: none; }
.btn-row { display: flex; flex-wrap: wrap; gap: var(--sp-3); align-items: center; }

@media (max-width: 600px) {
  .view > [class^="m-"] > .section:first-child .btn-row {
    display: grid;
    grid-template-columns: minmax(0, 1fr);
    width: 100%;
  }
  .view > [class^="m-"] > .section:first-child .btn-row .btn {
    width: 100%;
    min-width: 0;
    white-space: normal;
  }
}

.iconbtn {
  display: inline-flex; align-items: center; justify-content: center;
  width: 2.25rem; height: 2.25rem; flex: none;
  background: transparent; color: var(--ink-2);
  border: var(--hairline) solid transparent; border-radius: var(--radius-btn); cursor: pointer;
  transition: background var(--dur-1) var(--ease), color var(--dur-1) var(--ease);
}
/* Circular blue hover on the two round controls in the header. The fill is the
   quiet accent so it reads as blue without competing with the primary button
   beside it, and the ring is what makes it a circle rather than a smudge. */
.iconbtn { border-radius: var(--radius-pill); }
.iconbtn:hover {
  background: var(--accent-quiet);
  color: var(--accent);
  box-shadow: 0 0 0 1px var(--accent-quiet);
}
.iconbtn:active { background: var(--accent-quiet); color: var(--accent-hover); }
.iconbtn:focus-visible { outline: 2px solid var(--focus); outline-offset: 2px; }

/* ── the account button ───────────────────────────────────────────────────── */
/* Signed out it is a user glyph; signed in it becomes an exit-door glyph. */
/* [hidden] MUST BEAT A TYPE SELECTOR, AND IT DOES NOT BY DEFAULT.
   The UA sheet's `[hidden] { display: none }` is author-overridable, and this
   sheet sets `img { display: block }` — so a hidden <img> stayed a block, and a
   source-less one rendered as an empty box with the browser's own placeholder
   chrome drawn around it. That is what the "dotted circle" around the account
   glyph was. Global, because it will happen again to the next element that is
   both hidden and typed. */
[hidden] { display: none !important; }

/* Account trigger and its authenticated popup. */
.account-menu-wrap { position: relative; }
.iconbtn--avatar { overflow: hidden; }
.iconbtn--avatar .icon--user { width: 1.15rem; height: 1.15rem; }
.iconbtn--avatar.is-signed-in:hover { background: var(--accent-quiet); color: var(--accent); }
.account-menu {
  position: absolute; z-index: 60;
  inset: calc(100% + var(--sp-2)) 0 auto auto;
  inline-size: 13rem;
  padding: var(--sp-2);
  border: var(--hairline) solid var(--line);
  border-radius: var(--radius);
  background: var(--surface);
  box-shadow: var(--shadow-2);
}
.account-menu :is(a, button) {
  display: flex; align-items: center; gap: var(--sp-3);
  width: 100%; padding: var(--sp-3);
  border: 0; border-radius: var(--radius-btn);
  background: transparent; color: var(--ink);
  font: inherit; font-size: var(--fs-sm); font-weight: 600;
  text-align: start; text-decoration: none; cursor: pointer;
}
.account-menu :is(a, button):hover { background: var(--surface-2); color: var(--accent); text-decoration: none; }
.account-menu :is(a, button):focus-visible { outline: 2px solid var(--focus); outline-offset: -2px; }
.icon { width: 1.125rem; height: 1.125rem; flex: none; }

/* ── surfaces ─────────────────────────────────────────────────────────────── */
.card {
  background: var(--surface); border: var(--hairline) solid var(--line);
  border-radius: var(--radius); padding: var(--sp-6); box-shadow: var(--shadow-1);
}
.card--flat { box-shadow: var(--shadow-1); }
.card--quiet { background: var(--surface-2); border-color: var(--line); box-shadow: var(--shadow-1); }
.rule { height: var(--hairline); background: var(--line); border: 0; margin-block: var(--sp-6); }
.badge {
  display: inline-flex; align-items: center; gap: var(--sp-2);
  font-size: var(--fs-xs); font-weight: 600; letter-spacing: .01em;
  padding: .1875rem .5rem; border-radius: var(--radius-sm);
  background: var(--accent-quiet); color: var(--ink);
}
/* One hue means status is weight and wording, not colour (G1). */
.badge--strong { background: var(--ink); color: var(--bg); }
.badge--quiet { background: var(--surface-2); color: var(--ink-2); font-weight: 500; }

/* ── form fields (shared; controls/ own their internals) ──────────────────── */
.field { display: flex; flex-direction: column; gap: var(--sp-2); }
.field > label { font-size: var(--fs-sm); font-weight: 600; }
.field input, .field textarea, .field select {
  background: var(--surface); color: var(--ink);
  border: var(--hairline) solid var(--line-strong); border-radius: var(--radius-btn);
  padding: .625rem .75rem; font-size: var(--fs-sm);
  transition: border-color var(--dur-1) var(--ease), box-shadow var(--dur-1) var(--ease);
}
.field input::placeholder, .field textarea::placeholder { color: var(--ink-ui); }
.field input:focus, .field textarea:focus, .field select:focus {
  outline: none; border-color: var(--accent); box-shadow: 0 0 0 3px var(--focus-halo);
}

/* ── site chrome: header ──────────────────────────────────────────────────── */
.skip {
  position: absolute; left: var(--sp-4); top: var(--sp-4); z-index: 90;
  transform: translateY(-200%);
  background: var(--surface); color: var(--ink);
  border: var(--hairline) solid var(--line-strong); border-radius: var(--radius-btn);
  padding: var(--sp-2) var(--sp-4); font-size: var(--fs-sm); font-weight: 600;
  box-shadow: var(--shadow-2);
}
.skip:focus { transform: translateY(0); }

.site-head {
  position: sticky; top: 0; z-index: 50;
  background: var(--site-head-surface);
  border-bottom: var(--hairline) solid var(--line);
}
/* FULL WIDTH, 30px in from each edge — the wordmark sits at the left edge of
   the screen and the controls at the right, rather than both being pulled into
   the centred --page column the content uses. The header frames the window; the
   content frames the reading. `.site-head__inner` therefore drops `.wrap`'s max
   width and takes its own inline padding, and `space-between` does the rest.

   Literal 30px, matching the dashboard's table margin, and deliberately not a
   --sp step: the spacing scale is a rhythm for prose and has no 30px in it. */
.site-head__inner {
  display: flex; align-items: center; justify-content: space-between;
  gap: var(--sp-5);
  min-height: var(--head-h);
  max-width: none;
  padding-inline: 30px;
}
/* The nav is the middle of the three; it should not push the ends around. */
.site-head__inner > .site-nav { margin-inline: auto; }
@media (max-width: 26rem) { .site-head__inner { padding-inline: var(--gutter); } }
.wordmark {
  display: inline-flex; align-items: center; gap: var(--sp-2);
  color: var(--ink); font-weight: 640; letter-spacing: -.02em; font-size: var(--fs-body);
  flex: 0 1 auto; min-width: 0; text-decoration: none;
}
/* Last-resort safety net: the wordmark gives way before the header can push the
   page sideways (G6). It should never actually fire at >= 320px. */
.wordmark__text { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
/* Product artwork, like the provider logos: the app icon keeps its own colours.
   No tint, no filter, no mask — the one-hue palette does not reach it. The tile
   is fully opaque, so it needs no plate behind it on either theme. */
.wordmark__mark { flex: none; width: 1.75rem; height: 1.75rem;
  box-sizing: border-box;
  border: 1px solid #d1d5db;
  border-radius: 5px; }

.site-nav { display: flex; align-items: center; gap: var(--sp-5); margin-inline-start: auto; }
.site-nav a {
  color: var(--ink-2); font-size: var(--fs-sm); font-weight: 550;
  padding-block: var(--sp-2);
  transition: color var(--dur-1) var(--ease);
}
.site-nav a:hover { color: var(--ink); }
.site-nav a.is-current { color: var(--accent); font-weight: 640; }
.site-nav a.is-current:hover { color: var(--accent-hover); }

.site-head__end { display: flex; align-items: center; gap: var(--sp-3); flex: none; }
/* The Login button. The only .btn in the site chrome, and the owner's 15px
   corner applies to it and nothing else — .btn--sm is shared, so the radius is
   set here on the header slot rather than on the size modifier. More specific
   than the global :focus-visible, so the focus ring follows the same corner. */
/* the theme toggle shows the icon for the theme it switches TO */
.icon--dark { display: none; }
:root[data-theme="dark"] .icon--light { display: none; }
:root[data-theme="dark"] .icon--dark { display: block; }
@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) .icon--light { display: none; }
  :root:not([data-theme="light"]) .icon--dark { display: block; }
}

.nav-toggle { display: none; }
.nav-toggle .icon--close { display: none; }
.nav-scrim { display: none; position: fixed; inset: 0; z-index: 40; background: var(--scrim); }

/* ── site chrome: footer ──────────────────────────────────────────────────── */
.site-foot {
  margin-block-start: auto;
  border-top: var(--hairline) solid var(--line);
  padding-block: var(--sp-6);
  font-size: var(--fs-sm);
}
/* Centred, and centred when it wraps too — justify-content centres the rows,
   text-align centres a row that breaks inside an item on a narrow phone. */
.site-foot__inner {
  display: flex; flex-wrap: wrap;
  align-items: center; justify-content: center;
  text-align: center;
  gap: var(--sp-3); color: var(--ink-2);
}
.site-foot__sep { color: var(--ink-ui); }

/* ── toasts ───────────────────────────────────────────────────────────────── */
.toasts {
  position: fixed; z-index: 80;
  left: 50%; bottom: var(--sp-5); transform: translateX(-50%);
  display: flex; flex-direction: column; gap: var(--sp-2);
  width: max-content; max-width: min(92vw, 30rem);
}
.toast {
  background: var(--inverse-bg); color: var(--inverse-fg);
  padding: var(--sp-3) var(--sp-5); border-radius: var(--radius);
  font-size: var(--fs-sm); box-shadow: var(--shadow-2);
  animation: rise var(--dur-2) var(--ease);
}
.toast--strong { font-weight: 640; box-shadow: var(--shadow-2), 0 0 0 2px var(--accent); }
/* Status fills. Same box, same radius as the card — only the fill changes. */
.toast--error { background: var(--status-error-bg); color: var(--status-error-fg); font-weight: 620; }
.toast--ok    { background: var(--status-ok-bg);    color: var(--status-ok-fg); }
.toast--warn  { background: var(--status-warn-bg);  color: var(--status-warn-fg); }
@media (prefers-reduced-motion: reduce) { .toast { animation: none; } }
@keyframes rise { from { opacity: 0; transform: translateY(6px); } to { opacity: 1; transform: none; } }

/* ── utilities ────────────────────────────────────────────────────────────── */
.hidden { display: none !important; }
.center { text-align: center; }
.sr-only {
  position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px;
  overflow: hidden; clip-path: inset(50%); white-space: nowrap; border: 0;
}

/* ── responsive: the full topic nav collapses before the header gets crowded ─ */
@media (max-width: 64rem) {
  .nav-toggle { display: inline-flex; }
  body.nav-open .nav-toggle .icon--open { display: none; }
  body.nav-open .nav-toggle .icon--close { display: block; }

  /* A POPOVER UNDER THE BUTTON, NOT A FULL-WIDTH DRAWER.
     It used to span inset-inline:0, which on a phone is a slab across the whole
     screen for three links. It is now sized to its content, pinned to the left
     gutter under the toggle that opened it, and given the card's own corner,
     border and shadow so it reads as the same family of object. */
  .site-nav {
    position: fixed; z-index: 45;
    inset-inline: var(--gutter) auto;
    top: calc(var(--head-h) + var(--sp-1));
    margin: 0;
    inline-size: max-content;
    min-inline-size: 11rem;
    max-inline-size: calc(100vw - 2 * var(--gutter));
    flex-direction: column; align-items: stretch; gap: 0;
    background: var(--surface);
    border: var(--hairline) solid var(--line);
    border-radius: var(--radius);
    box-shadow: var(--shadow-2);
    padding: var(--sp-2) var(--sp-4);
    transform-origin: top left;
    transform: translateY(-6px) scale(.98);
    opacity: 0; visibility: hidden; pointer-events: none;
    transition: opacity var(--dur-2) var(--ease), transform var(--dur-2) var(--ease), visibility var(--dur-2);
  }
  body.nav-open .site-nav {
    transform: none; opacity: 1; visibility: visible; pointer-events: auto;
  }
  /* The header cluster hugs the right edge on a phone. */
  .site-head__inner { justify-content: space-between; }
  .site-head__end { margin-inline-start: auto; }
  body.nav-open .nav-scrim { display: block; }
  .site-nav a {
    padding-block: var(--sp-3);
    font-size: var(--fs-body);
    border-bottom: var(--hairline) solid var(--line);
  }
  .site-nav a:last-child { border-bottom: 0; }
  :root[data-theme="dark"] .site-nav { border-color: var(--line-strong); }
}

/* Phone portrait and landscape: keep the brand at the left, then place the
   menu trigger after the account controls as the rightmost header control. */
@media (max-width: 30rem), (max-width: 58.25rem) and (max-height: 30rem) {
  .wordmark { order: 1; }
  .site-head__end { order: 2; }
  .nav-toggle { order: 3; }
  .site-nav {
    inset-inline: auto var(--gutter);
    transform-origin: top right;
  }
}

/* 320px: tighten the header so the wordmark, theme toggle, Login and menu all
   fit without the page ever scrolling sideways (G6). Nothing is dropped. */
@media (max-width: 26rem) {
  :root { --gutter: 1rem; --head-h: 3.5rem; }
  .site-head__inner { gap: var(--sp-2); }
  .site-head__end { gap: var(--sp-1); }
  .wordmark { font-size: var(--fs-sm); gap: var(--sp-1); }
  .wordmark__mark { width: 1.5625rem; height: 1.5625rem; }
  .iconbtn { width: 2rem; height: 2rem; }
  .btn--sm { padding: .4375rem .6875rem; }
}

/* ── scrollbars ───────────────────────────────────────────────────────────── */
/* The system bar is too heavy for this palette and it does not follow the
   theme on every platform. Two declarations, because they are two different
   standards and neither is universal:

     scrollbar-width / scrollbar-color  — the CSS standard. Firefox, and Chrome
                                          and Safari on recent versions.
     ::-webkit-scrollbar                — the older WebKit/Blink pseudo-elements,
                                          still what most installed browsers use.

   Both are token-driven, so the bar follows light/dark with everything else.
   `thin` rather than `none`: a scrollbar that cannot be seen is not a style
   choice, it is a usability defect for anyone who scrolls by dragging it.

   The overlay bars macOS uses by default are untouched by any of this and
   remain what a trackpad user sees; these rules are what a user with
   "Show scroll bars: Always" or a mouse gets, and that is exactly the case
   the system bar looked wrong in. */
* {
  scrollbar-width: thin;
  scrollbar-color: var(--line-strong) transparent;
}
::-webkit-scrollbar { width: 10px; height: 10px; }
::-webkit-scrollbar-track { background: transparent; }
::-webkit-scrollbar-thumb {
  background: var(--line-strong);
  border-radius: var(--radius-pill);
  /* The border is transparent and clipped to the padding box, which is what
     insets the thumb from the track without a second element. */
  border: 3px solid transparent;
  background-clip: padding-box;
}
::-webkit-scrollbar-thumb:hover { background: var(--ink-ui); background-clip: padding-box; }
::-webkit-scrollbar-corner { background: transparent; }
