/* ==========================================================================
   Polymathes — shared stylesheet
   Single file, no build step, no external dependencies.
   ========================================================================== */

/* --- Design tokens ------------------------------------------------------- */
:root {
  --bg: #080c11;
  --bg-2: #0d141d;
  --panel: #121b26;
  --panel-2: #17222f;
  --line: #23313f;
  --line-soft: #1a2532;
  --ink: #e8eef6;
  --muted: #a9bacd;
  --muted-2: #93a7bd;

  --accent: #00ffff;
  --accent-2: #00ff8b;
  --warn: #ff7b54;

  /* Appearance tokens. --accent-text is the accent as *text*: identical to the
     fill in dark mode, and darkened on a light background (see below) so links
     and labels keep their contrast whatever the accent is. colour-scheme drives
     the browser's own widgets: scrollbars, form controls, autofill. */
  /* Declared on body below, not here: a custom property is substituted where it
     is *declared*, so `var(--accent)` on :root would bake in the root accent and
     every scheme would keep cyan links. */
  --accent-text: var(--accent);
  --accent-2-text: var(--accent-2);
  --on-accent: #05121a;      /* text drawn on an accent fill */
  --ink-strong: #ffffff;
  --panel-deep: #0b131c;     /* far end of the card/panel gradients */
  --well: #0a1119;           /* inputs, artwork plates */
  --chip-bg: #0a1119;
  --chip-ink: #cfe9ff;
  --pre-bg: #070c12;
  --pre-ink: #d7e6f5;
  --th-bg: #0d151e;
  --row-hover: #0f1721;
  --sink: #060a0f;           /* footer */
  color-scheme: dark;

  --radius: 14px;
  --radius-sm: 9px;
  --shadow: 0 20px 44px -26px rgba(0, 0, 0, 0.9);
  --maxw: 1120px;

  --mono: ui-monospace, SFMono-Regular, "SF Mono", Menlo, Consolas, "Liberation Mono", monospace;
  --sans: system-ui, -apple-system, "Segoe UI", Roboto, Inter, "Helvetica Neue", Arial, sans-serif;
}

/* --- Light background / dark foreground (the default light page) ---------- */
/* The first of the two hand-written pages: white background, near-black ink.
   A chosen scheme replaces every surface below from its own solved step, so
   this block only has to be right on its own.  --on-accent is the near-black
   drawn on an accent fill; --accent-text is the accent darkened by mixing it
   toward --on-accent, which keeps its hue where mixing toward black would not. */
body[data-mode="light"] {
  --bg: #f2f6fa;
  --bg-2: #e8eef5;
  --panel: #ffffff;
  --panel-2: #f7fafc;
  --line: #ccd8e3;
  --line-soft: #e1e9f1;
  --ink: #0b1620;
  --muted: #44546a;
  --muted-2: #506178;
  --warn: #a8451b;

  --shadow: 0 18px 40px -26px rgba(14, 32, 52, 0.35);
  --on-accent: #05121a;
  --ink-strong: #050f18;
  --panel-deep: #eef3f8;
  --well: #f1f5f9;
  --chip-bg: #edf3f9;
  --chip-ink: #17384c;
  --pre-bg: #f2f6fa;
  --pre-ink: #12303f;
  --th-bg: #edf3f8;
  --row-hover: #f4f8fb;
  --sink: #e9f0f6;

  /* 38% accent + near-black, the same mix palette.py proves for the weakest
     accent the picker offers (see LIGHT_ACCENT_TEXT_W). */
  --accent-text: color-mix(in srgb, var(--accent) 38%, #05121a);
  --accent-2-text: color-mix(in srgb, var(--accent-2) 38%, #05121a);
  color-scheme: light;
}

/* Per-section accent themes (set via data-section on <body>, or on a single
   component -- the three illustrated cards on the home page each keep the
   accent of the page they lead to, which is also what the heading beside them
   says). The selector matches any element, not just <body>. */
[data-section="networking"]  { --accent: #00ffff; --accent-2: #00ff8b; }
[data-section="audio"]       { --accent: #00ff8b; --accent-2: #00ffff; }
[data-section="graphics"]    { --accent: #c77dff; --accent-2: #00ffff; }
[data-section="photography"] { --accent: #ffc857; --accent-2: #ff9d5c; }
[data-section="contact"]     { --accent: #7dd3fc; --accent-2: #c77dff; }
[data-section="about"]       { --accent: #00ff8b; --accent-2: #00ffff; }

/* A card that keeps its own section accent re-derives the as-text variant from
   *its* accent rather than inheriting the body's.  These two are what the two
   default pages use; on a scheme page the generated rules after the schemes
   below win, because a neon is legible on one background and not on another. */
.domain-card[data-section] { --accent-text: var(--accent); --accent-2-text: var(--accent-2); }
body[data-mode="light"] .domain-card[data-section] {
  --accent-text: color-mix(in srgb, var(--accent) 38%, #05121a);
  --accent-2-text: color-mix(in srgb, var(--accent-2) 38%, #05121a);
}

/* --- 1024 site colour schemes --------------------------------------------- */
/* Generated by palette.py: 128 families x 8 steps = 1024 schemes.  A family is a
   hue with a saturation; a step is a target relative luminance.  Steps 1-4 are
   light pages (dark ink), steps 5-8 are dark pages (light ink); the mid greys
   in between are deliberately skipped, because no single ink clears AA there.
   The families are in four sections: Neutral (the greys), Spectrum (the evenly
   spaced stops around the hue circle), Named (the everyday colour words) and
   Flat (tinta unita - those same hues at full saturation, mixed with nothing but
   white or black).  "plain" ignores its hue: its eight steps are pure white,
   greys and pure black, for anyone who wants a normal-looking site.

   Every value below is solved, not eyeballed: the background, its surfaces,
   both accents, the warn colour and the accents as text are all measured with
   the WCAG formula before the file is written, and palette.py refuses to emit
   anything that does not clear AA on every surface it is drawn on.  The duty
   rules (which surfaces are recessed, how ink is mixed down for --muted) live
   in the generated region too, so the light and dark steps of one family are a
   single rule each.

   The twelve colours of each scheme are not here: they are rows in
   js/palette-data.js, because 1024 rules of twelve values would be 235 kB of
   CSS.  What is here is everything that is the same for a whole step, so 1024
   schemes cost two rules and 1024 tones a handful. */
/* The accent as text, re-substituted here so it follows the chosen scheme. */
body {
  --accent-text: var(--accent);
  --accent-2-text: var(--accent-2);
}

/* palette:schemes:start */
/* generated by palette.py - the step rules every scheme shares.
   The twelve colours of a scheme are applied by js/main.js from
   js/palette-data.js, so 1024 schemes cost one data file instead of
   1024 stylesheet rules.  What is left here depends only on the step,
   never on the family.
   */


/* Steps 5-8 are dark pages: light ink, white text on an accent fill. */
body[data-theme$="-5"], body[data-theme$="-6"], body[data-theme$="-7"], body[data-theme$="-8"] {
  --muted-base: var(--panel-2);
  --muted: color-mix(in srgb, var(--ink) var(--muted-w, 79.9%), var(--muted-base));
  --muted-2: color-mix(in srgb, var(--ink) var(--muted-2-w, 79.9%), var(--muted-base));
  --ink-strong: color-mix(in srgb, var(--ink) 75%, #ffffff);
  --chip-ink: var(--ink);
  --pre-ink: var(--ink);
  color-scheme: dark;
  --panel-deep: color-mix(in srgb, var(--panel) 70%, #000000);
  --well: color-mix(in srgb, var(--bg) 85%, #000000);
  --chip-bg: color-mix(in srgb, var(--panel) 78%, #000000);
  --pre-bg: color-mix(in srgb, var(--bg) 92%, #000000);
  --th-bg: color-mix(in srgb, var(--panel) 88%, #000000);
  --row-hover: color-mix(in srgb, var(--panel) 84%, #000000);
  --sink: color-mix(in srgb, var(--bg) 80%, #000000);
}

/* Steps 1-4 are light pages: dark ink, near-black text on an accent fill. */
body[data-theme$="-1"], body[data-theme$="-2"], body[data-theme$="-3"], body[data-theme$="-4"] {
  --muted-base: #ffffff;
  --muted: color-mix(in srgb, var(--ink) var(--muted-w, 61.8%), var(--muted-base));
  --muted-2: color-mix(in srgb, var(--ink) var(--muted-2-w, 61.8%), var(--muted-base));
  --ink-strong: color-mix(in srgb, var(--ink) 75%, #000000);
  --chip-ink: var(--ink);
  --pre-ink: var(--ink);
  color-scheme: light;
  --panel-deep: color-mix(in srgb, var(--panel) 96%, #000000);
  --well: color-mix(in srgb, var(--bg) 97.5%, #000000);
  --chip-bg: color-mix(in srgb, var(--panel) 97%, #000000);
  --pre-bg: color-mix(in srgb, var(--bg) 97%, #000000);
  --th-bg: color-mix(in srgb, var(--panel) 97.5%, #000000);
  --row-hover: color-mix(in srgb, var(--panel) 95%, #000000);
  --sink: color-mix(in srgb, var(--bg) 96%, #000000);
}
/* palette:schemes:end */

/* palette:cards:start */
/* generated by palette.py - the per-section accents as text inside a card.
   Each section keeps its own hue, but re-solved for the card's gradient at
   this step: a neon reads on a near-black card and not on a pale one.
   */

body[data-theme$="-1"] .domain-card[data-section="networking"] { --accent-text: #007b7b; --accent-2-text: #007e45; }
body[data-theme$="-1"] .domain-card[data-section="audio"] { --accent-text: #007e45; --accent-2-text: #007b7b; }
body[data-theme$="-1"] .domain-card[data-section="graphics"] { --accent-text: #9e1fff; --accent-2-text: #007b7b; }
body[data-theme$="-1"] .domain-card[data-section="photography"] { --accent-text: #956500; --accent-2-text: #bb4a00; }
body[data-theme$="-1"] .domain-card[data-section="contact"] { --accent-text: #0475aa; --accent-2-text: #9e1fff; }
body[data-theme$="-1"] .domain-card[data-section="about"] { --accent-text: #007e45; --accent-2-text: #007b7b; }
body[data-theme$="-2"] .domain-card[data-section="networking"] { --accent-text: #007575; --accent-2-text: #007a42; }
body[data-theme$="-2"] .domain-card[data-section="audio"] { --accent-text: #007a42; --accent-2-text: #007575; }
body[data-theme$="-2"] .domain-card[data-section="graphics"] { --accent-text: #970fff; --accent-2-text: #007575; }
body[data-theme$="-2"] .domain-card[data-section="photography"] { --accent-text: #8e6000; --accent-2-text: #b34800; }
body[data-theme$="-2"] .domain-card[data-section="contact"] { --accent-text: #046fa3; --accent-2-text: #970fff; }
body[data-theme$="-2"] .domain-card[data-section="about"] { --accent-text: #007a42; --accent-2-text: #007575; }
body[data-theme$="-3"] .domain-card[data-section="networking"] { --accent-text: #006b6b; --accent-2-text: #006e3c; }
body[data-theme$="-3"] .domain-card[data-section="audio"] { --accent-text: #006e3c; --accent-2-text: #006b6b; }
body[data-theme$="-3"] .domain-card[data-section="graphics"] { --accent-text: #8800f0; --accent-2-text: #006b6b; }
body[data-theme$="-3"] .domain-card[data-section="photography"] { --accent-text: #825700; --accent-2-text: #a34100; }
body[data-theme$="-3"] .domain-card[data-section="contact"] { --accent-text: #036594; --accent-2-text: #8800f0; }
body[data-theme$="-3"] .domain-card[data-section="about"] { --accent-text: #006e3c; --accent-2-text: #006b6b; }
body[data-theme$="-4"] .domain-card[data-section="networking"] { --accent-text: #006161; --accent-2-text: #006437; }
body[data-theme$="-4"] .domain-card[data-section="audio"] { --accent-text: #006437; --accent-2-text: #006161; }
body[data-theme$="-4"] .domain-card[data-section="graphics"] { --accent-text: #7c00db; --accent-2-text: #006161; }
body[data-theme$="-4"] .domain-card[data-section="photography"] { --accent-text: #754f00; --accent-2-text: #933b00; }
body[data-theme$="-4"] .domain-card[data-section="contact"] { --accent-text: #035c86; --accent-2-text: #7c00db; }
body[data-theme$="-4"] .domain-card[data-section="about"] { --accent-text: #006437; --accent-2-text: #006161; }
body[data-theme$="-5"] .domain-card[data-section="networking"] { --accent-text: #00ffff; --accent-2-text: #00ff8b; }
body[data-theme$="-5"] .domain-card[data-section="audio"] { --accent-text: #00ff8b; --accent-2-text: #00ffff; }
body[data-theme$="-5"] .domain-card[data-section="graphics"] { --accent-text: #e1b8ff; --accent-2-text: #00ffff; }
body[data-theme$="-5"] .domain-card[data-section="photography"] { --accent-text: #ffc857; --accent-2-text: #ffb88a; }
body[data-theme$="-5"] .domain-card[data-section="contact"] { --accent-text: #7dd3fc; --accent-2-text: #e1b8ff; }
body[data-theme$="-5"] .domain-card[data-section="about"] { --accent-text: #00ff8b; --accent-2-text: #00ffff; }
body[data-theme$="-6"] .domain-card[data-section="networking"] { --accent-text: #00ffff; --accent-2-text: #00ff8b; }
body[data-theme$="-6"] .domain-card[data-section="audio"] { --accent-text: #00ff8b; --accent-2-text: #00ffff; }
body[data-theme$="-6"] .domain-card[data-section="graphics"] { --accent-text: #cc89ff; --accent-2-text: #00ffff; }
body[data-theme$="-6"] .domain-card[data-section="photography"] { --accent-text: #ffc857; --accent-2-text: #ff9d5c; }
body[data-theme$="-6"] .domain-card[data-section="contact"] { --accent-text: #7dd3fc; --accent-2-text: #cc89ff; }
body[data-theme$="-6"] .domain-card[data-section="about"] { --accent-text: #00ff8b; --accent-2-text: #00ffff; }
body[data-theme$="-7"] .domain-card[data-section="networking"] { --accent-text: #00ffff; --accent-2-text: #00ff8b; }
body[data-theme$="-7"] .domain-card[data-section="audio"] { --accent-text: #00ff8b; --accent-2-text: #00ffff; }
body[data-theme$="-7"] .domain-card[data-section="graphics"] { --accent-text: #c77dff; --accent-2-text: #00ffff; }
body[data-theme$="-7"] .domain-card[data-section="photography"] { --accent-text: #ffc857; --accent-2-text: #ff9d5c; }
body[data-theme$="-7"] .domain-card[data-section="contact"] { --accent-text: #7dd3fc; --accent-2-text: #c77dff; }
body[data-theme$="-7"] .domain-card[data-section="about"] { --accent-text: #00ff8b; --accent-2-text: #00ffff; }
body[data-theme$="-8"] .domain-card[data-section="networking"] { --accent-text: #00ffff; --accent-2-text: #00ff8b; }
body[data-theme$="-8"] .domain-card[data-section="audio"] { --accent-text: #00ff8b; --accent-2-text: #00ffff; }
body[data-theme$="-8"] .domain-card[data-section="graphics"] { --accent-text: #c77dff; --accent-2-text: #00ffff; }
body[data-theme$="-8"] .domain-card[data-section="photography"] { --accent-text: #ffc857; --accent-2-text: #ff9d5c; }
body[data-theme$="-8"] .domain-card[data-section="contact"] { --accent-text: #7dd3fc; --accent-2-text: #c77dff; }
body[data-theme$="-8"] .domain-card[data-section="about"] { --accent-text: #00ff8b; --accent-2-text: #00ffff; }
/* palette:cards:end */

/* palette:scheme-chips:start */
/* Nothing here: a scheme swatch is a gradient of that scheme's own
   background and accent, and js/main.js paints it from
   js/palette-data.js when the picker opens.  .theme-swatch i in the
   hand-written CSS supplies the shape and the fallback.
   */

/* palette:scheme-chips:end */

/* --- 1024 text (foreground) colours --------------------------------------- */
/* Generated by palette.py: the same 128 families x 8 weights = 1024 tones.  A tone
   sets one value per background, --ink-t1..--ink-t8; the scheme's step picks
   which one is --ink, and the per-level weights mix --muted and --muted-2 down
   from it against the surface they are actually drawn on.  A tone that is a pale
   ink on a dark page becomes a deep one on a light page, because the same
   colour cannot clear AA on both: palette.py measures all 1024 x 1024 pairings,
   the two default pages, and every surface each token reaches. */
/* palette:tones:start */
/* generated by palette.py - 1024 text tones
   */

/* The automatic ink: what a scheme with no tone chosen uses.  One
   neutral per step, solved against the worst surface at that step. */
:root { --ink-t1: #000001; --ink-t2: #000000; --ink-t3: #000000; --ink-t4: #000000; --ink-t5: #feffff; --ink-t6: #feffff; --ink-t7: #feffff; --ink-t8: #fbfcfc; }

/* One tone is ten inks - one per step, plus the two default pages -
   and js/main.js applies them from js/palette-data.js, because the
   ink a tone resolves to depends on the step that is live.  What is
   left here is what a tone shares: it depends on the step or on the
   level and never on the tone itself. */

/* Step aliases: pick the ink for the chosen background step.  Before
   the per-level weights below, so a real tone's weights win. */
body[data-theme$="-1"] { --ink: var(--ink-t1); --muted-w: 61.8%; --muted-2-w: 61.8%; }
body[data-theme$="-2"] { --ink: var(--ink-t2); --muted-w: 64.6%; --muted-2-w: 64.6%; }
body[data-theme$="-3"] { --ink: var(--ink-t3); --muted-w: 69.3%; --muted-2-w: 69.3%; }
body[data-theme$="-4"] { --ink: var(--ink-t4); --muted-w: 77.5%; --muted-2-w: 77.5%; }
body[data-theme$="-5"] { --ink: var(--ink-t5); --muted-w: 79.9%; --muted-2-w: 79.9%; }
body[data-theme$="-6"] { --ink: var(--ink-t6); --muted-w: 65.3%; --muted-2-w: 65.3%; }
body[data-theme$="-7"] { --ink: var(--ink-t7); --muted-w: 55.1%; --muted-2-w: 55.1%; }
body[data-theme$="-8"] { --ink: var(--ink-t8); --muted-w: 50.6%; --muted-2-w: 50.6%; }

/* Per level: the muted weights the weakest ink of that level needs,
   taken over every scheme, every tone and both default pages. */
body[data-text$="-1"] { --muted-w: 80.2%; --muted-2-w: 80.2%; }
body[data-text$="-2"] { --muted-w: 81.6%; --muted-2-w: 81.6%; }
body[data-text$="-3"] { --muted-w: 83.3%; --muted-2-w: 83.3%; }
body[data-text$="-4"] { --muted-w: 85.1%; --muted-2-w: 85.1%; }
body[data-text$="-5"] { --muted-w: 87%; --muted-2-w: 87%; }
body[data-text$="-6"] { --muted-w: 88.7%; --muted-2-w: 88.7%; }
body[data-text$="-7"] { --muted-w: 91.5%; --muted-2-w: 91.5%; }
body[data-text$="-8"] { --muted-w: 96%; --muted-2-w: 96%; }

/* A tone with no scheme chosen uses the two default pages' own ink,
   and derives its secondary levels the same way a scheme does. */
body[data-text]:not([data-theme]) {
  --ink: var(--ink-td);
  --muted-base: var(--panel-2);
  --muted: color-mix(in srgb, var(--ink) var(--muted-w), var(--muted-base));
  --muted-2: color-mix(in srgb, var(--ink) var(--muted-2-w), var(--muted-base));
  --ink-strong: color-mix(in srgb, var(--ink) 75%, #ffffff);
}
body[data-mode="light"][data-text]:not([data-theme]) {
  --ink: var(--ink-tl);
  --muted-base: #ffffff;
  --ink-strong: color-mix(in srgb, var(--ink) 75%, #000000);
}
/* palette:tones:end */

/* palette:tone-chips:start */
/* Nothing here: the tone swatches are painted by js/main.js, because
      the colour of a tone is whatever it resolves to on the *current* page - the
      same tone is a pale ink on the near-black steps and a deep one on white, and
      only the browser knows which step is live.  .text-swatch i in the
      hand-written CSS supplies the shape and the fallback.
   */

/* palette:tone-chips:end */

/* --- Base ---------------------------------------------------------------- */
*, *::before, *::after { box-sizing: border-box; }

html {
  -webkit-text-size-adjust: 100%;
  scroll-behavior: smooth;
  scroll-padding-top: 5.5rem;
}

body {
  margin: 0;
  font-family: var(--sans);
  font-size: 1rem;
  line-height: 1.65;
  color: var(--ink);
  background-color: var(--bg);
  background-image:
    radial-gradient(900px 520px at 12% -10%, color-mix(in srgb, var(--accent) 16%, transparent), transparent 70%),
    radial-gradient(760px 460px at 92% 4%, color-mix(in srgb, var(--accent-2) 11%, transparent), transparent 72%),
    linear-gradient(180deg, var(--bg) 0%, var(--bg-2) 100%);
  background-repeat: no-repeat;
  background-attachment: fixed;
}

img, svg { max-width: 100%; height: auto; }

h1, h2, h3, h4 {
  line-height: 1.18;
  margin: 0 0 0.6em;
  letter-spacing: -0.018em;
  font-weight: 650;
}

h1 { font-size: clamp(2rem, 5.2vw, 3.35rem); }
h2 { font-size: clamp(1.5rem, 3.1vw, 2.05rem); }
h3 { font-size: 1.14rem; }

p { margin: 0 0 1rem; }
p:last-child { margin-bottom: 0; }

a { color: var(--accent-text); text-decoration: none; }
a:hover { text-decoration: underline; }

strong { color: var(--ink-strong); font-weight: 620; }

code, kbd, pre, samp { font-family: var(--mono); font-size: 0.9em; }

code {
  background: var(--chip-bg);
  border: 1px solid var(--line-soft);
  border-radius: 5px;
  padding: 0.12em 0.38em;
  color: var(--chip-ink);
}

/* Long identifiers must not push prose past the viewport. */
p code, li code, dd code, figcaption code, .callout code { overflow-wrap: anywhere; }

pre {
  margin: 0 0 1.1rem;
  padding: 1rem 1.1rem;
  background: var(--pre-bg);
  border: 1px solid var(--line);
  border-left: 3px solid var(--accent);
  border-radius: var(--radius-sm);
  overflow-x: auto;
  line-height: 1.55;
  tab-size: 2;
}
pre code { background: none; border: 0; padding: 0; color: var(--pre-ink); }

blockquote {
  margin: 0 0 1.1rem;
  padding: 0.2rem 0 0.2rem 1.1rem;
  border-left: 3px solid var(--accent);
  color: var(--muted);
  font-style: italic;
}

hr {
  border: 0;
  border-top: 1px solid var(--line-soft);
  margin: 2.5rem 0;
}

::selection { background: color-mix(in srgb, var(--accent) 28%, transparent); color: var(--ink-strong); }

:focus-visible {
  outline: 2px solid var(--accent-text);
  outline-offset: 3px;
  border-radius: 4px;
}

/* --- Utilities ----------------------------------------------------------- */
.wrap { width: 100%; max-width: var(--maxw); margin-inline: auto; padding-inline: 1.25rem; }

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

.skip-link {
  position: absolute; left: 1rem; top: -4rem;
  z-index: 100;
  background: var(--accent); color: var(--on-accent);
  padding: 0.6rem 1rem; border-radius: 0 0 10px 10px;
  font-weight: 700;
  transition: top 0.15s ease;
}
.skip-link:focus { top: 0; text-decoration: none; }

.eyebrow {
  display: inline-flex; align-items: center; gap: 0.55rem;
  font-family: var(--mono);
  font-size: 0.76rem;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: var(--accent-text);
  margin: 0 0 0.9rem;
}
.eyebrow::before {
  content: "";
  width: 1.9rem; height: 1px;
  background: linear-gradient(90deg, transparent, var(--accent));
}

.lede { font-size: clamp(1.02rem, 1.9vw, 1.2rem); color: var(--muted); max-width: 62ch; }

.muted { color: var(--muted); }
.small { font-size: 0.86rem; }
.mono { font-family: var(--mono); }
.center { text-align: center; }
.gradient-text { color: var(--accent-text); }

@supports ((-webkit-background-clip: text) or (background-clip: text)) {
  .gradient-text {
    background-image: linear-gradient(100deg, var(--accent-text), var(--accent-2-text));
    -webkit-background-clip: text;
    background-clip: text;
    color: transparent;
  }
}

.section { padding: clamp(2.6rem, 6vw, 4.6rem) 0; }
.section--tight { padding: clamp(1.8rem, 4vw, 2.6rem) 0; }
.section-head { max-width: 68ch; margin-bottom: 2rem; }
.section-head p { color: var(--muted); }

/* --- Header / nav -------------------------------------------------------- */
.site-header {
  position: sticky; top: 0; z-index: 50;
  background: color-mix(in srgb, var(--bg) 82%, transparent);
  backdrop-filter: blur(12px) saturate(140%);
  border-bottom: 1px solid var(--line-soft);
}
.header-inner {
  display: flex; align-items: center; justify-content: space-between;
  gap: 1rem; min-height: 4.25rem;
}

.brand {
  display: inline-flex; align-items: center; gap: 0.65rem;
  color: var(--ink); font-weight: 700; letter-spacing: -0.02em;
}
.brand:hover { text-decoration: none; }
.brand-mark { width: 34px; height: 34px; flex: none; }
.brand-name { font-size: 1.06rem; }
.brand-name span { color: var(--accent-text); }

.primary-nav ul {
  display: flex; align-items: center; gap: 0.35rem;
  list-style: none; margin: 0; padding: 0;
}
.primary-nav a {
  display: block;
  padding: 0.5rem 0.8rem;
  border-radius: 999px;
  color: var(--muted);
  font-size: 0.94rem;
  font-weight: 550;
  border: 1px solid transparent;
  transition: color 0.15s ease, background 0.15s ease, border-color 0.15s ease;
}
.primary-nav a:hover {
  color: var(--ink); background: var(--panel); text-decoration: none;
}
.primary-nav a[aria-current="page"] {
  color: var(--ink);
  background: color-mix(in srgb, var(--accent) 14%, transparent);
  border-color: color-mix(in srgb, var(--accent) 42%, transparent);
}

.nav-toggle {
  display: none;
  width: 44px; height: 40px;
  align-items: center; justify-content: center;
  background: var(--panel); border: 1px solid var(--line); border-radius: 10px;
  color: var(--ink);   /* buttons do not inherit colour: the UA sets black */
  cursor: pointer;
}
.nav-toggle-bars, .nav-toggle-bars::before, .nav-toggle-bars::after {
  display: block; width: 18px; height: 2px; border-radius: 2px;
  background: var(--ink); content: ""; position: relative;
  transition: transform 0.18s ease, opacity 0.18s ease;
}
.nav-toggle-bars::before { position: absolute; top: -6px; }
.nav-toggle-bars::after  { position: absolute; top: 6px; }
.nav-toggle[aria-expanded="true"] .nav-toggle-bars { background: transparent; }
.nav-toggle[aria-expanded="true"] .nav-toggle-bars::before { transform: translateY(6px) rotate(45deg); }
.nav-toggle[aria-expanded="true"] .nav-toggle-bars::after  { transform: translateY(-6px) rotate(-45deg); }

/* --- Buttons ------------------------------------------------------------- */
.btn {
  display: inline-flex; align-items: center; gap: 0.5rem;
  padding: 0.72rem 1.15rem;
  border-radius: 10px;
  font-weight: 620; font-size: 0.95rem;
  border: 1px solid var(--line);
  background: var(--panel);
  color: var(--ink);
  cursor: pointer;
  transition: transform 0.15s ease, background 0.15s ease, border-color 0.15s ease;
}
.btn:hover { text-decoration: none; transform: translateY(-1px); border-color: var(--accent); }
.btn--primary {
  background-color: var(--accent);
  background-image: linear-gradient(100deg, var(--accent), var(--accent-2));
  border-color: transparent;
  color: var(--on-accent);
}
.btn--primary:hover { filter: brightness(1.06); }
.btn--ghost { background: transparent; }
.btn-row { display: flex; flex-wrap: wrap; gap: 0.7rem; margin-top: 1.6rem; }

/* --- Hero ---------------------------------------------------------------- */
.hero { padding: clamp(3rem, 8vw, 6rem) 0 clamp(2.2rem, 5vw, 3.6rem); }
.hero h1 { max-width: 22ch; }
.hero .lede { margin-top: 0.4rem; }

.hero-grid {
  display: grid;
  grid-template-columns: minmax(0, 1.15fr) minmax(0, 0.85fr);
  gap: clamp(1.5rem, 4vw, 3.2rem);
  align-items: center;
}

/* Decorative signal-chain graphic */
.hero-art {
  background: linear-gradient(160deg, var(--panel), var(--panel-deep));
  border: 1px solid var(--line);
  border-radius: var(--radius);
  padding: 1.1rem;
  box-shadow: var(--shadow);
}
.hero-art svg { display: block; width: 100%; height: auto; }
.hero-art figcaption { color: var(--muted-2); font-size: 0.78rem; margin-top: 0.7rem; text-align: center; }

/* Photographs: real screenshots and real hardware, each one a file in this
   directory plus its credit. The site makes no external requests, so the CSP
   can stay img-src 'self' data: — the credits are what make that honest. */
.plate {
  margin: 0;
  display: flex;
  flex-direction: column;
  background: var(--panel);
  border: 1px solid var(--line);
  border-radius: var(--radius);
  box-shadow: var(--shadow);
  overflow: hidden;
}
.plate img { display: block; width: 100%; height: auto; background: var(--well); }
.plate figcaption {
  margin-top: auto;
  padding: 0.7rem 0.9rem 0.8rem;
  border-top: 1px solid var(--line-soft);
  color: var(--muted-2);
  font-size: 0.78rem;
  line-height: 1.55;
}
.plate figcaption strong { color: var(--ink); font-weight: 600; }
.plate-row {
  display: grid;
  gap: 1.1rem;
  /* min() keeps a track from being wider than the page on a 320px screen, where
     a bare minmax(270px, 1fr) would push the layout sideways. */
  grid-template-columns: repeat(auto-fit, minmax(min(270px, 100%), 1fr));
  margin-top: 1.6rem;
}
.plate-row--wide { grid-template-columns: repeat(auto-fit, minmax(min(340px, 100%), 1fr)); }
/* One plate on its own: cap the column so a small photograph is never stretched
   across the full measure of the page, which is what makes it look soft. The
   -sm variant is for a file with no more than 500px to give: at the default
   width it would be blown up past its own resolution. */
.plate-row--solo { grid-template-columns: minmax(0, 560px); }
.plate-row--solo-sm { grid-template-columns: minmax(0, 500px); }
.plate-credit { color: var(--muted-2); font-size: 0.78rem; }

/* --- Stats --------------------------------------------------------------- */
.stats {
  display: grid;
  grid-template-columns: repeat(4, minmax(0, 1fr));
  gap: 1px;
  background: var(--line-soft);
  border: 1px solid var(--line-soft);
  border-radius: var(--radius);
  overflow: hidden;
}
.stat { background: var(--bg-2); padding: 1.15rem 1.25rem; }
.stat b { display: block; font-size: 1.6rem; letter-spacing: -0.02em; color: var(--ink); }
.stat span { color: var(--muted-2); font-size: 0.82rem; }

/* --- Cards --------------------------------------------------------------- */
.grid { display: grid; gap: 1.1rem; }
.grid--2 { grid-template-columns: repeat(2, minmax(0, 1fr)); }
.grid--3 { grid-template-columns: repeat(3, minmax(0, 1fr)); }
.grid--4 { grid-template-columns: repeat(4, minmax(0, 1fr)); }

/* --- Spacing and sizing utilities ---------------------------------------
   A style="…" attribute is an inline style, and the production policy
   (style-src 'self', see _headers and the nginx block in README.md) blocks
   inline styles outright - the browser drops them and logs a violation. These
   used to be style attributes and were therefore invisible on any host that
   served the real policy: headings fell back to their default size, block
   spacing vanished and the gradient stops rendered black.

   They are classes now, with the exact values they replaced, so the rendered
   result is unchanged on hosts that do not send the policy and correct on
   hosts that do. The selector is deliberately doubled (.mt-16.mt-16) to give
   the utility the same weight an inline style had against the component rules
   above: it wins ties by document order. Do not reintroduce style attributes -
   links.mjs fails the check and csp.mjs proves the rendering. */
.mt-06.mt-06 { margin-top: 0.6rem; }
.mt-10.mt-10 { margin-top: 1rem; }
.mt-12.mt-12 { margin-top: 1.2rem; }
.mt-13.mt-13 { margin-top: 1.3rem; }
.mt-14.mt-14 { margin-top: 1.4rem; }
.mt-15.mt-15 { margin-top: 1.5rem; }
.mt-16.mt-16 { margin-top: 1.6rem; }
.mt-20.mt-20 { margin-top: 2rem; }
.mt-fluid-sm.mt-fluid-sm { margin-top: clamp(1.4rem, 3.5vw, 2rem); }
.mt-fluid-lg.mt-fluid-lg { margin-top: clamp(1.6rem, 4vw, 2.4rem); }
.m-0.m-0 { margin: 0; }
.min-w-0.min-w-0 { min-width: 0; }
.border-0.border-0 { border: 0; }
.h2-sm.h2-sm { font-size: clamp(1.35rem, 2.6vw, 1.8rem); }
/* Gradient stops name the -text variant, so a connector line drawn with the
   accent is as visible on the light background as it is on the dark one (the
   two are the same colour in dark mode). */
.stop-a.stop-a { stop-color: var(--accent-text); }
.stop-b.stop-b { stop-color: var(--accent-2-text); }

/* Diagrams: every colour is a token, so a drawing follows the chosen scheme,
   the chosen text tone and the light/dark background instead of staying blue.
   The hard-coded hex these replaced measured 2.1:1 and 1.05:1 in light mode --
   labels at 1.05:1 are not dim, they are invisible. Each class sets only what
   the element it replaced actually painted, so no border appears anywhere the
   original drawing had none. */
.svg-frame { fill: var(--panel); stroke: var(--line); }
.svg-block { fill: var(--panel-2); stroke: var(--line); }
.svg-block--deep { fill: var(--panel-deep); stroke: var(--line); }
.svg-label { fill: var(--muted); }
.svg-key { fill: var(--ink); }
.svg-rule { stroke: var(--line); }
.svg-panel { fill: var(--panel); }
.svg-deep { fill: var(--panel-deep); }
.svg-accent { fill: var(--accent-text); }
.svg-accent-2 { fill: var(--accent-2-text); }
.svg-accent-stroke { stroke: var(--accent-text); }
.svg-accent-2-stroke { stroke: var(--accent-2-text); }
/* A block that carries the accent on its border: the device the drawing is
   actually about. */
.svg-block--key { fill: var(--panel-2); stroke: var(--accent-text); }

/* The diagram is the page's one piece of hand-drawn explanation, and at half the
   measure the block labels were too small to read. Give it the whole width of
   the hero and its own row under the copy. */
.hero-art--full { grid-column: 1 / -1; }

/* --- Image lightbox ------------------------------------------------------- */
/* Plates are links to their own file, so the browser's own "open image in a new
   tab" stays the fallback and this is the enhancement on top. */
.plate-link { position: relative; display: block; cursor: zoom-in; }
.plate-zoom {
  position: absolute; right: 0.6rem; bottom: 0.6rem;
  display: inline-flex; align-items: center; justify-content: center;
  width: 1.7rem; height: 1.7rem;
  border-radius: 7px;
  border: 1px solid var(--line);
  background: var(--panel); color: var(--ink);
  font-size: 1rem; line-height: 1;
}
.plate-link:hover .plate-zoom,
.plate-link:focus-visible .plate-zoom { border-color: var(--accent); }
.lightbox {
  position: fixed; inset: 0; z-index: 100;
  display: grid; place-content: center; justify-items: center;
  padding: clamp(0.8rem, 3vw, 2rem);
  background: color-mix(in srgb, var(--bg) 90%, transparent);
  backdrop-filter: blur(6px) saturate(120%);
}
.lightbox[hidden] { display: none; }
.lightbox-figure {
  margin: 0;
  max-width: min(96vw, 1800px);
  display: flex; flex-direction: column; align-items: center;
}
.lightbox img {
  display: block; width: auto; height: auto;
  max-width: 100%; max-height: 74vh;
  border: 1px solid var(--line); border-radius: var(--radius);
  background: var(--well);
  box-shadow: var(--shadow);
}
.lightbox-caption {
  margin: 0.85rem auto 0;
  max-width: 76ch;
  padding: 0.6rem 0.9rem;
  border: 1px solid var(--line); border-radius: var(--radius);
  background: var(--panel); color: var(--muted);
  font-size: 0.84rem; line-height: 1.5; text-align: left;
}
/* No close button: the overlay itself is the target, so the whole surface gets
   the zoom-out cursor and a click anywhere - picture included - dismisses it. */
.lightbox { cursor: zoom-out; }
.lightbox-figure { cursor: default; }
/* The dialog takes focus on open, purely so Escape and focus restoration work;
   it must not draw the focus ring across the whole screen when it does. */
.lightbox:focus, .lightbox:focus-visible { outline: none; }
.lightbox-hint {
  margin: 0.85rem auto 0; max-width: 76ch;
  color: var(--muted); font-size: 0.8rem; text-align: center;
}
.lightbox-hint kbd {
  padding: 0.1em 0.4em;
  border: 1px solid var(--line); border-radius: 5px;
  background: var(--panel); color: var(--ink);
}
body.lightbox-open { overflow: hidden; }

@media (max-width: 1080px) {
  .grid--4 { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}
@media (max-width: 940px) {
  .grid--3 { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}
@media (max-width: 620px) {
  .grid--2, .grid--3, .grid--4 { grid-template-columns: minmax(0, 1fr); }
}

.card {
  position: relative;
  display: flex; flex-direction: column;
  background: linear-gradient(180deg, var(--panel), var(--panel-deep));
  border: 1px solid var(--line);
  border-radius: var(--radius);
  padding: 1.35rem 1.35rem 1.45rem;
  box-shadow: var(--shadow);
  transition: transform 0.18s ease, border-color 0.18s ease;
}
.card:hover { transform: translateY(-3px); border-color: color-mix(in srgb, var(--accent) 45%, var(--line)); }
.card h3 { margin-bottom: 0.45rem; }
.card p { color: var(--muted); font-size: 0.95rem; }
.card .card-tag {
  font-family: var(--mono); font-size: 0.72rem; letter-spacing: 0.12em;
  text-transform: uppercase; color: var(--accent-text); margin-bottom: 0.7rem;
}
.card--link { color: inherit; }
.card--link:hover { text-decoration: none; }
.card--link::after {
  content: "Read more →";
  margin-top: auto; padding-top: 1rem;
  font-size: 0.88rem; font-weight: 600; color: var(--accent-text);
}

.icon-badge {
  width: 40px; height: 40px; flex: none;
  display: grid; place-items: center;
  border-radius: 11px;
  background: color-mix(in srgb, var(--accent) 15%, transparent);
  border: 1px solid color-mix(in srgb, var(--accent) 35%, transparent);
  color: var(--accent-text);
  margin-bottom: 0.95rem;
}
.icon-badge svg { width: 21px; height: 21px; }

/* --- Domain (home trio) -------------------------------------------------- */
.domain-card { overflow: hidden; }
.domain-card .domain-art { margin: -1.35rem -1.35rem 1.1rem; display: block; background: var(--well); border-bottom: 1px solid var(--line); }
.domain-card .domain-art svg { display: block; width: 100%; height: 118px; }

/* --- Feature list -------------------------------------------------------- */
.feature-list { list-style: none; margin: 0; padding: 0; display: grid; gap: 0.8rem; }
.feature-list li {
  position: relative; padding-left: 1.75rem; color: var(--muted);
}
.feature-list li::before {
  content: ""; position: absolute; left: 0; top: 0.55em;
  width: 0.62rem; height: 0.62rem; border-radius: 3px;
  background: linear-gradient(135deg, var(--accent), var(--accent-2));
}
.feature-list li strong { display: block; color: var(--ink); }

/* --- Tables -------------------------------------------------------------- */
.table-scroll { overflow-x: auto; border: 1px solid var(--line); border-radius: var(--radius); }
table { width: 100%; border-collapse: collapse; min-width: 520px; font-size: 0.93rem; }
caption {
  caption-side: top; text-align: left;
  padding: 0.9rem 1.05rem; color: var(--muted-2); font-size: 0.83rem;
  border-bottom: 1px solid var(--line);
}
th, td { padding: 0.72rem 1.05rem; text-align: left; border-bottom: 1px solid var(--line-soft); vertical-align: top; }
thead th {
  background: var(--th-bg); color: var(--ink);
  font-size: 0.78rem; letter-spacing: 0.09em; text-transform: uppercase;
}
tbody tr:last-child td { border-bottom: 0; }
tbody tr:hover { background: var(--row-hover); }
td code { white-space: nowrap; }

/* --- Callouts ------------------------------------------------------------ */
.callout {
  border: 1px solid var(--line);
  border-left: 3px solid var(--accent);
  border-radius: var(--radius-sm);
  background: color-mix(in srgb, var(--accent) 7%, var(--panel));
  padding: 1.05rem 1.2rem;
}
.callout h3 { font-size: 1rem; margin-bottom: 0.35rem; }
.callout p { color: var(--muted); font-size: 0.94rem; }
.callout--warn { border-left-color: var(--warn); background: color-mix(in srgb, var(--warn) 8%, var(--panel)); }

/* --- Steps / timeline ---------------------------------------------------- */
.steps { list-style: none; margin: 0; padding: 0; counter-reset: step; display: grid; gap: 1rem; }
.steps li {
  position: relative; padding-left: 3.1rem; counter-increment: step;
}
.steps li::before {
  content: counter(step, decimal-leading-zero);
  position: absolute; left: 0; top: 0.1rem;
  width: 2.2rem; height: 2.2rem;
  display: grid; place-items: center;
  border-radius: 50%;
  font-family: var(--mono); font-size: 0.8rem;
  color: var(--accent-text);
  border: 1px solid color-mix(in srgb, var(--accent) 40%, transparent);
  background: color-mix(in srgb, var(--accent) 9%, transparent);
}
.steps h3 { margin-bottom: 0.25rem; }
.steps p { color: var(--muted); font-size: 0.95rem; }

/* --- Split panel --------------------------------------------------------- */
.split {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: clamp(1.4rem, 4vw, 3rem);
  align-items: start;
}

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

.panel {
  background: linear-gradient(180deg, var(--panel), var(--panel-deep));
  border: 1px solid var(--line);
  border-radius: var(--radius);
  padding: 1.4rem;
  box-shadow: var(--shadow);
}
.panel h3 { margin-bottom: 0.7rem; }

/* --- CTA band ------------------------------------------------------------ */
.cta-band {
  border: 1px solid color-mix(in srgb, var(--accent) 35%, var(--line));
  border-radius: var(--radius);
  background:
    radial-gradient(600px 220px at 15% 0%, color-mix(in srgb, var(--accent) 10%, transparent), transparent 70%),
    linear-gradient(180deg, var(--panel-2), var(--panel-deep));
  padding: clamp(1.6rem, 4vw, 2.6rem);
  box-shadow: var(--shadow);
}

/* --- Footer -------------------------------------------------------------- */
.site-footer {
  margin-top: clamp(2.5rem, 6vw, 4rem);
  border-top: 1px solid var(--line-soft);
  background: var(--sink);
  padding-top: 2.5rem;
  font-size: 0.92rem;
}
.footer-grid {
  display: grid;
  grid-template-columns: minmax(0, 1.5fr) repeat(3, minmax(0, 1fr));
  gap: 2rem;
}
.footer-grid h4 {
  font-size: 0.78rem; letter-spacing: 0.12em; text-transform: uppercase;
  color: var(--muted-2); margin-bottom: 0.75rem;
}
.footer-grid ul { list-style: none; margin: 0; padding: 0; display: grid; gap: 0.45rem; }
.footer-grid a { color: var(--muted); }
.footer-grid a:hover { color: var(--ink); }
.footer-about p { color: var(--muted-2); max-width: 38ch; margin-top: 0.8rem; }
.footer-legal {
  margin-top: 2.2rem; padding: 1.1rem 1.25rem 2rem;
  border-top: 1px solid var(--line-soft);
  display: flex; flex-wrap: wrap; gap: 0.6rem 1.5rem;
  justify-content: space-between;
  color: var(--muted-2); font-size: 0.83rem;
}
.footer-legal p { margin: 0; }
/* The names notice takes a row of its own under the two short legal lines,
   instead of being squeezed into the space-between gap between them. */
.footer-note { flex-basis: 100%; max-width: 92ch; }

/* --- Contact form -------------------------------------------------------- */
.contact-grid {
  display: grid;
  grid-template-columns: minmax(0, 1.25fr) minmax(0, 0.85fr);
  gap: clamp(1.2rem, 3vw, 2rem);
  align-items: start;
  margin-top: clamp(1.6rem, 4vw, 2.4rem);
}
.contact-form { display: grid; gap: 1.05rem; }
.form-row { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 1.05rem; }
.field { display: grid; gap: 0.4rem; }
.field label { font-size: 0.9rem; font-weight: 600; }
.field .req { color: var(--accent-text); }
.field .opt { color: var(--muted); font-weight: 400; font-size: 0.8rem; }
.field input, .field select, .field textarea {
  font: inherit;
  font-size: 0.95rem;
  color: var(--ink);
  background-color: var(--well);
  border: 1px solid var(--line);
  border-radius: var(--radius-sm);
  padding: 0.62rem 0.75rem;
  width: 100%;
  min-width: 0;
}
.field textarea { min-height: 9rem; resize: vertical; }
.field input::placeholder, .field textarea::placeholder { color: var(--muted-2); opacity: 1; }
.field input:hover, .field select:hover, .field textarea:hover {
  border-color: color-mix(in srgb, var(--accent) 35%, var(--line));
}
.field input:focus-visible, .field select:focus-visible, .field textarea:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 1px;
  border-color: transparent;
}
.field input:user-invalid, .field select:user-invalid, .field textarea:user-invalid,
.field input[aria-invalid="true"], .field select[aria-invalid="true"], .field textarea[aria-invalid="true"] {
  border-color: var(--warn);
}
.field select {
  appearance: none;
  -webkit-appearance: none;
  padding-right: 2.3rem;
  background-image:
    linear-gradient(45deg, transparent 50%, var(--muted) 50%),
    linear-gradient(135deg, var(--muted) 50%, transparent 50%);
  background-position: calc(100% - 19px) 55%, calc(100% - 14px) 55%;
  background-size: 5px 5px, 5px 5px;
  background-repeat: no-repeat;
}
.form-hint { margin: 0; font-size: 0.82rem; color: var(--muted); }
.form-actions { display: flex; flex-wrap: wrap; align-items: center; gap: 0.7rem; }
.form-status { margin: 0; min-height: 1.35em; font-size: 0.9rem; color: var(--muted); }
.form-status[data-state="ok"] { color: var(--accent-2-text); }
.form-status[data-state="warn"] { color: var(--warn); }
.form-trap {
  position: absolute; width: 1px; height: 1px;
  overflow: hidden; clip-path: inset(50%); white-space: nowrap;
}
.form-trap input { width: 1px; height: 1px; padding: 0; border: 0; }

.contact-list { list-style: none; margin: 1rem 0 0; padding: 0; display: grid; gap: 0.9rem; }
.contact-list li { display: grid; gap: 0.15rem; padding-bottom: 0.9rem; border-bottom: 1px solid var(--line-soft); }
.contact-list li:last-child { border-bottom: 0; padding-bottom: 0; }
.contact-list .what { font-size: 0.76rem; letter-spacing: 0.1em; text-transform: uppercase; color: var(--muted); }
.contact-list code { font-size: 0.92rem; overflow-wrap: anywhere; }
.contact-list .when { font-size: 0.84rem; color: var(--muted); }

/* --- Breadcrumbs --------------------------------------------------------- */
.breadcrumbs { margin-bottom: 0.85rem; }
.breadcrumbs ol {
  display: flex; flex-wrap: wrap; align-items: center;
  gap: 0.35rem; list-style: none; margin: 0; padding: 0;
  font-size: 0.82rem; color: var(--muted);
}
.breadcrumbs li { display: flex; align-items: center; gap: 0.35rem; }
.breadcrumbs li + li::before { content: "/"; color: var(--muted); }
.breadcrumbs a { color: var(--muted); }
.breadcrumbs a:hover { color: var(--ink); }
.breadcrumbs [aria-current="page"] { color: var(--ink); }

/* --- Sub-headings inside panels and split columns ------------------------ */
.panel h4, .split h4, .cta-band h4 {
  margin: 1.3rem 0 0.3rem;
  font-size: 0.76rem; font-weight: 650;
  letter-spacing: 0.1em; text-transform: uppercase;
  color: var(--accent-text);
}

/* --- Colour-scheme switcher --------------------------------------------- */
/* The cluster holds the light/dark button and the three pickers. */
.theme-switcher {
  position: relative;
  display: flex; align-items: center; gap: 0.5rem;
}
/* [hidden] comes from the UA sheet and loses to the display above. */
.theme-switcher[hidden] { display: none; }
.theme-toggle,
.mode-toggle,
.text-toggle,
.font-toggle {
  display: inline-flex; align-items: center; gap: 0.5rem;
  padding: 0.5rem 0.85rem;
  border-radius: 999px;
  border: 1px solid var(--line);
  background: var(--panel);
  color: var(--muted);
  font: inherit; font-size: 0.9rem; font-weight: 550;
  cursor: pointer;
  transition: color 0.15s ease, border-color 0.15s ease;
}
.theme-toggle:hover,
.theme-toggle[aria-expanded="true"],
.mode-toggle:hover,
.mode-toggle[aria-pressed="true"],
.text-toggle:hover,
.text-toggle[aria-expanded="true"],
.font-toggle:hover,
.font-toggle[aria-expanded="true"] { color: var(--ink); border-color: var(--accent); }
.theme-dot {
  width: 14px; height: 14px; border-radius: 50%; flex: none;
  background-image: linear-gradient(100deg, var(--accent), var(--accent-2));
  box-shadow: 0 0 0 1px color-mix(in srgb, var(--ink) 14%, transparent);
}
.theme-panel,
.text-panel,
.font-panel {
  position: absolute; right: 0; top: calc(100% + 0.55rem);
  width: min(94vw, 34rem);
  padding: 0.85rem;
  background: var(--bg-2);
  border: 1px solid var(--line);
  border-radius: var(--radius);
  box-shadow: var(--shadow);
  z-index: 60;
}
.theme-panel-title,
.text-panel-title,
.font-panel-title {
  margin: 0 0 0.6rem;
  font-size: 0.72rem; font-weight: 650;
  letter-spacing: 0.1em; text-transform: uppercase;
  color: var(--muted);
}
/* Says how the 1025 options are laid out, so the numbers in the names read as a
   scale rather than as decoration. */
.panel-legend {
  margin: -0.35rem 0 0.6rem;
  font-size: 0.76rem; line-height: 1.35;
  color: var(--muted-2);
}
/* 1025 options per panel, so the list is a specimen sheet: as many columns as
   fit, one heading per section, and a filter box above. */
.picker-filter {
  display: block; width: 100%; box-sizing: border-box;
  margin: 0 0 0.6rem;
  padding: 0.45rem 0.6rem;
  border: 1px solid var(--line); border-radius: 8px;
  background: var(--panel); color: var(--ink);
  font: inherit; font-size: 0.85rem;
}
.picker-filter::placeholder { color: var(--muted-2); opacity: 1; }
.picker-filter:focus-visible { border-color: var(--accent); }
.theme-list,
.text-list,
.font-list {
  display: grid; grid-template-columns: repeat(auto-fill, minmax(9.5rem, 1fr));
  gap: 0.2rem 0.3rem; list-style: none; margin: 0; padding: 0;
  max-height: min(56vh, 25rem); overflow-y: auto;
}
.swatch-group {
  grid-column: 1 / -1; position: sticky; top: 0; z-index: 1;
  margin: 0.45rem 0 0.1rem;
  padding: 0.2rem 0.5rem;
  background: var(--bg-2); color: var(--muted);
  font-size: 0.66rem; font-weight: 650;
  letter-spacing: 0.1em; text-transform: uppercase;
}
.swatch-group:first-child { margin-top: 0; }
.theme-swatch,
.text-swatch,
.font-swatch {
  display: flex; align-items: center; gap: 0.5rem; width: 100%;
  padding: 0.42rem 0.5rem;
  border: 1px solid transparent; border-radius: 8px;
  background: transparent; color: var(--muted);
  font: inherit; font-size: 0.82rem; text-align: left;
  cursor: pointer;
}
.theme-swatch:hover,
.text-swatch:hover,
.font-swatch:hover { color: var(--ink); background: var(--panel); }
.theme-swatch[aria-pressed="true"],
.text-swatch[aria-pressed="true"],
.font-swatch[aria-pressed="true"] {
  color: var(--ink);
  border-color: var(--accent);
  background: var(--panel-2);
}
.theme-swatch i {
  width: 17px; height: 17px; border-radius: 5px; flex: none;
  background-image: linear-gradient(100deg, var(--sw-a, var(--accent)), var(--sw-b, var(--accent-2)));
}

/* Light/dark button: the icon shows the mode you would switch *to*, so the sun
   sits in the dark theme and the moon in the light one. */
.mode-icon { width: 16px; height: 16px; flex: none; fill: none; stroke: currentColor; stroke-width: 1.7; stroke-linecap: round; }
.mode-toggle .mode-icon--moon { display: none; }
body[data-mode="light"] .mode-toggle .mode-icon--sun { display: none; }
body[data-mode="light"] .mode-toggle .mode-icon--moon { display: block; }

/* Text button: the "Aa" previews the colour the body text is using now. */
.text-dot {
  width: 17px; height: 17px; border-radius: 5px; flex: none;
  display: inline-flex; align-items: center; justify-content: center;
  font-size: 0.58rem; font-weight: 700; line-height: 1; letter-spacing: 0;
  color: var(--ink);
  background: var(--bg);
  box-shadow: inset 0 0 0 1px var(--line);
}

/* One chip per text tone: the letters are drawn in that tone on the page
   background, so the list reads as a specimen sheet rather than a row of paint
   pots.  js/main.js resolves each tone against the live page and paints the
   chip; without JavaScript these neutral values are the fallback. */
.text-swatch i {
  width: 22px; height: 18px; border-radius: 5px; flex: none;
  display: inline-flex; align-items: center; justify-content: center;
  font-family: var(--mono); font-size: 0.66rem; font-weight: 600; line-height: 1;
  color: var(--ink);
  background: var(--bg);
  box-shadow: inset 0 0 0 1px var(--line);
}

/* --- Typeface picker ----------------------------------------------------- */
/* The Font button chooses from the 1024-family catalogue generated into
   js/font-data.js: css/fonts.css declares the faces, assets/fonts/ carries the
   files, the designers and the licence texts.  The choice lands on the body as
   data-font and as --sans, the variable the whole page inherits, so nothing
   else in the stylesheet has to know about it, and js/theme-init.js applies it
   before the first paint.  The rules below are the sixteen families the site
   shipped before the catalogue existed: they are one line each, they are what
   a page still shows if js/font-data.js never arrives, and they are the only
   families whose stack is not built from the catalogue at run time.  The
   fixed-pitch stack stays put, so code samples keep their monospace whatever
   the prose is set in. */
body[data-font="inter"] { --sans: 'Inter', system-ui, -apple-system, sans-serif; }
body[data-font="source-sans"] { --sans: 'Source Sans 3', system-ui, -apple-system, sans-serif; }
body[data-font="work-sans"] { --sans: 'Work Sans', system-ui, -apple-system, sans-serif; }
body[data-font="manrope"] { --sans: 'Manrope', system-ui, -apple-system, sans-serif; }
body[data-font="archivo"] { --sans: 'Archivo', system-ui, -apple-system, sans-serif; }
body[data-font="space-grotesk"] { --sans: 'Space Grotesk', system-ui, -apple-system, sans-serif; }
body[data-font="eb-garamond"] { --sans: 'EB Garamond', Georgia, 'Times New Roman', serif; }
body[data-font="source-serif"] { --sans: 'Source Serif 4', Georgia, serif; }
body[data-font="literata"] { --sans: 'Literata', Georgia, serif; }
body[data-font="fraunces"] { --sans: 'Fraunces', Georgia, serif; }
body[data-font="playfair"] { --sans: 'Playfair Display', Georgia, serif; }
body[data-font="lora"] { --sans: 'Lora', Georgia, serif; }
body[data-font="bitter"] { --sans: 'Bitter', 'Rockwell', Georgia, serif; }
body[data-font="dm-serif"] { --sans: 'DM Serif Display', Georgia, serif; }
body[data-font="jetbrains-mono"] { --sans: 'JetBrains Mono', ui-monospace, monospace; }
body[data-font="plex-mono"] { --sans: 'IBM Plex Mono', ui-monospace, monospace; }

/* The button's own preview: "Aa" set in whatever face is live, which is the
   chosen one, so the pill shows the page's type at a glance. */
.font-dot {
  width: 17px; height: 17px; border-radius: 5px; flex: none;
  display: inline-flex; align-items: center; justify-content: center;
  font-size: 0.6rem; font-weight: 650; line-height: 1; letter-spacing: 0;
  color: var(--ink);
  background: var(--bg);
  box-shadow: inset 0 0 0 1px var(--line);
}

/* One row per family: the name and a short specimen, both in that family.  A
   face is only downloaded once js/main.js has actually put the row on screen,
   so opening the panel costs nothing and scrolling costs one font per row. */
.font-swatch i {
  width: 26px; height: 18px; border-radius: 5px; flex: none;
  display: inline-flex; align-items: center; justify-content: center;
  font-size: 0.62rem; font-weight: 650; line-height: 1;
  color: var(--muted);
  background: var(--bg);
  box-shadow: inset 0 0 0 1px var(--line);
}
.font-swatch .font-sample {
  margin-left: auto; padding-left: 0.6rem;
  color: var(--muted-2); font-size: 0.9rem; white-space: nowrap;
}
/* Roomier columns than the colour lists, because a family name and a specimen
   share the row; a long name clips rather than wraps, so every row keeps its
   height and the list stays a specimen sheet. */
.font-list { grid-template-columns: repeat(auto-fill, minmax(12.5rem, 1fr)); }
.font-swatch { white-space: nowrap; overflow: hidden; }
/* The chip is an <i>, so it would be italic by default - and italic is a second
   file per family.  Upright, one face per painted row. */
.font-swatch i { font-style: normal; }

/* --- Reveal on scroll (progressive enhancement) -------------------------- */
.js .reveal { opacity: 0; transform: translateY(14px); transition: opacity 0.5s ease, transform 0.5s ease; }
.js .reveal.is-visible { opacity: 1; transform: none; }

/* --- Responsive ---------------------------------------------------------- */
@media (max-width: 860px) {
  .hero-grid { grid-template-columns: minmax(0, 1fr); }
  .hero h1 { max-width: none; }
  .contact-grid { grid-template-columns: minmax(0, 1fr); }
}

@media (max-width: 720px) {
  /* Four controls plus the hamburger is a tight fit: drop the labels (every
     button keeps an aria-label) and tighten the pills. */
  .theme-toggle-text { display: none; }
  .theme-switcher { gap: 0.3rem; }
  .theme-toggle,
  .mode-toggle,
  .text-toggle,
  .font-toggle { padding: 0.5rem 0.6rem; }
  /* The controls no longer reach the right edge of the header, so anchor the
     popovers to the header itself instead of to the button cluster. */
  .theme-switcher { position: static; }
  .theme-panel,
  .text-panel { left: 0.9rem; right: 0.9rem; width: auto; }
}

/* The wide layout, but not wide enough: the seven links and the four pills do
   not fit side by side until about 1600px, and below that they push the cluster
   past the right edge (measured with the fourth pill in place: 83px over at
   1420px, 73px at 1440px, 33px at 1520px and still 13px at 1560px). Icon-only
   pills and a slightly tighter nav bring that back inside the page; every
   button keeps its aria-label. */
@media (min-width: 1041px) and (max-width: 1600px) {
  .theme-toggle-text { display: none; }
  .theme-switcher { gap: 0.35rem; }
  .theme-toggle,
  .mode-toggle,
  .text-toggle,
  .font-toggle { padding: 0.5rem 0.65rem; }
  .primary-nav ul { gap: 0.15rem; }
  .primary-nav a { padding: 0.5rem 0.55rem; font-size: 0.89rem; }
}

@media (max-width: 1040px) {
  /* The collapsed navigation needs scripting: the hamburger is only revealed,
     and the list only folded away, once js/theme-init.js has marked
     <html class="has-js">. Without scripting the toggle would be a dead
     control, so the seven links wrap under the brand instead (the
     html:not(.has-js) rules below). */
  .has-js .nav-toggle { display: inline-flex; order: 3; }
  .has-js .theme-switcher { order: 2; margin-left: auto; }
  .has-js .primary-nav {
    position: absolute; left: 0; right: 0; top: 100%;
    background: var(--bg-2);
    border-bottom: 1px solid var(--line);
    padding: 0.6rem 1.25rem 1rem;
  }
  .has-js .primary-nav:not(.is-open) { display: none; }
  .has-js .primary-nav.is-open { display: block; }
  .has-js .primary-nav ul { flex-direction: column; align-items: stretch; gap: 0.15rem; }
  .has-js .primary-nav a { padding: 0.7rem 0.8rem; border-radius: 9px; }

  html:not(.has-js) .header-inner { flex-wrap: wrap; }
  html:not(.has-js) .primary-nav { flex-basis: 100%; }
  html:not(.has-js) .primary-nav ul { flex-wrap: wrap; gap: 0.35rem 0.9rem; }
}

@media (max-width: 760px) {
  .footer-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  .stats { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  .form-row { grid-template-columns: minmax(0, 1fr); }
}

@media (max-width: 900px) and (min-width: 761px) {
  .footer-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}

@media (max-width: 460px) {
  .footer-grid { grid-template-columns: minmax(0, 1fr); }
  .stats { grid-template-columns: minmax(0, 1fr); }
  .stat b { font-size: 1.35rem; }
  /* A 320px phone is the tightest case: the brand, three icon pills and the
     hamburger used to run 63px past the edge. */
  .header-inner { gap: 0.4rem; }
  .brand-mark { width: 28px; height: 28px; }
  .brand-name { font-size: 0.95rem; }
  .theme-switcher { gap: 0.2rem; }
  .theme-toggle,
  .mode-toggle,
  .text-toggle { padding: 0.45rem 0.5rem; gap: 0.35rem; }
  .nav-toggle { width: 38px; height: 38px; }
  /* and the page gutter loses 0.35rem a side, which is the last 10px of it */
  .wrap { padding-inline: 0.9rem; }
}

@media (max-width: 360px) {
  /* 320px, the narrowest phone still in use: the brand and the pills each give
     up a few pixels so the header fits without a sideways scroll. */
  .brand-name { font-size: 0.9rem; }
  .theme-toggle,
  .mode-toggle,
  .text-toggle { padding: 0.4rem 0.45rem; }
  .nav-toggle { width: 36px; }
}

/* --- Motion & print ------------------------------------------------------ */
@media (prefers-reduced-motion: reduce) {
  html { scroll-behavior: auto; }
  *, *::before, *::after {
    animation-duration: 0.001ms !important;
    transition-duration: 0.001ms !important;
  }
  .js .reveal { opacity: 1; transform: none; }
}

@media print {
  body { background: #fff; color: #000; }
  .site-header, .site-footer, .btn-row, .hero-art, .breadcrumbs, .theme-switcher { display: none; }
  .card, .panel, .table-scroll { border-color: #bbb; box-shadow: none; background: #fff; }
  .card p, .lede, .muted { color: #333; }
  a { color: #000; text-decoration: underline; }
  /* The plates print as ordinary images, without an affordance they no longer
     have on paper, and without a lightbox that happened to be open. */
  .plate-zoom { display: none; }
  .lightbox { display: none !important; }
}
