/* ==========================================================================
   Tokens
   --------------------------------------------------------------------------
   Placement rules for this file:
   * A custom property substitutes its var()s on the element that declares
     it, and what inherits is the finished value. So anything that embeds a
     palette stop is declared on ":root, [data-accent]": declared on :root
     alone it would reach a profile card already resolved in the visitor's
     palette, not the owner's accent.
   * Palette rows come after the generic rule they override:
     [data-accent='x'] and [data-accent] have equal specificity.
   * Light overrides use :root[data-theme='light'] (plus
     ":root[data-theme='light'] [data-accent]" when they embed a stop),
     never a bare [data-theme]: hCaptcha slots carry that attribute too.
   * The legacy block at the end keeps the old panel and radius tokens at
     their old values for admin.css and the rules still written against
     them. New rules read only the sections above it.
   ========================================================================== */

/* ==========================================================================
   1. Palette stops
   ========================================================================== */

:root {
  /* The Boundless stops, held on aliases so the four values are written down
     exactly once. Two things need to read them: the site default immediately
     below, and a profile that has chosen the Boundless accent, which needs
     its own [data-accent='boundless'] row further down for the same reason
     the other nine palettes need theirs. Nothing else changes: with
     data-palette unset, --indigo and friends resolve to precisely what they
     always did. */
  --boundless-indigo: #241d57;
  --boundless-violet: #4b3aed;
  --boundless-blue: #3d6fe0;
  --boundless-teal: #17aacb;

  --indigo: var(--boundless-indigo);
  --violet: var(--boundless-violet);
  --blue: var(--boundless-blue);
  --teal: var(--boundless-teal);

  color-scheme: dark;
}

/* The site's other nine accent themes. The picker lives behind the home
   page's Appearance swatch row (see home.ejs and site.js). Each row only
   touches the four source-of-truth stops the bare :root default above uses,
   and every role, gradient and wash below is derived from them, so a row is
   all a palette needs. Boundless itself gets no row: leaving data-palette
   unset is what "Boundless" means.
   Each line carries a second selector, [data-accent='...'], which is how a
   public profile wears a palette of its owner's choosing (see the profile
   block in community.css). data-accent sits on the profile card, a
   descendant of <html>, so it always wins for its own subtree whatever
   data-palette says, with no specificity fight and no !important.
   Void is the one palette that stays dark at every stop. Its stops are the
   app's neutral greys, so the site and the app wear the same void. */
:root[data-palette='nova'], [data-accent='nova'] { --indigo: #3A1808; --violet: #E8732A; --blue: #F2954A; --teal: #FFC66D; }
:root[data-palette='ink'], [data-accent='ink'] { --indigo: #0D0D12; --violet: #5C6472; --blue: #8B93A1; --teal: #D3D9E1; }
:root[data-palette='mono'], [data-accent='mono'] { --indigo: #000000; --violet: #5A5A5C; --blue: #9B9B9E; --teal: #FFFFFF; }
:root[data-palette='reverse'], [data-accent='reverse'] { --indigo: #241D57; --violet: #5B4FE0; --blue: #9FB8E8; --teal: #F5F8FC; }
:root[data-palette='blossom'], [data-accent='blossom'] { --indigo: #4A0E2E; --violet: #E0447B; --blue: #F2789F; --teal: #FFD6E8; }
:root[data-palette='ember'], [data-accent='ember'] { --indigo: #1A0800; --violet: #D62828; --blue: #FF6B1A; --teal: #FFC93F; }
:root[data-palette='mint'], [data-accent='mint'] { --indigo: #052E26; --violet: #12A37E; --blue: #4DDBAB; --teal: #CFF7E7; }
:root[data-palette='royal'], [data-accent='royal'] { --indigo: #1F0A33; --violet: #8A3FE8; --blue: #C08AF2; --teal: #F0C674; }
:root[data-palette='void'], [data-accent='void'] { --indigo: #000000; --violet: #18181C; --blue: #383840; --teal: #5C5C66; }

/* Boundless needs a row here for the same reason the other nine do, even
   though it is also the bare :root default: an accent is the profile owner's
   choice and must not change with the palette the *visitor* happens to have
   picked. It reads the --boundless-* aliases on :root, so this adds no
   eleventh copy of those four values. */
[data-accent='boundless'] {
  --indigo: var(--boundless-indigo);
  --violet: var(--boundless-violet);
  --blue: var(--boundless-blue);
  --teal: var(--boundless-teal);
}

/* ==========================================================================
   2. Roles and everything derived from the stops
   ========================================================================== */

:root, [data-accent] {
  /* Roles. --tint is the single colour for eyebrows, marks and small
     accents; --accent-a/--accent-b are the two-stop accent pair. */
  --tint: var(--violet);
  --accent-a: var(--violet);
  --accent-b: var(--teal);

  --brand-gradient: linear-gradient(135deg, var(--indigo), var(--violet), var(--blue), var(--teal));
  /* The exact stops and angle of #boundless-stroke (see head.ejs), so the
     wordmark's fill and the mark's own stroke are provably one gradient. */
  --mark-gradient: linear-gradient(135deg, var(--violet), var(--blue), var(--teal));
  --wordmark-gradient: var(--mark-gradient);
  --grad-icon: linear-gradient(135deg, var(--indigo) 0%, var(--violet) 38%, var(--blue) 72%, var(--teal) 100%);
  --accent-gradient: linear-gradient(135deg, var(--accent-a), var(--accent-b));
  --grad-select: linear-gradient(135deg, var(--tint), var(--accent-b));

  /* Washes: stops 2 to 4 (the dark anchor dropped), top left to bottom
     right, at the strengths the app's glass uses: 10 neutral, 12 sheet,
     16 neutral hover, 26 card, 30 selected row, 34 accent, 40 accent hover,
     62 tab bubble. */
  --wash-10: linear-gradient(to bottom right, color-mix(in srgb, var(--violet) 10%, transparent), color-mix(in srgb, var(--blue) 10%, transparent), color-mix(in srgb, var(--teal) 10%, transparent));
  --wash-12: linear-gradient(to bottom right, color-mix(in srgb, var(--violet) 12%, transparent), color-mix(in srgb, var(--blue) 12%, transparent), color-mix(in srgb, var(--teal) 12%, transparent));
  --wash-16: linear-gradient(to bottom right, color-mix(in srgb, var(--violet) 16%, transparent), color-mix(in srgb, var(--blue) 16%, transparent), color-mix(in srgb, var(--teal) 16%, transparent));
  --wash-26: linear-gradient(to bottom right, color-mix(in srgb, var(--violet) 26%, transparent), color-mix(in srgb, var(--blue) 26%, transparent), color-mix(in srgb, var(--teal) 26%, transparent));
  --wash-30: linear-gradient(to bottom right, color-mix(in srgb, var(--violet) 30%, transparent), color-mix(in srgb, var(--blue) 30%, transparent), color-mix(in srgb, var(--teal) 30%, transparent));
  --wash-34: linear-gradient(to bottom right, color-mix(in srgb, var(--violet) 34%, transparent), color-mix(in srgb, var(--blue) 34%, transparent), color-mix(in srgb, var(--teal) 34%, transparent));
  --wash-40: linear-gradient(to bottom right, color-mix(in srgb, var(--violet) 40%, transparent), color-mix(in srgb, var(--blue) 40%, transparent), color-mix(in srgb, var(--teal) 40%, transparent));
  --wash-62: linear-gradient(to bottom right, color-mix(in srgb, var(--violet) 62%, transparent), color-mix(in srgb, var(--blue) 62%, transparent), color-mix(in srgb, var(--teal) 62%, transparent));

  /* Accent inks. 68% is the measured share that keeps an eyebrow at 4.5:1
     on the ground in every palette (worst, boundless, 5.24:1). */
  --tint-ink-share: 68%;
  --eyebrow-ink: color-mix(in srgb, var(--tint) var(--tint-ink-share), var(--text));
  --rim-ink: color-mix(in srgb, var(--blue) 22%, white);

  /* Selection fill: the solid --grad-select under a per-palette scrim, with
     a per-palette label (the palette rows below carry the measured values). */
  --select-scrim: rgb(0 0 0 / .20);
  --on-select: #fff;
  --on-select-shadow: var(--label-shadow);

  /* Labels on accent glass. Aliased to --on-select so the same per-palette
     dark-label rows below (nova, blossom, ember, mint, royal) apply here
     too; light re-derives this in section 4. */
  --on-accent: var(--on-select);
  --on-accent-shadow: var(--on-select-shadow);

  /* Opaque stand-in for neutral glass, for cut-out rings that must paint a
     solid colour. */
  --on-glass-solid: color-mix(in srgb, var(--violet) 8%, #1b1c24);

  --shadow-select: 0 3px 15px color-mix(in srgb, var(--tint) 30%, transparent);
}

/* Palette roles. The scrim and label values are measured (white or dark ink
   on the solid selection fill at 4.5:1 across the label's span); do not
   change one without re-measuring. mono's two accent hexes are the app's own
   palette values. */
:root[data-palette='nova'], [data-accent='nova'] { --select-scrim: rgb(255 255 255 / 0); --on-select: var(--ink-on-light); --on-select-shadow: none; }
:root[data-palette='ink'], [data-accent='ink'] { --tint: var(--blue); --accent-b: var(--blue); --select-scrim: rgb(0 0 0 / .24); }
:root[data-palette='mono'], [data-accent='mono'] { --tint: var(--blue); --accent-a: #3A3A3C; --accent-b: #6E6E72; --select-scrim: rgb(0 0 0 / .24); }
:root[data-palette='reverse'], [data-accent='reverse'] { --accent-b: var(--blue); --select-scrim: rgb(0 0 0 / .33); }
:root[data-palette='blossom'], [data-accent='blossom'] { --select-scrim: rgb(255 255 255 / 0); --on-select: var(--ink-on-light); --on-select-shadow: none; }
:root[data-palette='ember'], [data-accent='ember'] { --select-scrim: rgb(255 255 255 / .08); --on-select: var(--ink-on-light); --on-select-shadow: none; }
:root[data-palette='mint'], [data-accent='mint'] { --select-scrim: rgb(255 255 255 / 0); --on-select: var(--ink-on-light); --on-select-shadow: none; }
:root[data-palette='royal'], [data-accent='royal'] { --select-scrim: rgb(255 255 255 / .12); --on-select: var(--ink-on-light); --on-select-shadow: none; }
:root[data-palette='void'], [data-accent='void'] { --tint: var(--teal); --accent-b: var(--blue); --select-scrim: rgb(0 0 0 / 0); }

/* Void's wordmark gradient is near black on the dark ground, so it is lifted
   toward --text (its two stops measure 4.55:1 and 7.2:1 on --bg). */
:root[data-palette='void']:not([data-theme='light']) {
  --wordmark-gradient: linear-gradient(135deg, color-mix(in srgb, var(--teal) 55%, var(--text)), color-mix(in srgb, var(--teal) 80%, var(--text)));
}

/* Void's --violet is near black (#18181C), so the generic dark --link mix in
   section 3 (50% violet, 50% white) still lands under 4.5:1 on ordinary
   prose (measured as low as 3.71 to 3.98:1 on /sources and /signup). Only
   void needs this: every other palette's violet is bright enough that the
   shared formula already clears 4.5:1. Lowering void's own violet share
   leans its link further toward white without touching any other
   palette's link colour. */
:root[data-palette='void']:not([data-theme='light']) {
  --link: color-mix(in srgb, var(--violet) 28%, white);
}

/* ==========================================================================
   3. Grounds, inks and status
   --------------------------------------------------------------------------
   Dark is the default so a page paints correctly before any script has run;
   light overrides it wholesale on the root element.
   ========================================================================== */

:root {
  --bg: #0a0b11;
  --bg-soft: #0f111a;
  --surface: rgba(255, 255, 255, 0.035);
  --surface-solid: #14161f;
  --surface-raised: #1a1d29;
  --border: rgba(255, 255, 255, 0.09);
  --border-strong: rgba(255, 255, 255, 0.16);
  --text: #e9ebf5;
  --text-soft: #b6bccf;
  --text-muted: #868da5;
  /* Secondary text on the ground and on neutral glass, where --text-muted
     measures under 4.5:1 (3.83 to 4.46:1 on dark glass). --ink-2-strong is
     for secondary text on the 26% and 30% washes. 24% still failed on
     several tinted surfaces (blog/docs chips, search-dropdown text,
     source/post-card prose, /signup strings): 32% is the smallest share the
     fuller sweep found clears 4.5:1 everywhere. */
  --ink-2: color-mix(in srgb, var(--text) 32%, var(--text-muted));
  --ink-2-strong: color-mix(in srgb, var(--text) 74%, var(--text-muted));
  --ink-on-light: #14161f;
  /* Channels, for rgb(var(--ink) / a): white on dark, black on light. */
  --ink: 255 255 255;
  --hairline: rgb(var(--ink) / .10);

  /* The measured-safe share of raw --teal to keep when it is used as a
     foreground colour (text or an icon) rather than a fill or a border.
     Started as .eyebrow's own token; every other raw var(--teal)-as-
     foreground rule the site had turned out to share the exact same
     failure, so this is now the one shared token all of them read. 60% is
     the dark figure (worst case void, 6.60:1); the light figure sits in the
     light block below. */
  --teal-ink-share: 60%;
  /* Same shape as --teal-ink-share, for the one place raw --violet is used
     as a foreground colour rather than a fill, border or outline: the
     field-icon glyph's focus-within state (see .field-icon-glyph in
     forms.css). Plain --violet only reaches 2.92:1 against dark --bg at the
     default palette and 1.11:1 for void, both under the 3:1 non-text
     minimum. 58% is the highest share that still clears 3:1 with margin for
     the worst case across all ten palettes: void, 4.04:1. See
     .claude/audit-loop-log.md for the original sweep. */
  --violet-ink-share: 58%;
  /* Same shape again, for the one place the accent gradient is clipped onto
     the flat page as text: .error-code in reading.css, the 404/500 status
     glyph. Void's --violet is near black there and mono and reverse fade a
     stop to near white on the light page. 35% is the highest share that
     still clears 4.5:1 with margin for the worst case across all ten
     palettes in both themes: light theme mono, 5.21:1. */
  --error-code-ink-share: 35%;

  /* Inline links read the live --violet role, so they retint with the
     palette. 50% is the highest violet share (least diluted toward white)
     that clears 4.5:1 with margin across all ten palettes, worst case void
     at 5.85:1 (color-mix output through the canvas contrast method). */
  --link: color-mix(in srgb, var(--violet) 50%, white);
  /* Focus rings need their own token, not plain --violet: raw --violet
     against dark --bg is under the 3:1 non-text floor for most palettes
     (boundless 2.93:1, mono 2.86:1, void 1.11:1). Mixing half --text in
     inherits --text's own contrast in both themes with one formula. Worst
     case across all ten palettes in both themes: void dark, 5.11:1. */
  --focus-ring: color-mix(in srgb, var(--text) 50%, var(--violet));
  --mark: color-mix(in srgb, var(--violet) 28%, transparent);
  --code-bg: #0d0f18;

  /* One error ink for the whole form vocabulary (.field-error and the banner
     uploader's status line both read it). */
  --field-error-ink: #ef7f90;
  /* Status hues: glyphs and dots; the -ink pair is for text on the ground
     and on glass. */
  --status-safe: #3DBE6C;
  --status-caution: #E8A33D;
  --status-danger: #F1595E;
  --status-safe-ink: #3DBE6C;
  --status-caution-ink: #E8A33D;
  /* A white label measures 5.9:1 on it. */
  --danger-fill: #b92f43;
}

:root[data-theme='light'] {
  color-scheme: light;
  --bg: #f6f7fc;
  --bg-soft: #ffffff;
  --surface: rgba(255, 255, 255, 0.75);
  --surface-solid: #ffffff;
  --surface-raised: #ffffff;
  --border: rgba(20, 22, 40, 0.1);
  --border-strong: rgba(20, 22, 40, 0.18);
  --text: #14161f;
  --text-soft: #414659;
  --text-muted: #666d84;
  --ink-2: color-mix(in srgb, var(--text) 16%, var(--text-muted));
  --ink-2-strong: var(--text-soft);
  --ink: 0 0 0;
  --hairline: rgb(var(--ink) / .10);
  /* Lower than the dark 60%: a light page has far less distance between raw
     --teal and the near-white background, so 60% falls as low as 2.39:1
     here (mono, whose teal is pure white). 36% clears 4.5:1 for all ten
     (worst case, also mono, 5.04:1). */
  --teal-ink-share: 36%;
  /* The dark share leaves real headroom here: 85% still clears 3:1, worst
     case nova at 3.67:1. */
  --violet-ink-share: 85%;
  /* Mixed toward black on the light page. 65% is the highest share that
     clears 4.5:1 with margin, worst case nova at 5.88:1. */
  --link: color-mix(in srgb, var(--violet) 65%, black);
  --mark: color-mix(in srgb, var(--violet) 16%, transparent);
  --code-bg: #f2f3fa;
  --field-error-ink: #c32e46;
  /* Darkened from #1f7a45/#8a5a12: both measured marginal (4.04:1, 4.52:1)
     on etched light glass, which forced controls.css to compensate with its
     own color-mix toward --text. These clear 4.5:1 with real margin at the
     source instead. */
  --status-safe-ink: #186b3a;
  --status-caution-ink: #7a4f0f;
}

:root[data-theme='light'], :root[data-theme='light'] [data-accent] {
  /* Eyebrow share on the light ground (worst, mono, 5.17:1). */
  --tint-ink-share: 62%;
  --on-glass-solid: color-mix(in srgb, var(--violet) 5%, #fbfbfe);
  --wordmark-gradient: linear-gradient(135deg, color-mix(in srgb, var(--violet) 88%, black), color-mix(in srgb, var(--blue) 70%, black), color-mix(in srgb, var(--teal) 55%, black));
}

/* ==========================================================================
   4. Glass material
   --------------------------------------------------------------------------
   Components never write a backdrop-filter literal or the tint: they read
   the --mat-* bundles, the rims and the --blur-* filters below. That keeps
   one switch, --glass-on (overrides.css flips it for reduced transparency,
   more contrast, no backdrop-filter support), in charge of every surface.
   ========================================================================== */

:root {
  /* 1 draws real glass; 0 turns every blur off, and the switches in
     overrides.css make the tint opaque at the same time. */
  --glass-on: 1;
  --glass-tint: rgb(255 255 255 / .07);
  --tint-layer: linear-gradient(var(--glass-tint), var(--glass-tint));
  /* The app's saturate(1.4) and its frost sigmas (CSS blur() takes the same
     sigma). */
  --glass-sat: 1.4;
  /* Dark theme only: darkens whatever sits behind a floating chrome surface
     (header, tab dock, tab bubble, popovers, sheets) before it blurs, so a
     bright page under glass cannot wash a light-on-glass label or icon out
     to under 4.5:1 (or 3:1 for an icon-only control). Light theme's own
     chrome is bright already and needs no darkening, so it resets this to a
     no-op in section 3's light block. */
  --glass-chrome-brightness: 0.08;
  --frost-bubble: 2px;
  --frost-fill: 3px;
  --frost-list: 4px;
  --frost-card: 6px;
  --frost-dock: 14px;
  --frost-chrome: 16px;
  --frost-pane: 16px;
  --frost-sheet: 18px;
  --frost-scrim: 6px;
  /* Tone layers under text-heavy glass (black on dark, white on light). */
  --tone-chrome: rgb(0 0 0 / .44);
  --tone-dock: rgb(0 0 0 / .40);
  --tone-pane: rgb(0 0 0 / .56);
  --tone-sheet: rgb(0 0 0 / .60);
  /* Etched: anything drawn on a glass surface (no glass in glass). */
  --etch-fill: rgb(var(--ink) / .07);
  --etch-line: rgb(var(--ink) / .14);
  --etch-fill-hover: rgb(var(--ink) / .11);
  --etch-line-hover: rgb(var(--ink) / .22);
  --row-hover: rgb(var(--ink) / .05);
  --row-press: rgb(var(--ink) / .08);

  /* Complete backdrop-filter values, one per surface kind. */
  --blur-bubble: saturate(calc(1 + (var(--glass-sat) - 1) * var(--glass-on))) brightness(var(--glass-chrome-brightness)) blur(calc(var(--frost-bubble) * var(--glass-on)));
  --blur-fill: saturate(calc(1 + (var(--glass-sat) - 1) * var(--glass-on))) blur(calc(var(--frost-fill) * var(--glass-on)));
  --blur-list: saturate(calc(1 + (var(--glass-sat) - 1) * var(--glass-on))) blur(calc(var(--frost-list) * var(--glass-on)));
  --blur-card: saturate(calc(1 + (var(--glass-sat) - 1) * var(--glass-on))) blur(calc(var(--frost-card) * var(--glass-on)));
  --blur-dock: saturate(calc(1 + (var(--glass-sat) - 1) * var(--glass-on))) brightness(var(--glass-chrome-brightness)) blur(calc(var(--frost-dock) * var(--glass-on)));
  --blur-chrome: saturate(calc(1 + (var(--glass-sat) - 1) * var(--glass-on))) brightness(var(--glass-chrome-brightness)) blur(calc(var(--frost-chrome) * var(--glass-on)));
  --blur-pane: saturate(calc(1 + (var(--glass-sat) - 1) * var(--glass-on))) brightness(var(--glass-chrome-brightness)) blur(calc(var(--frost-pane) * var(--glass-on)));
  --blur-sheet: saturate(calc(1 + (var(--glass-sat) - 1) * var(--glass-on))) brightness(var(--glass-chrome-brightness)) blur(calc(var(--frost-sheet) * var(--glass-on)));
  --blur-scrim: blur(calc(var(--frost-scrim) * var(--glass-on)));

  /* Shadows only for floating chrome and artwork, never on cards. Flutter
     blur b is CSS blur 1.155 b + 1. */
  --shadow-float: 0 8px 26px -12px rgb(0 0 0 / .45);
  --shadow-bubble: 0 6px 19.5px rgb(0 0 0 / .40);
  --shadow-cover: 0 6px 17px rgb(0 0 0 / .45);
  --shadow-pop: 0 18px 48px -18px rgb(0 0 0 / .60);
  --shadow-device: 0 40px 80px -40px rgb(0 0 0 / .70);
  /* The filter form, for device frames: drop-shadow() has no spread. */
  --drop-device: drop-shadow(0 28px 36px rgb(0 0 0 / .45));
  /* White labels over accent glass. */
  --label-shadow: 0 0 8px rgb(0 0 0 / .35);
}

:root, [data-accent] {
  /* The rim: 1px of inset light, dim along the top and left, bright along
     the bottom and right (the app lights glass from 225 degrees), tinted by
     the palette. Inset shadows follow border-radius and need no
     pseudo-element. */
  --glass-rim:
    inset 0 1px 0 color-mix(in srgb, var(--rim-ink) 36%, transparent),
    inset 1px 0 0 color-mix(in srgb, var(--rim-ink) 20%, transparent),
    inset 0 -1px 0 color-mix(in srgb, var(--rim-ink) 48%, transparent),
    inset -1px 0 0 color-mix(in srgb, var(--rim-ink) 38%, transparent);
  /* The hero-grade ring for the header capsule and the tab bubble only,
     painted by a masked ::after. */
  --glass-rim-grad: linear-gradient(135deg,
    color-mix(in srgb, var(--rim-ink) 55%, transparent) 0%, color-mix(in srgb, var(--rim-ink) 16%, transparent) 22%,
    color-mix(in srgb, var(--rim-ink) 4%, transparent) 50%, color-mix(in srgb, var(--rim-ink) 20%, transparent) 78%,
    color-mix(in srgb, var(--rim-ink) 65%, transparent) 100%);
  --rim-etched: inset 0 0 0 1px var(--etch-line);
  --rim-quiet: inset 0 0 0 1px rgb(var(--ink) / .20), inset 0 1px 0 rgb(255 255 255 / .10);

  /* Material bundles: a surface writes background: var(--mat-x) and nothing
     else. The wash always sits over the tint, so nothing reads colourless. */
  --mat-neutral: var(--wash-10), var(--tint-layer);
  --mat-neutral-hover: var(--wash-16), var(--tint-layer);
  --mat-card: var(--wash-26), var(--tint-layer);
  --mat-row-selected: var(--wash-30);
  --mat-select: linear-gradient(var(--select-scrim), var(--select-scrim)), var(--grad-select);
  /* D2: the vivid per-palette gradient, not a dim wash, so a dark-theme
     primary button, current-page capsule, nav marker and pressed tag read
     as saturated as the app's own selected state (light already read this
     as --mat-select below; this mirrors it for dark). */
  --mat-accent: var(--mat-select);
  --mat-accent-hover: var(--mat-select);
  --mat-bubble: var(--wash-62), var(--tint-layer);
  --mat-quiet: linear-gradient(rgb(var(--ink) / .16), rgb(var(--ink) / .16));
  --mat-etched: linear-gradient(var(--etch-fill), var(--etch-fill));
  --mat-etched-hover: linear-gradient(var(--etch-fill-hover), var(--etch-fill-hover));
  --mat-chrome: var(--wash-10), linear-gradient(var(--tone-chrome), var(--tone-chrome)), var(--tint-layer);
  --mat-dock: var(--wash-10), linear-gradient(var(--tone-dock), var(--tone-dock)), var(--tint-layer);
  --mat-pane: var(--wash-10), linear-gradient(var(--tone-pane), var(--tone-pane)), var(--tint-layer);
  --mat-sheet: var(--wash-12), linear-gradient(var(--tone-sheet), var(--tone-sheet)), var(--tint-layer);
}

:root[data-theme='light'] {
  /* A whitish frost: the app's black 6% leaves --ink-2 under 4.5:1 on light
     glass. */
  --glass-tint: rgb(255 255 255 / .58);
  /* Light chrome measures fine without darkening (D1/F-2 is a dark-theme-only
     failure), so this resets to a no-op here. */
  --glass-chrome-brightness: 1;
  --tone-chrome: rgb(255 255 255 / .52);
  --tone-dock: rgb(255 255 255 / .50);
  --tone-pane: rgb(255 255 255 / .60);
  --tone-sheet: rgb(255 255 255 / .60);
  --etch-fill: rgb(var(--ink) / .05);
  --etch-line: rgb(var(--ink) / .12);
  --etch-fill-hover: rgb(var(--ink) / .08);
  --etch-line-hover: rgb(var(--ink) / .18);
  --row-hover: rgb(var(--ink) / .04);
  --row-press: rgb(var(--ink) / .07);
  --shadow-float: 0 8px 26px -12px rgb(20 22 40 / .22);
  --shadow-bubble: 0 6px 19.5px rgb(20 22 40 / .20);
  --shadow-cover: 0 6px 17px rgb(0 0 0 / .14);
  --shadow-pop: 0 18px 48px -18px rgb(20 22 40 / .25);
  --shadow-device: 0 40px 80px -40px rgb(20 22 40 / .35);
  --drop-device: drop-shadow(0 28px 36px rgb(20 22 40 / .22));
}

:root[data-theme='light'], :root[data-theme='light'] [data-accent] {
  --glass-rim: inset 0 1px 0 rgb(255 255 255 / .90), inset 0 0 0 1px color-mix(in srgb, var(--indigo) 14%, transparent);
  --glass-rim-grad: linear-gradient(135deg, rgb(255 255 255 / .95), rgb(255 255 255 / .35) 40%, color-mix(in srgb, var(--indigo) 16%, transparent));
  --rim-quiet: 0 1px 3px rgb(20 22 40 / .16), inset 0 0 0 1px rgb(20 22 40 / .08);
  --mat-quiet: linear-gradient(#fff, #fff);
  /* A 34% wash over white is pastel and fails white labels, so accent
     surfaces take the solid selection fill in light. */
  --mat-accent: var(--mat-select);
  --mat-accent-hover: var(--mat-select);
  --mat-bubble: var(--mat-select);
  --on-accent: var(--on-select);
  --on-accent-shadow: var(--on-select-shadow);
}

/* ==========================================================================
   5. Shape
   --------------------------------------------------------------------------
   The app's ladder, glassRadius(r) = 3r + 8. Any control whose height is at
   most twice its radius renders as a capsule; that is intended.
   ========================================================================== */

:root {
  --r-chip: 17px;      /* chips, small capsules (a capsule at 34px tall) */
  --r-control: 20px;   /* buttons, fields, segmented track, code blocks, callouts, tables */
  --r-group: 26px;     /* groups, list rows as cards, panes, popovers */
  --r-card: 32px;      /* cards, sheets, dialogs, banners */
  --r-hero: 38px;      /* large feature objects */
  --r-pill: 999px;
  --r-cover: 16px;
  --r-cover-sm: 12px;
  --r-icon: 10px;
  --r-icon-lg: 14px;
  --r-inline: 7px;     /* inline code, kbd */
}

/* ==========================================================================
   6. Space and layout
   ========================================================================== */

:root {
  --s-2: 2px; --s-4: 4px; --s-6: 6px; --s-8: 8px; --s-10: 10px; --s-12: 12px; --s-13: 13px; --s-14: 14px;
  --s-16: 16px; --s-18: 18px; --s-20: 20px; --s-22: 22px; --s-24: 24px; --s-28: 28px; --s-32: 32px;
  --s-40: 40px; --s-48: 48px; --s-56: 56px; --s-64: 64px; --s-80: 80px; --s-96: 96px;

  --gutter: 12px;             /* admin and the older grids, which also set it locally */
  --gutter-page: clamp(20px, 4vw, 32px);
  --shell: 1200px;            /* marketing and footer width */
  --reading: 720px;           /* the older name of --col-reading */
  --col-reading: 720px;       /* prose measure */
  --col-page: 760px;          /* list pages */
  --col-wide: 1040px;         /* account, profile */
  --page-top: clamp(28px, 4vw, 48px);
  --section-gap: clamp(64px, 9vw, 112px);

  /* Floating header capsule. 12 + 56 + 16 = 84, the scroll-padding-top in
     base.css. --header-height itself stays measured by site.js. */
  --header-top: 12px;
  --header-h: 56px;
  --header-inset: clamp(12px, 3vw, 28px);
  --header-clear: 68px;
  /* Phone tab bar. */
  --tabbar-w: 340px;
  --tabbar-h: 56px;
  --tabbar-bottom: 10px;
  --tabbar-clear: calc(var(--tabbar-h) + var(--tabbar-bottom) + 24px + env(safe-area-inset-bottom));
}

/* ==========================================================================
   7. Type
   ========================================================================== */

:root {
  /* One bespoke family (Bricolage Grotesque, self-hosted, variable 300 to
     800 in one file; see fonts.css), the same face the app uses. Falls back
     to the system stack if it ever fails to load. */
  --font: 'Boundless Display', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;
  --mono: ui-monospace, SFMono-Regular, 'SF Mono', Menlo, Consolas, 'Liberation Mono', monospace;
  /* Headings are Bricolage too; the name stays so older rules that read it
     land on the same family. */
  --font-display: var(--font);
  /* Reserved for the one permitted serif numeral; nothing reads it. */
  --font-serif: ui-serif, 'New York', Georgia, serif;
  --font-modern: var(--font);

  /* The type scale, in px at a phone and at a desktop width:
       --step--3      10.5        kickers, bottom meta line
       --step--2      12          eyebrows, small chips
       --step-chip    12.5        chips, segmented labels
       --step--1      13          meta, hints, captions, breadcrumbs
       --step-0s      15          UI labels, buttons, card copy, rows
       --step-0       16 to 17    body, prose
       --step-0l      18          ledes on list pages, large card titles
       --step-1       19 to 21    hero tagline, h3
       --step-2       24 to 32    h2
       --step-3       32 to 48    h1
       --step-display 48 to 88    the hero wordmark only
     --step-4 is an alias of --step-3 for older rules. */
  --step--3: 0.65625rem;
  --step--2: 0.75rem;
  --step-chip: 0.78125rem;
  --step--1: 0.8125rem;
  --step-0s: 0.9375rem;
  --step-0: clamp(1rem, 0.96rem + 0.2vw, 1.0625rem);
  --step-0l: 1.125rem;
  --step-1: clamp(1.1875rem, 1.1rem + 0.35vw, 1.3125rem);
  --step-2: clamp(1.5rem, 1.25rem + 1vw, 2rem);
  --step-3: clamp(2rem, 1.45rem + 2.2vw, 3rem);
  --step-4: var(--step-3);
  --step-display: clamp(3rem, 1.1rem + 7vw, 5.5rem);

  /* The app renders at most 700; 800 is for the hero wordmark alone. */
  --weight-body: 400;
  --weight-ui: 600;
  --weight-title: 700;
  --weight-display: 800;
  /* One tracking for every uppercase label (eyebrows, rail headings, table
     headers, kind chips); titles track tighter as they grow. */
  --track-caps: 0.11em;
  --track-label: 0.136em;
  --track-meta: 0.14em;
  --track-title: -0.018em;
  --track-h2: -0.022em;
  --track-h1: -0.028em;
  --track-display: -0.035em;
  --lh-body: 1.65;
  --lh-ui: 1.4;
  --lh-title: 1.15;
}

/* ==========================================================================
   8. Motion
   ========================================================================== */

:root {
  --ease: cubic-bezier(0.4, 0, 0.2, 1);
  --ease-out: cubic-bezier(0.16, 1, 0.3, 1);
  /* The strong ease-out the verified tick, the role marks and the app
     screens write out longhand (see the comment above @keyframes
     tick-seal-in), named so newer choreography can reference it. --ease-out
     stays the everyday entrance; this one is for a gesture that should land
     rather than simply arrive. */
  --ease-entrance: cubic-bezier(0.23, 1, 0.32, 1);
  /* Flutter's own curves. */
  --ease-decel: cubic-bezier(0, 0, .58, 1);          /* easeOut */
  --ease-accel: cubic-bezier(.42, 0, 1, 1);          /* easeIn */
  --ease-out-cubic: cubic-bezier(.215, .61, .355, 1);
  --ease-out-back: cubic-bezier(.175, .885, .32, 1.275);
  /* Half a sine wave: an alternate animation on this curve traces a cosine. */
  --ease-sine: cubic-bezier(.37, 0, .63, 1);
  --ease-marker: cubic-bezier(0, 1, .5, 1);
  /* The tab bubble's spring (mass 1, stiffness 320, damping ratio 0.62) over
     --dur-spring; the @supports block below swaps in the sampled curve. */
  --ease-spring: var(--ease-out-back);

  --dur-press: 90ms;
  --dur-release: 220ms;
  --dur-chip: 180ms;
  --dur-switch: 200ms;
  --dur-fade: 200ms;
  --dur-marker: 250ms;
  --dur-chrome: 280ms;
  --dur-segment: 320ms;
  --dur-toast: 360ms;
  --dur-reveal: 450ms;
  --dur-spring: 600ms;
  --dur-entrance: 900ms;

  /* The ambient field (base.css): x repeats every 48s and y every 48 / 0.7,
     each as an alternate animation over half its period. */
  --ambient-period: 48s;
  --ambient-x-half: calc(var(--ambient-period) / 2);
  --ambient-y-half: calc(var(--ambient-period) / 1.4);
  --ambient-peak: 5.8%;
}

/* Safari before 17.2 has no linear(); it keeps the ease-out-back fallback. */
@supports (transition-timing-function: linear(0, 1)) {
  :root {
    --ease-spring: linear(0, 0.082 4%, 0.268 8%, 0.486 12%, 0.688 17%, 0.851 21%, 0.968 25%, 1.039 29%, 1.075 33%, 1.084 38%, 1.076 42%, 1.060 46%, 1.042 50%, 1.025 54%, 1.012 58%, 1.002 62%, 0.997 67%, 0.994 71%, 0.993 75%, 0.994 79%, 0.995 83%, 0.997 88%, 0.998 92%, 0.999 96%, 1);
  }
}

/* ==========================================================================
   9. Layers
   ========================================================================== */

:root {
  --z-ambient: -1;
  --z-local-nav: 50;
  --z-scrim: 90;
  --z-header: 100;
  --z-tabbar: 100;
  --z-cookie: 110;
  --z-dropdown: 120;
  --z-popover: 120;
  --z-skip: 200;
  --z-modal: 500;
  --z-wuji: 600;
}

/* ==========================================================================
   10. Legacy
   --------------------------------------------------------------------------
   The panel material and the old radius scale, at their old values, for
   admin.css and for the rules not yet rewritten against the sections above.
   Nothing new reads these.
   ========================================================================== */

:root {
  --radius-sm: 4px;
  --radius: 6px;
  --radius-lg: 8px;
  --radius-xl: 10px;

  /* The panel material: a ground a few steps lighter than the page, a
     hairline edge, a hairline of light along the top, a soft shadow down
     onto the ground, and the tinted band of light the sweeps carry. Mixed
     from --text, --bg and the palette stops so every theme and palette gets
     its own. */
  --panel-ground: color-mix(in srgb, var(--text) 2.5%, var(--bg));
  --panel-edge: color-mix(in srgb, var(--text) 10%, transparent);
  --panel-edge-light: color-mix(in srgb, var(--text) 7%, transparent);
  --panel-sheen: color-mix(in srgb, var(--text) 7%, transparent);
  --panel-shadow: rgba(0, 0, 0, 0.75);
  /* --panel-lift is admin.css's own box-shadow; it is built from
     --panel-shadow and --panel-edge-light above, so both stay even though
     neither is read directly outside this file. */
  --panel-lift: 0 10px 26px -16px var(--panel-shadow), inset 0 1px 0 var(--panel-edge-light);
}

:root[data-theme='light'] {
  --panel-ground: color-mix(in srgb, var(--indigo) 9%, var(--bg));
  --panel-edge: color-mix(in srgb, var(--indigo) 14%, transparent);
  --panel-edge-light: rgba(255, 255, 255, 0.7);
  --panel-sheen: rgba(255, 255, 255, 0.28);
  --panel-shadow: rgba(20, 22, 40, 0.28);
}

/* Void's stops sit within a few steps of the page colour, so its panels and
   the ground would meet on the hairline alone: a stronger seam for that one
   palette, in either theme. After the light block above on purpose: both
   selectors weigh the same, and void has to win in light too. */
:root[data-palette='void'] {
  --panel-ground: color-mix(in srgb, var(--text) 7%, var(--bg));
  --panel-edge: color-mix(in srgb, var(--text) 16%, transparent);
}
