/* Inter Tight, self-hosted, served from this site so a visitor's browser never calls Google.
   Same two subsets castomapi.com ships (SIL Open Font License). */

@font-face {
  font-family: 'Inter Tight';
  font-style: normal;
  font-weight: 400 700;
  font-display: swap;
  src: url('/assets/fonts/inter-tight-latin-ext.woff2') format('woff2');
  unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}
@font-face {
  font-family: 'Inter Tight';
  font-style: normal;
  font-weight: 400 700;
  font-display: swap;
  src: url('/assets/fonts/inter-tight-latin.woff2') format('woff2');
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}

/* castomflow.com — the one stylesheet, copied from fabls.io (itself castomo.com's, castomo-landing
   assets/site.css) so every ecosystem site shares one header, footer and component set
   (docs/ECOSYSTEM-GUIDELINES.md); only the ramp changes: castomFlow blue (#003C87 / #0052B6 / #008FFF)
   on the shared plate #0B0C10. #0052B6 carries white text at 5.9:1, so it is the fill that holds
   words; the dark stop #003C87 is the colour words take on paper.
   Order: tokens → themes → reader preferences → reset → type → chrome → components → sections → print. */

/* ------------------------------------------------------------------ tokens (light) */
:root {
  --paper: #F4F5F8;
  --paper-2: #FFFFFF;
  --field: #E9EBF0;
  --ink: #0B0C10;
  --ink-2: #454A5C;
  --ink-3: #5F6478;
  --rule: #D7DAE3;
  --rule-w: 1px;
  /* --accent is the fill white words sit on; --accent-ink is the colour words take on paper.
     On light grounds both are the dark stop, because the mid stop fails AA under white text. */
  --accent: #0052B6;
  --accent-ink: #003C87;
  --accent-soft: rgba(0, 82, 182, .14);
  --ok: #0E7C3A;
  --warn: #8A5200;
  --bad: #C2261A;
  --brand-1: #003C87;
  --brand-2: #0052B6;
  --brand-3: #008FFF;
  --fill-hover: #003C87;
  --on-fill: #FFFFFF;
  --shadow: 0 1px 2px rgba(11, 12, 16, .06), 0 12px 32px -16px rgba(11, 12, 16, .25);
  --btn-bg: var(--ink);
  --btn-fg: var(--paper);
  --radius: 14px;
  --radius-sm: 8px;
  --radius-pill: 999px;
  --t-1: .875rem;
  --t0: 1.0625rem;
  --t1: 1.25rem;
  --t2: 1.5625rem;
  --t3: 1.95rem;
  --t4: 2.44rem;
  --t5: 3.05rem;
  --t6: 3.8rem;
  --wrap: 1320px;
  --bar-h: 64px;
  --ease: cubic-bezier(.2, .7, .2, 1);
  --dur: .18s;
  /* DESIGN §4 and §5 tokens. They carry no colour, so they live here once and every theme inherits
     them. The spacing steps are for new components only; older rules keep their own numbers rather
     than being refactored for the sake of it. --wrap-hero is the hero's wider stage (§4). */
  --wrap-hero: var(--wrap);
  --s-1: 4px; --s0: 8px; --s1: 12px; --s2: 16px; --s3: 24px; --s4: 32px; --s5: 48px; --s6: 64px; --s7: 96px;
  --ease-ink: var(--ease);
  --ease-paper: cubic-bezier(.65, 0, .35, 1);
  --ease-stamp: cubic-bezier(.34, 1.56, .64, 1);
  --t-write: 320ms; --t-paint: 480ms; --t-stamp: 420ms; --t-lift: 640ms;
  --t-settle: 600ms; --t-tear: 520ms; --t-slide: 220ms; --t-stagger: 70ms;
  color-scheme: light;
}

/* ------------------------------------------------------------------ dark */
[data-theme="dark"] {
  --paper: #0B0C10;
  --paper-2: #14161D;
  --field: #1B1E27;
  --ink: #E8EAEE;
  --ink-2: #B4B8C6;
  --ink-3: #9296A6;
  --rule: #262A36;
  --rule-w: 1px;
  --accent: #0052B6;
  --accent-ink: #008FFF;
  --accent-soft: rgba(0, 82, 182, .20);
  --ok: #5FD898;
  --warn: #FFC46B;
  --bad: #FF8D81;
  --brand-1: #003C87;
  --brand-2: #0052B6;
  --brand-3: #008FFF;
  --fill-hover: #003C87;
  --on-fill: #FFFFFF;
  --shadow: 0 1px 2px rgba(0, 0, 0, .4), 0 12px 32px -16px rgba(0, 0, 0, .8);
  --btn-bg: var(--ink);
  --btn-fg: var(--paper);
  color-scheme: dark;
}
@media (prefers-color-scheme: dark) {
  :root:not([data-theme]) {
    --paper: #0B0C10;
    --paper-2: #14161D;
    --field: #1B1E27;
    --ink: #E8EAEE;
    --ink-2: #B4B8C6;
    --ink-3: #9296A6;
    --rule: #262A36;
    --rule-w: 1px;
    --accent: #0052B6;
    --accent-ink: #008FFF;
    --accent-soft: rgba(0, 82, 182, .20);
    --ok: #5FD898;
    --warn: #FFC46B;
    --bad: #FF8D81;
    --brand-1: #003C87;
    --brand-2: #0052B6;
    --brand-3: #008FFF;
    --fill-hover: #003C87;
    --on-fill: #FFFFFF;
    --shadow: 0 1px 2px rgba(0, 0, 0, .4), 0 12px 32px -16px rgba(0, 0, 0, .8);
    --btn-bg: var(--ink);
    --btn-fg: var(--paper);
    color-scheme: dark;
  }
}

/* ------------------------------------------------------------------ high contrast (light) */
[data-theme="contrast"] {
  --paper: #FFFFFF;
  --paper-2: #FFFFFF;
  --field: #F0F0F0;
  --ink: #000000;
  --ink-2: #000000;
  --ink-3: #000000;
  --rule: #000000;
  --rule-w: 2px;
  --accent: #003C87;
  --accent-ink: #00284F;
  --accent-soft: rgba(0, 0, 0, 0);
  --ok: #005A24;
  --warn: #6B3F00;
  --bad: #A81208;
  --brand-1: #00284F;
  --brand-2: #00284F;
  --brand-3: #00284F;
  --fill-hover: #00284F;
  --on-fill: #FFFFFF;
  --shadow: none;
  --btn-bg: #000000;
  --btn-fg: #FFFFFF;
  color-scheme: light;
}
@media (prefers-contrast: more) {
  :root:not([data-theme]) {
    --paper: #FFFFFF;
    --paper-2: #FFFFFF;
    --field: #F0F0F0;
    --ink: #000000;
    --ink-2: #000000;
    --ink-3: #000000;
    --rule: #000000;
    --rule-w: 2px;
    --accent: #003C87;
    --accent-ink: #00284F;
    --accent-soft: rgba(0, 0, 0, 0);
    --ok: #005A24;
    --warn: #6B3F00;
    --bad: #A81208;
    --brand-1: #00284F;
    --brand-2: #00284F;
    --brand-3: #00284F;
    --fill-hover: #00284F;
    --on-fill: #FFFFFF;
    --shadow: none;
    --btn-bg: #000000;
    --btn-fg: #FFFFFF;
    color-scheme: light;
  }
}

/* ------------------------------------------------------------------ high contrast (dark) */
[data-theme="contrast-dark"] {
  --paper: #000000;
  --paper-2: #000000;
  --field: #111111;
  --ink: #FFFFFF;
  --ink-2: #FFFFFF;
  --ink-3: #FFFFFF;
  --rule: #FFFFFF;
  --rule-w: 2px;
  --accent: #008FFF;
  --accent-ink: #008FFF;
  --accent-soft: rgba(0, 0, 0, 0);
  --ok: #7FFF9F;
  --warn: #FFE066;
  --bad: #FF8D81;
  --brand-1: #008FFF;
  --brand-2: #008FFF;
  --brand-3: #008FFF;
  --fill-hover: #FFFFFF;
  --on-fill: #000000;
  --shadow: none;
  --btn-bg: #FFFFFF;
  --btn-fg: #000000;
  color-scheme: dark;
}

/* ------------------------------------------------------------------ reader preferences */
[data-size="large"] { font-size: 112.5%; }
[data-size="xlarge"] { font-size: 125%; }
[data-spacing="wide"] body { letter-spacing: .12em; word-spacing: .16em; line-height: 1.8; }
[data-spacing="wide"] p, [data-spacing="wide"] li { margin-block-end: 2em; }
[data-spacing="wide"] h1, [data-spacing="wide"] h2, [data-spacing="wide"] h3 { letter-spacing: .04em; }
[data-motion="reduce"] *, [data-motion="reduce"] *::before, [data-motion="reduce"] *::after {
  transition: none !important; animation: none !important; scroll-behavior: auto !important;
}
@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after { transition: none !important; animation: none !important; scroll-behavior: auto !important; }
}

/* ------------------------------------------------------------------ reset */
*, *::before, *::after { box-sizing: border-box; }
/* Reserved permanently, not just while a popover is locking scroll — otherwise the scrollbar's own
   width appearing and disappearing shifts every fixed-width thing on the page sideways. */
html { font-size: 106.25%; scroll-behavior: smooth; -webkit-text-size-adjust: 100%; scrollbar-gutter: stable; }
/* The bar is sticky, so an anchor jump or a focused field would otherwise land underneath it
   (WCAG 2.4.11). The same padding keeps a :target heading on a legal page in view. */
html { scroll-padding-block-start: calc(var(--bar-h) + 8px); }
/* The scrollbar wears the brand accent instead of the browser default — `scrollbar-color` for
   Firefox and current Chrome, the `::-webkit-scrollbar-*` pseudo-elements for older Safari. */
html { scrollbar-color: var(--accent) var(--paper); }
::-webkit-scrollbar { width: 11px; height: 11px; }
::-webkit-scrollbar-track { background: var(--paper); }
::-webkit-scrollbar-thumb { background: var(--accent); border-radius: 999px; }
::-webkit-scrollbar-thumb:hover { background: var(--accent-ink); }
body {
  margin: 0; background: var(--paper); color: var(--ink);
  font-family: 'Inter Tight', system-ui, -apple-system, "Segoe UI", sans-serif;
  font-size: var(--t0); line-height: 1.6; font-weight: 400;
  -webkit-font-smoothing: antialiased; text-rendering: optimizeLegibility;
  /* §0.14: no text ever runs out of its box. `anywhere` breaks a word only when it cannot fit whole
     (a long Polish or German compound, a URL, an e-mail address) and, unlike `break-word`, also lowers
     the word's minimum width, so a grid or flex column holding it shrinks instead of overflowing.
     It is inherited, so it reaches the components' shadow roots too. */
  overflow-wrap: anywhere;
  word-break: normal;
}
/* A grid or flex child may shrink below its content's width only if it is allowed to: without this, one
   long word sets the column's minimum and pushes the row off a phone's screen. */
:where(.grid, [class*="-grid"], [class*="tiles"], .split, .cards) > * { min-width: 0; }
/* Long words in narrow, running text are hyphenated where the language allows it (the page's lang
   attribute picks the dictionary); display headlines never are, they break where the author put <br>. */
@media (max-width: 600px) { :where(p, li, dd, td, figcaption, .card h3, .sc-tile h3) { hyphens: auto; -webkit-hyphens: auto; } }
img, svg { max-width: 100%; display: block; }
svg { fill: none; }
button, input, textarea, select { font: inherit; color: inherit; }
button { cursor: pointer; }
/* Text links (ECOSYSTEM-GUIDELINES §0.11): no full underline at rest, only a short line under the
   first third, which stretches to full width with a spring on hover and focus. The line is a
   background, not text-decoration, so it follows a link that wraps onto two lines. Only class-less
   links get it: a link with a class is a component (a card, a tile, a menu row, a button) that draws
   its own affordance, and the list below switches it off for the class-less links inside those. */
:root {
  --u-rest: max(33%, 1.5em);
  --u-thick: 2px;
  --u-spring: cubic-bezier(.34, 1.56, .64, 1);
}
@supports (transition-timing-function: linear(0, 1)) {
  :root { --u-spring: linear(0, .21 7%, .6 15%, .92 23%, 1.08 30%, 1.13 35%, 1.1 41%, 1.02 50%, .98 57%, .985 66%, 1.003 80%, 1); }
}
[data-theme^="contrast"] { --u-thick: 3px; }
@media (prefers-contrast: more) { :root:not([data-theme]) { --u-thick: 3px; } }
a { color: var(--accent-ink); text-decoration: none; }
p a, li a, td a { overflow-wrap: anywhere; }
a:not([class]) {
  background-image: linear-gradient(currentColor, currentColor);
  background-position: 0 100%;
  background-repeat: no-repeat;
  background-size: var(--u-rest) var(--u-thick);
  padding-block-end: 2px;
  transition: background-size .6s var(--u-spring), color var(--dur, .2s);
}
a:not([class]):is(:hover, :focus-visible, :active) { background-size: 100% var(--u-thick); }
:is(.nav-menu, .lang-menu, .subnav, .pager, .read-on, .board-job, .ad-tile, .lg-links, .lg-post h3, .skip-list) a:not([class]) { background-image: none; padding-block-end: 0; }
/* The line is for links in the content only. Header, local bar and footer links are navigation, marked
   by their place and their colour change, so they never get it (Maciej, 2026-09-24). */
:is(body > header, body > footer, .bar, .foot) a:not([class]) { background-image: none; padding-block-end: 0; }
/* The shelf tiles draw their own affordance, but keep their padding: the reset above would zero it at the foot. */
.foot-family a:not([class]) { background-image: none; }
/* An arrow typed at the end of a text link ("How we count →") is wrapped by the build; it nudges
   along with the line. */
.link-arrow { display: inline-block; transition: translate .35s var(--u-spring); }
a:is(:hover, :focus-visible) .link-arrow { translate: 3px 0; }
@media (prefers-reduced-motion: reduce) { a:not([class]), .link-arrow { transition-duration: .01s; } }
[data-motion="reduce"] :is(a:not([class]), .link-arrow) { transition-duration: .01s; }
@media (forced-colors: active) { a:not([class]) { background-image: none; text-decoration: underline; } }
:focus-visible { outline: 3px solid var(--accent-ink); outline-offset: 3px; border-radius: 4px; }
:focus:not(:focus-visible) { outline: none; }
::selection { background: var(--brand-3); color: #0B0C10; }
hr { border: 0; border-top: var(--rule-w) solid var(--rule); margin: 32px 0; }
code, pre, kbd, samp { font-family: ui-monospace, "SF Mono", Menlo, Consolas, "Liberation Mono", monospace; font-size: .92em; }
[hidden] { display: none !important; }
.sr { position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px; overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; border: 0; }

/* ------------------------------------------------------------------ type */
h1, h2, h3, h4 { font-weight: 600; letter-spacing: -.025em; line-height: 1.12; margin: 0 0 .5em; text-wrap: balance; color: var(--ink); }
h1 { font-size: clamp(var(--t4), 5.5vw, var(--t6)); }
h2 { font-size: clamp(var(--t2), 3.4vw, var(--t4)); }
h3 { font-size: var(--t2); }
h4 { font-size: var(--t1); }
p { margin: 0 0 1.1em; }
.lead { font-size: var(--t1); color: var(--ink-2); line-height: 1.5; max-width: 60ch; text-wrap: pretty; }
/* The kicker above a section's heading. It had no rule of its own and read as a stray sentence;
   this is Maciej's, a small bold uppercase line in the accent. `.panel .objection` further down is
   more specific and keeps the panels' own larger treatment. */
.objection { font-weight: bold; text-transform: uppercase; font-size: max(80%, var(--t-1)); color: var(--accent-ink); letter-spacing: 2px; line-height: 1.4; margin-block-end: .9em; }
/* The Blues pack (04B) is SVG line work, so unlike fabls.io's raster Hipster art it is recoloured,
   never dimmed (guidelines §0.3). Every colour is a role read from the theme: ink is the page's ink,
   the pale fills are the ink mixed into the page's paper (a light grey on light, a lifted grey on
   dark), and the pink accent is castomFlow blue. So all four themes redraw the art with no rule of
   their own. <blues-art> (the components) reads --blues-ink / --blues-paper through its shadow
   root; the drawings the build inlines (tools/inline_art.py: heroes, pager cards) read the
   --art-* roles through the classes it writes.
   A custom property that holds var(--ink) is resolved where it is declared, so these are declared
   again wherever --ink changes: on :root (every theme), on each chapter (the dark .sc-ink chapter
   redefines --ink; a :root-only declaration kept black lines on it), and on the art itself. The
   <blues-art> a component draws sits inside that component's shadow root, where no page selector
   reaches, so it lives on what it inherits from the chapter. */
:root, .sc-chapter, blues-art, .kit-ink {
  --art-ink: var(--ink);
  --art-paper: color-mix(in srgb, var(--ink) 7%, var(--paper));
  --art-paper-2: color-mix(in srgb, var(--ink) 16%, var(--paper));
  --art-1: var(--brand-1);
  --art-2: var(--brand-2);
  --art-3: var(--brand-3);
  /* <blues-art> paints its accents from --slot-0/1/2 and its unfilled shapes from --blues-empty.
     --noodle-accent / --noodle-on-accent are the names Blues' motion.js reads for its CTA fill and the
     words on it (a name it shares with Noodle): pinned to the dark stop and white, so a kit CTA band
     never puts white words on the pale slot (the castomAI white-on-white lesson). */
  --slot-0: var(--art-2);
  --slot-1: var(--art-1);
  --slot-2: var(--art-paper-2);
  --blues-empty: var(--art-paper-2);
  --blues-ink: var(--art-ink);
  --blues-paper: var(--art-paper);
  --noodle-accent: var(--brand-1);
  --noodle-on-accent: #fff;
}
.kit-ink { fill: var(--art-ink); overflow: visible; }
.kit-ink .fp { fill: var(--art-paper); }
.kit-ink .fq { fill: var(--art-paper-2); }
.kit-ink .fa { fill: var(--art-2); }
.kit-ink .fb { fill: var(--art-1); }
.kit-ink .sk { stroke: var(--art-ink); }
.kit-ink .sa { stroke: var(--art-2); }
.kit-ink .sq { stroke: var(--art-paper-2); }
.kit-ink .sp { stroke: var(--art-paper); }

/* The one gradient term an h1 may carry (DESIGN §3): a compound joined by U+00A0 counts as one. It is
   large text, so 3:1 applies. Upright, because italics are not how this site emphasises; the stops
   are the ones that measure on each ground — the dark stop drops to 3.28 on the dark page, so there
   the ramp starts at the mid stop. The contrast themes and forced colours get flat ink. */
h1 em { font-style: normal; background: linear-gradient(90deg, var(--brand-1), var(--brand-2)); -webkit-background-clip: text; background-clip: text; color: transparent; -webkit-box-decoration-break: clone; box-decoration-break: clone; }
[data-theme="dark"] h1 em { background-image: linear-gradient(90deg, var(--brand-2), var(--brand-3)); }
@media (prefers-color-scheme: dark) { :root:not([data-theme]) h1 em { background-image: linear-gradient(90deg, var(--brand-2), var(--brand-3)); } }
[data-theme^="contrast"] h1 em { background: none; color: var(--accent-ink); }
@media (prefers-contrast: more) { :root:not([data-theme]) h1 em { background: none; color: var(--accent-ink); } }
@media (forced-colors: active) { h1 em { background: none; color: CanvasText; } }
.num { font-variant-numeric: tabular-nums; }
.muted { color: var(--ink-3); }

/* ------------------------------------------------------------------ layout */
.wrap { width: min(var(--wrap), 100% - clamp(32px, 8vw, 64px)); margin-inline: auto; }
.section { padding-block: clamp(56px, 8vw, 96px); }
.section + .section { padding-top: 0; }
.section.band + .section, .section.band-ink + .section,
.section + .section.band, .section + .section.band-ink { padding-top: clamp(56px, 8vw, 96px); }
.section-head { margin-bottom: clamp(24px, 4vw, 40px); max-width: 68ch; }
.band { background: var(--paper-2); border-block: var(--rule-w) solid var(--rule); }
/* The inverted band: ink ground, paper text — in the dark theme it is the light band. Written in
   the two tokens only, never a literal, or one theme fails contrast. */
.band-ink { background: var(--ink); color: var(--paper); }
.band-ink h2, .band-ink h3 { color: var(--paper); }
.band-ink a:not(.btn) { color: var(--paper); }
.band-ink .lead, .band-ink .muted { color: var(--paper); opacity: .85; }
.band-ink .btn-primary { background: var(--paper); color: var(--ink); }
.band-ink .btn-primary:hover { background: var(--brand-3); color: #0B0C10; }
/* In the contrast themes --brand-3 is the theme's own deep accent, which #0B0C10 cannot sit on; the
   hover there inverts to the band's own pair and says so with an outline instead of a colour. */
[data-theme^="contrast"] .band-ink .btn-primary:hover { background: var(--ink); color: var(--paper); outline: 3px solid var(--paper); outline-offset: 2px; }
@media (prefers-contrast: more) { :root:not([data-theme]) .band-ink .btn-primary:hover { background: var(--ink); color: var(--paper); outline: 3px solid var(--paper); outline-offset: 2px; } }
.band-ink .btn-ghost { color: var(--paper); border-color: var(--paper); }
.band-ink .btn-ghost:hover { background: var(--paper); color: var(--ink); }
.grid { display: grid; gap: 20px; }
/* No gaps in a structured grid (ECOSYSTEM-GUIDELINES §0.13). The numbered grids are flex rows, not CSS
   grid: every cell starts at 1/--cols of the row and may grow, so a short last row widens to fill the
   line (7 in threes is 3 + 3 + 1 with the one full width; 8 is 3 + 3 + 2 with the two at half) instead
   of leaving holes. The column count still drops at each breakpoint, and the same rule holds there. */
.grid:is(.grid-2, .grid-3, .grid-4) { display: flex; flex-wrap: wrap; --cols: 3; }
.grid-2 { --cols: 2; }
.grid-4 { --cols: 4; }
/* The basis allows for the widest gap any band sets (24 px), so a full row never wraps early. */
.grid:is(.grid-2, .grid-3, .grid-4) > * { flex: 1 1 calc((100% - (var(--cols) - 1) * 32px) / var(--cols)); }
@media (max-width: 960px) { .grid:is(.grid-3, .grid-4) { --cols: 2; } }
@media (max-width: 600px) { .grid:is(.grid-2, .grid-3, .grid-4) { --cols: 1; } }
.split { display: grid; grid-template-columns: 1fr 1fr; gap: clamp(24px, 5vw, 64px); align-items: center; }
@media (max-width: 860px) { .split { grid-template-columns: 1fr; } }
/* Every layout child may shrink below its content; the scroll containers inside then scroll. */
.grid > *, .split > *, .hero-grid > * { min-width: 0; }

/* ------------------------------------------------------------------ skip + bar */
.skip { position: absolute; inset-inline-start: 16px; inset-block-start: 8px; transform: translateY(-160%); background: var(--ink); color: var(--paper); padding: 10px 16px; border-radius: var(--radius-pill); z-index: 100; text-decoration: none; font-weight: 600; }
.skip:focus { transform: none; }

/* The header rests see-through, with no rule under it, and turns white (99% --paper-2) once the page
   scrolls; it never shows a border (the owner, 2026-09-24). Phones: .is-scrolled from header.js;
   desktop: the pill layer below does the same with --p. The border stays, transparent, so the bar
   keeps its height. */
.bar { position: sticky; top: 0; z-index: 50; background-color: color-mix(in srgb, var(--paper-2) 0%, transparent); border-bottom: var(--rule-w) solid transparent; }
@media (max-width: 1200px) { .bar.is-scrolled { background-color: color-mix(in srgb, var(--paper-2) 99%, transparent); } }
.bar-in { display: flex; align-items: center; gap: 10px; min-height: var(--bar-h); }
.brand { display: inline-flex; align-items: center; gap: 10px; color: var(--ink); text-decoration: none; font-weight: 700; font-size: var(--t1); letter-spacing: -.02em; flex: none; }
.brand svg { width: 32px; height: 32px; }
.brand b { color: var(--accent-ink); font-weight: 700; }
/* The mark's plate stays dark under a dark theme instead of following the light one it was drawn
   for — a light square on a dark bar reads as a mistake, not a mark. */
:is([data-theme="dark"], [data-theme="contrast-dark"]) .brand svg > rect { fill: #111111; }
@media (prefers-color-scheme: dark) {
  :root:not([data-theme]) .brand svg > rect { fill: #111111; }
}
/* Marks pop on hover — ported from castomapi.com, after the brand kit's "Draw — Pop" cut
   (kupisiewicz/brand-kit, tools/build_animations.py draw_pop): each tile of a mark pops in from 45%
   with a small overshoot, one after another, and settles. The kit's own keyframes and easing, cut to
   the 0.64 s draw-in so it plays once per hover instead of looping. The animation exists only while
   the link is hovered or focused, so each new hover replays it; `both` holds each tile at its first
   frame until its stagger has passed.

   The header logo sits on its plate, with its tiles inside a <g> that carries the plate's scale, so
   the logo's tiles are `svg > g > *` and that <g> is never touched — a CSS transform-origin or
   transform reaching an SVG transform attribute moves it. A family tile's mark is plate-less, its
   tiles the direct children of its <svg>, and the faint background copy (.shelf-bg, site.js) stays
   out. The hover rules set the animation longhands, because the shorthand would reset the per-tile
   delays below. Under reduced motion the global rule removes the animation and the mark stays whole. */
:is(.brand svg > g, .foot-family a > svg:not(.shelf-bg)) > * { transform-box: fill-box; transform-origin: center; }
:is(.brand:hover, .brand:focus-visible) svg > g > *, :is(.foot-family a:hover, .foot-family a:focus-visible) > svg:not(.shelf-bg) > * {
  animation-name: mk-pop; animation-duration: .64s; animation-timing-function: cubic-bezier(.2, .7, .3, 1); animation-fill-mode: both;
}
@keyframes mk-pop {
  0% { opacity: 0; transform: scale(.45); }
  70% { opacity: 1; transform: scale(1.08); }
  100% { opacity: 1; transform: scale(1); }
}
/* The kit's stagger, by position: tile k of n starts (k-1) × 0.42 s / (n-1) after the hover begins.
   `:nth-child(k):nth-last-child(n-k+1)` picks tile k of a mark with exactly n tiles, so no delay is
   written into the markup. Marks of up to five tiles are covered (castomapi-www's tools/sync_marks.py
   refuses a mark with more). The first tile of every mark starts at once. */
:is(.brand svg > g, .foot-family a > svg:not(.shelf-bg)) > :nth-child(2):nth-last-child(1) { animation-delay: .42s; }
:is(.brand svg > g, .foot-family a > svg:not(.shelf-bg)) > :nth-child(2):nth-last-child(2) { animation-delay: .21s; }
:is(.brand svg > g, .foot-family a > svg:not(.shelf-bg)) > :nth-child(3):nth-last-child(1) { animation-delay: .42s; }
:is(.brand svg > g, .foot-family a > svg:not(.shelf-bg)) > :nth-child(2):nth-last-child(3) { animation-delay: .14s; }
:is(.brand svg > g, .foot-family a > svg:not(.shelf-bg)) > :nth-child(3):nth-last-child(2) { animation-delay: .28s; }
:is(.brand svg > g, .foot-family a > svg:not(.shelf-bg)) > :nth-child(4):nth-last-child(1) { animation-delay: .42s; }
:is(.brand svg > g, .foot-family a > svg:not(.shelf-bg)) > :nth-child(2):nth-last-child(4) { animation-delay: .105s; }
:is(.brand svg > g, .foot-family a > svg:not(.shelf-bg)) > :nth-child(3):nth-last-child(3) { animation-delay: .21s; }
:is(.brand svg > g, .foot-family a > svg:not(.shelf-bg)) > :nth-child(4):nth-last-child(2) { animation-delay: .315s; }
:is(.brand svg > g, .foot-family a > svg:not(.shelf-bg)) > :nth-child(5):nth-last-child(1) { animation-delay: .42s; }
*[data-current] { color: var(--accent); }
.nav-links { display: flex; align-items: center; gap: 2px; margin-inline-start: 8px; margin-inline-end: auto; }
.nav-top, .nav-group > summary { font-size: var(--t-1); font-weight: 500; color: var(--ink-2); padding: 8px 12px; border-radius: var(--radius-sm); text-decoration: none; white-space: nowrap; line-height: 1.2; }
.nav-top:hover, .nav-group > summary:hover, .nav-group[open] > summary { color: var(--ink); background: var(--field); }
/* The section you are in marks itself in the top bar (build_site.py puts data-current on its menu),
   which is what tells a reader arriving from a search result where they have landed. The mark is on
   the <details>, but its <summary> sets a colour of its own and would keep it — including while the
   menu is open — so the summary is told to take the group's instead. */
*[data-current] { color: var(--accent-ink); }
.nav-group[data-current] > summary, .nav-group[data-current][open] > summary { color: inherit; }
.nav-group { position: relative; }
.nav-group > summary { list-style: none; cursor: pointer; display: inline-flex; align-items: center; gap: 6px; }
.nav-group > summary::-webkit-details-marker { display: none; }
.nav-group > summary::after { content: ""; width: 7px; height: 7px; border-right: 1.8px solid currentColor; border-bottom: 1.8px solid currentColor; transform: translateY(-2px) rotate(45deg); transition: transform var(--dur) var(--ease); }
.nav-group[open] > summary::after { transform: translateY(1px) rotate(-135deg); }
.nav-menu { position: absolute; top: calc(100% + 10px); inset-inline-start: 0; background: var(--paper-2); border: var(--rule-w) solid var(--rule); border-radius: var(--radius); box-shadow: var(--shadow); padding: 12px; display: grid; gap: 4px; grid-template-columns: repeat(auto-fit, minmax(15rem, 1fr)); width: min(var(--menu-w, 40rem), calc(100vw - 2.5rem)); z-index: 60; animation: menu-in var(--dur) var(--ease); }
/* The gap between the trigger and the panel would drop the hover; this bridges it. */
.nav-menu::before { content: ""; position: absolute; inset-inline: 0; top: -12px; height: 12px; }
.nav-menu[data-cols="3"] { --menu-w: 56rem; }
.nav-menu[data-cols="1"] { --menu-w: 22rem; }
@keyframes menu-in { from { opacity: 0; transform: translateY(-4px); } to { opacity: 1; transform: none; } }
.nav-menu a { display: grid; grid-template-columns: 2.4rem 1fr; grid-template-rows: auto auto; column-gap: 12px; align-items: center; padding: 8px 10px; border-radius: var(--radius-sm); text-decoration: none; color: var(--ink); }
.nav-menu a:hover, .nav-menu a:focus-visible { background: var(--field); }
.nav-menu a b { font-weight: 600; font-size: var(--t-1); line-height: 1.3; }
.nav-menu a span:not(.nav-ico) { grid-column: 2; font-size: var(--t-1); color: var(--ink-3); line-height: 1.35; }
.nav-ico { grid-row: 1 / span 2; width: 2.4rem; height: 2.4rem; border-radius: 10px; background: var(--accent-soft); color: var(--accent-ink); display: grid; place-items: center; border: var(--rule-w) solid transparent; }
[data-theme="contrast"] .nav-ico, [data-theme="contrast-dark"] .nav-ico { border-color: var(--rule); }
.nav-ico svg { width: 1.3rem; height: 1.3rem; stroke: currentColor; stroke-width: 1.8; stroke-linecap: round; stroke-linejoin: round; }
/* The hover fill is the pair that always holds words (DESIGN §2.2): --accent under --on-fill, which is
   white on the dark stop in three themes and black on the light stop in contrast-dark. */
.nav-menu a:hover .nav-ico { background: var(--accent); color: var(--on-fill); }
[data-theme="contrast"] .nav-menu a:hover .nav-ico, [data-theme="contrast-dark"] .nav-menu a:hover .nav-ico { background: var(--ink); color: var(--paper); }
.nav-menu a.compact { grid-template-rows: auto; padding: 5px 8px; }
.nav-menu a.compact .nav-ico { grid-row: 1; width: 1.9rem; height: 1.9rem; border-radius: 8px; }
.nav-menu a.compact .nav-ico svg { width: 1.05rem; height: 1.05rem; }
.nav-col { display: grid; gap: 2px; align-content: start; }
.nav-col h3 { font-size: var(--t-1); font-weight: 600; color: var(--ink-3); letter-spacing: .06em; text-transform: uppercase; margin: 8px 10px 6px; }
.nav-col + .nav-col { border-inline-start: var(--rule-w) solid var(--rule); padding-inline-start: 12px; }
.nav-menu .nav-more { grid-column: 1 / -1; margin-top: 6px; padding: 10px; border-top: var(--rule-w) solid var(--rule); font-size: var(--t-1); font-weight: 600; text-align: center; display: block; }

.bar-end { display: flex; align-items: center; gap: 6px; flex: none; }
.icon-btn { display: inline-grid; place-items: center; width: 40px; height: 40px; border-radius: var(--radius-pill); border: var(--rule-w) solid transparent; background: transparent; color: var(--ink-2); }
.icon-btn:hover, .icon-btn[aria-expanded="true"] { background: var(--field); color: var(--ink); }
.icon-btn svg { width: 20px; height: 20px; stroke: currentColor; stroke-width: 1.8; stroke-linecap: round; stroke-linejoin: round; }
.btn { display: inline-flex; align-items: center; justify-content: center; gap: 8px; padding: 11px 20px; border-radius: var(--radius-pill); font-weight: 600; font-size: var(--t-1); text-decoration: none; border: var(--rule-w) solid transparent; line-height: 1.2; min-height: 44px; transition: transform var(--dur) var(--ease), background var(--dur), color var(--dur); }
.btn:hover { transform: translateY(-1px); }
.btn-primary { background: var(--btn-bg); color: var(--btn-fg); }
/* Ink on the mid stop, not white on it: the dark plate colour carries far more contrast than white does. */
.btn-primary:hover { background: var(--fill-hover); color: var(--on-fill); }
[data-theme="contrast"] .btn-primary:hover, [data-theme="contrast-dark"] .btn-primary:hover { background: var(--btn-bg); color: var(--btn-fg); outline: 3px solid var(--accent-ink); outline-offset: 2px; }
.btn-ghost { background: transparent; color: var(--ink); border-color: var(--rule); }
.btn-ghost:hover { background: var(--field); }
.btn-lg { font-size: var(--t0); padding: 14px 26px; }
.btn svg { width: 18px; height: 18px; stroke: currentColor; stroke-width: 2; stroke-linecap: round; stroke-linejoin: round; }
/* The icon every button carries. The build picks it from where the button goes and gives it one of
   the icon set's motions, which icons.css plays on hover or keyboard focus of the button and freezes
   under prefers-reduced-motion — so nothing here has to animate it. It is decoration in the strict
   sense: the label already says what the button does, and the icon is aria-hidden.
   `overflow: visible` lets a motion that leaves the icon's own box, like the rocket's, actually
   leave it, and the small inset stops the larger ones touching the button's edge. */
.btn .btn-ico { overflow: visible; margin-inline-end: 1px; }
.btn-lg .btn-ico { width: 20px; height: 20px; }
/* Every button icon here is drawn once, pointing right — an arrow, an external-link
   corner, a send glyph — because that is the reading direction the paths were drawn
   for. Under RTL the button's own text and layout already mirror through logical
   properties (margin-inline-end above, the flex order that follows dir); the icon
   is a fixed SVG path and does not, so it has to be told to. */
[dir="rtl"] .btn .btn-ico { transform: scaleX(-1); }
/* The working ring is not an arrow: it has no direction to mirror. */
[dir="rtl"] .btn .btn-ring { transform: none; }

/* the language picker and the accessibility panel */
.lang, .a11y { position: relative; }
.lang-btn { display: inline-flex; align-items: center; gap: 4px; height: 40px; padding: 0 10px; border-radius: var(--radius-pill); border: var(--rule-w) solid transparent; background: transparent; color: var(--ink-2); font-weight: 600; font-size: var(--t-1); text-transform: uppercase; letter-spacing: .04em; }
.lang-btn:hover, .lang-btn[aria-expanded="true"] { background: var(--field); color: var(--ink); }
.lang-btn svg { width: 10px; height: 6px; stroke: currentColor; }
.pop { position: absolute; inset-inline-end: 0; top: calc(100% + 8px); background: var(--paper-2); border: var(--rule-w) solid var(--rule); border-radius: var(--radius); box-shadow: var(--shadow); padding: 8px; min-width: 12rem; z-index: 70; animation: menu-in var(--dur) var(--ease); }
.pop-head { display: flex; align-items: center; justify-content: space-between; gap: 12px; margin: 0 0 12px; }
.pop-close { width: 32px; height: 32px; flex: none; }
.pop-close svg { width: 16px; height: 16px; }
.lang-menu { list-style: none; margin: 0; padding: 0; }
.lang-menu a { display: block; padding: 8px 12px; border-radius: var(--radius-sm); text-decoration: none; color: var(--ink); font-size: var(--t-1); }
.lang-menu a:hover { background: var(--field); }
.lang-menu a[aria-current="true"] { font-weight: 600; box-shadow: inset 3px 0 0 var(--accent); }
.lang-menu li[hidden] { display: none; }
.a11y-panel { width: min(22rem, calc(100vw - 2rem)); padding: 16px; }
.a11y-h { font-size: var(--t0); margin: 0; }
/* the language popover: a search box over a scrollable, multi-column list — 99 languages as a single
   vertical column was a screen and a half of scrolling before a reader's own language turned up. */
.lang-pop { width: min(26rem, calc(100vw - 2rem)); max-height: min(70vh, 34rem); padding: 12px; display: flex; flex-direction: column; }
.lang-pop-body { overflow-y: auto; -webkit-overflow-scrolling: touch; margin: 0 -4px; padding: 0 4px; }
.lang-search { display: flex; align-items: center; gap: 8px; height: 40px; margin: 0 0 10px; padding: 0 10px; border-radius: var(--radius-pill); border: var(--rule-w) solid var(--rule); background: var(--field); flex: none; }
.lang-search svg { width: 16px; height: 16px; stroke: var(--ink-3); stroke-width: 1.8; flex: none; }
.lang-search input { flex: 1; min-width: 0; border: 0; background: none; font: inherit; color: var(--ink); }
.lang-search input:focus { outline: none; }
.lang-group-h { font-size: var(--t-1); font-weight: 600; color: var(--ink-3); text-transform: uppercase; letter-spacing: .06em; margin: 14px 0 6px; }
.lang-group-h:first-child { margin-top: 0; }
/* The languages suggested from the browser's own list, ruled off above everything else. */
.lang-suggested { margin: 0 0 12px; padding: 0 0 10px; border-bottom: var(--rule-w) solid var(--rule); }
.lang-menu--suggested { display: grid; gap: 2px; }
.lang-menu--all { columns: 2; column-gap: 10px; }
.lang-menu--all li { break-inside: avoid; }
.lang-empty { font-size: var(--t-1); color: var(--ink-3); margin: 0; padding: 12px 4px; }
@media (min-width: 760px) { .lang-pop { width: min(34rem, calc(100vw - 2rem)); } .lang-menu--all { columns: 3; } }
@media (max-width: 420px) { .lang-menu--all { columns: 1; } }
/* a scrim behind a full-screen mobile popover, so it reads as a takeover rather than a stray box */
.pop-scrim { display: none; }
.a11y-set { border: 0; padding: 0; margin: 0 0 14px; }
.a11y-set legend { font-size: var(--t-1); font-weight: 600; color: var(--ink-2); padding: 0; margin-bottom: 6px; display: block; }
.a11y-opts { display: flex; flex-wrap: wrap; gap: 6px; }
.a11y-opts button { padding: 7px 12px; border-radius: var(--radius-pill); border: var(--rule-w) solid var(--rule); background: transparent; font-size: var(--t-1); font-weight: 500; min-height: 36px; }
.a11y-opts button[aria-checked="true"] { background: var(--ink); color: var(--paper); border-color: var(--ink); }
.a11y-switch { display: flex; align-items: center; justify-content: space-between; width: 100%; padding: 8px 0; background: none; border: 0; font-weight: 600; font-size: var(--t-1); text-align: left; min-height: 40px; }
.a11y-dot { width: 40px; height: 22px; border-radius: 11px; background: var(--field); border: var(--rule-w) solid var(--rule); position: relative; flex: none; }
.a11y-dot::after { content: ""; position: absolute; top: 2px; left: 2px; width: 16px; height: 16px; border-radius: 50%; background: var(--ink-3); transition: transform var(--dur) var(--ease), background var(--dur); }
.a11y-switch[aria-checked="true"] .a11y-dot { background: var(--accent); border-color: var(--accent); }
.a11y-switch[aria-checked="true"] .a11y-dot::after { transform: translateX(18px); background: var(--on-fill); }
.a11y-hint { font-size: var(--t-1); color: var(--ink-3); margin: -2px 0 6px; }
.a11y-foot { margin: 8px 0 0; font-size: var(--t-1); }
/* Below 640px the trigger buttons sit close to the bar's own edge, so a popover anchored to their
   right edge (inset-inline-end: 0 above) runs off the LEFT of the viewport instead — the panel is
   there, it is just mostly off-screen. Below that width both popovers become a full-screen sheet
   instead of a dropdown: no anchor point to overflow, room for the language list, a close button
   because outside-click is not a reliable gesture once the sheet covers everything. */
@media (max-width: 640px) {
  .pop { position: fixed; inset: 0; width: auto; max-width: none; min-width: 0; max-height: none; height: 100dvh; border-radius: 0; border: 0; box-shadow: none; padding: max(16px, env(safe-area-inset-top)) 16px max(16px, env(safe-area-inset-bottom)); z-index: 80; animation: pop-sheet-in var(--dur) var(--ease); }
  .a11y-panel, .lang-pop { width: auto; max-height: none; }
  /* The sheet is exactly one viewport tall and the page under it is locked, so a panel taller than a
     short or landscape phone (or one grown by its own "Larger" text setting) must scroll ITSELF, or
     its last options sit below the fold with nothing able to reach them. Side insets clear the notch. */
  .a11y-panel { overflow-y: auto; overscroll-behavior: contain; -webkit-overflow-scrolling: touch; padding-inline: max(16px, env(safe-area-inset-left)) max(16px, env(safe-area-inset-right)); }
  /* Below .bar's own z-index: 50 — the popover is a descendant of .bar and cannot render above a
     sibling stacking context whose root sits higher than .bar's, however high its OWN z-index is. */
  .pop-scrim:not([hidden]) { display: block; position: fixed; inset: 0; background: rgba(10, 12, 16, .45); z-index: 45; }
}
@keyframes pop-sheet-in { from { opacity: 0; transform: translateY(8px); } to { opacity: 1; transform: translateY(0); } }
/* The page is locked only while a popover is the full-screen phone sheet above. On desktop the
   popover hangs from the bar and the page must stay as it is: overflow: hidden on <body> turns it
   into a clipping scroll container at scroll 0, and the sticky .bar/.subnav then stick to its top,
   i.e. jump off screen when the page is scrolled down. */
@media (max-width: 640px) { html.pop-lock, html.pop-lock body { overflow: hidden; } }

/* read-aloud: one button on the bar, a stop button beside it while reading */
.player { display: inline-flex; align-items: center; gap: 4px; }
.player[data-state="unsupported"] { display: none; }
.player-btn { display: inline-flex; align-items: center; gap: 8px; height: 40px; padding: 0 14px 0 10px; border-radius: var(--radius-pill); border: var(--rule-w) solid var(--rule); background: transparent; color: var(--ink-2); font-weight: 600; font-size: var(--t-1); }
.player-btn:hover { background: var(--field); color: var(--ink); }
.player-btn svg { width: 16px; height: 16px; fill: currentColor; }
.player[data-state="idle"] .i-pause, .player[data-state="playing"] .i-play, .player[data-state="paused"] .i-pause { display: none; }
.player.is-active .player-btn { background: var(--accent); color: var(--on-fill); border-color: var(--accent); }
.is-reading { outline: 3px solid var(--accent-ink); outline-offset: 6px; border-radius: 4px; }
@media (max-width: 720px) { .player-label { display: none; } .player-btn { width: 40px; padding: 0; justify-content: center; } }

/* the drawer */
.menu-toggle { position: absolute; opacity: 0; width: 0; height: 0; margin: 0; pointer-events: none; }
.burger { display: none; }
.menu-body { display: contents; }
/* The drawer takes over at 1200, not at the 1080 the rest of the layout turns on: the bar carries five
   menus and a link since 2026-09-21, and below 1200 they no longer fit beside the call to action. */
@media (max-width: 1200px) {
  .bar-in { gap: 6px; }
  .burger { display: grid; place-items: center; width: 40px; height: 40px; border-radius: var(--radius-pill); color: var(--ink-2); order: 10; cursor: pointer; }
  .burger:hover { background: var(--field); color: var(--ink); }
  .burger svg { width: 22px; height: 22px; stroke: currentColor; stroke-width: 2; stroke-linecap: round; }
  .burger .burger-x { display: none; }
  .menu-toggle:checked ~ .burger .burger-x { display: block; }
  .menu-toggle:checked ~ .burger svg:not(.burger-x) { display: none; }
  .menu-toggle:focus-visible ~ .burger { outline: 3px solid var(--accent-ink); outline-offset: 2px; }
  .bar-end { margin-inline-start: auto; }
  .bar-end .bar-cta { display: none; }
  .menu-body { display: none; position: absolute; inset-inline: 0; top: 100%; background: var(--paper-2); border-bottom: var(--rule-w) solid var(--rule); box-shadow: var(--shadow); padding: 12px clamp(16px, 4vw, 32px) 20px; max-height: calc(100vh - var(--bar-h)); overflow: auto; }
  .menu-toggle:checked ~ .menu-body { display: block; }
  .nav-links { display: grid; gap: 4px; margin: 0; }
  .nav-top, .nav-group > summary { display: flex; align-items: center; justify-content: space-between; font-size: var(--t0); padding: 12px 10px; }
  .nav-group { position: static; }
  .nav-menu, .nav-menu[data-cols="3"] { position: static; width: auto; box-shadow: none; border: 0; padding: 4px 0 12px 8px; grid-template-columns: 1fr; animation: none; }
  .nav-menu::before { display: none; }
  .nav-col + .nav-col { border: 0; padding: 0; }
  .nav-menu a b { font-size: var(--t0); }
  .drawer-cta { display: flex; margin-top: 12px; }
}
/* On a wide screen the burger is hidden, so its checkbox must not exist as a form control either —
   an off-screen checkbox with a hidden label is an unlabelled field to a checker. */
@media (min-width: 1201px) { .drawer-cta { display: none; } .menu-toggle { display: none; } }

/* The section tab row under the header (dash-b's landing shape, and castomapi.com's): the current
   page's siblings on one line, scrolling sideways when there are more than fit. An underline, not a
   pill, so it reads as "where you are" rather than as a row of buttons. */
.subnav { border-bottom: var(--rule-w) solid var(--rule); background: var(--paper-2); position: sticky; top: var(--bar-h); z-index: 30; }
.subnav ul { display: flex; gap: 4px; list-style: none; margin: 0; padding: 0; overflow-x: auto; scrollbar-width: none; }
.subnav ul::-webkit-scrollbar { display: none; }
.subnav li { flex: none; }
.subnav a { display: block; padding: 14px 10px; font-size: var(--t-1); font-weight: 500; color: var(--ink-2); text-decoration: none; white-space: nowrap; border-bottom: 2px solid transparent; }
.subnav a:hover { color: var(--ink); }
.subnav a[aria-current="page"] { color: var(--ink); font-weight: 600; border-bottom-color: var(--accent); }
.subnav a:focus-visible { outline: 3px solid var(--accent-ink); outline-offset: -3px; border-radius: var(--radius-sm); }
@media print { .subnav { display: none; } }

/* ------------------------------------------------------------------ prose (the written pages) */
.prose { max-width: 68ch; }
/* When .prose sits ON the .wrap rather than inside it, capping the wrap itself would centre the
   column and leave the headings above it starting somewhere else. Cap the children instead: the box
   keeps the page's left edge, the measure stays readable. */
.wrap.prose { max-width: none; }
.wrap.prose > * { max-width: 68ch; }
.prose > * + h2 { margin-top: 1.8em; }
.prose > * + h3 { margin-top: 1.5em; }
.prose h2 { font-size: var(--t2); }
.prose h3 { font-size: var(--t1); }
.prose ul, .prose ol { padding-inline-start: 1.3em; margin: 0 0 1.2em; color: var(--ink-2); }
.prose li + li { margin-top: .4em; }
.prose blockquote { margin: 1.5em 0; padding: 4px 0 4px 20px; border-inline-start: 3px solid var(--accent); color: var(--ink-2); font-size: var(--t1); }
.prose dl { margin: 0 0 1.2em; }
.prose dt { font-weight: 600; margin-top: 1em; }
.prose dd { margin: 0 0 .2em; color: var(--ink-2); }
.prose table { font-size: var(--t-1); margin: 1.2em 0; }
.prose th, .prose td { text-align: left; padding: 10px 12px; border-bottom: var(--rule-w) solid var(--rule); vertical-align: top; }
.prose th { font-weight: 600; color: var(--ink-2); }
.table-wrap { overflow-x: auto; }

/* The page head on an inner page: where you are, what this is, and nothing else competing. */
.page-head { padding: clamp(36px, 6vw, 64px) 0 0; }
.page-head h1 { font-size: clamp(var(--t3), 5vw, var(--t5)); letter-spacing: -.03em; margin: 8px 0 16px; max-width: 20ch; }
.page-head .lead { font-size: clamp(var(--t0), 2vw, var(--t1)); }
.crumbs { font-size: var(--t-1); color: var(--ink-3); margin-block: 0 4px; }
.crumbs ol { list-style: none; margin: 0; padding: 0; display: flex; flex-wrap: wrap; gap: 4px 8px; }
.crumbs li + li::before { content: "/"; content: "/" / ""; margin-inline-end: 8px; color: var(--rule); }
.crumbs a { color: var(--ink-3); text-decoration: none; }
.crumbs a:hover { color: var(--ink); }

/* The next/previous pair at the foot of a section page: the tab row's job for someone reading
   straight through rather than picking. */
.pager { display: grid; grid-template-columns: 1fr 1fr; gap: 16px; margin-top: clamp(32px, 5vw, 56px); }
@media (max-width: 600px) { .pager { grid-template-columns: 1fr; } }
.pager a { display: block; padding: 18px 20px; border: var(--rule-w) solid var(--rule); border-radius: var(--radius); text-decoration: none; color: var(--ink); background: var(--paper-2); }
.pager a:hover { border-color: var(--accent); }
.pager small { display: block; color: var(--ink-3); font-size: var(--t-1); margin-bottom: 4px; }
.pager b { font-weight: 600; }
.pager .next { text-align: end; }

/* A quiet aside for the joke that would break the paragraph it belongs to. */
/* The italic is the aside's identity (DESIGN §3): the one place the site speaks in a dry human voice. */
.aside { border-inline-start: 3px solid var(--accent); padding: 4px 0; padding-inline-start: 18px; margin: 24px 0; color: var(--ink-2); font-size: var(--t-1); font-style: italic; max-width: 60ch; }
.aside b { color: var(--ink); font-style: normal; }

/* ------------------------------------------------------------------ footer + back to top */
/* `overflow-x: clip` because the family shelf below runs out to the edges of the window, and 100vw
   counts the scrollbar — without it the page would gain a few pixels of sideways scroll. */
.foot { margin-top: clamp(64px, 10vw, 120px); border-top: var(--rule-w) solid var(--rule); background: var(--paper-2); padding: clamp(40px, 6vw, 64px) 0 32px; font-size: var(--t-1); color: var(--ink-2); overflow-x: clip; }
.foot-grid { display: grid; grid-template-columns: 1.4fr repeat(4, 1fr); gap: 24px; }
@media (max-width: 1080px) { .foot-grid { grid-template-columns: repeat(3, 1fr); } .foot-brand { grid-column: 1 / -1; } }
@media (max-width: 600px) { .foot-grid { grid-template-columns: repeat(2, 1fr); } }
.foot h2 { font-size: var(--t-1); font-weight: 600; color: var(--ink); letter-spacing: .06em; text-transform: uppercase; margin: 0 0 12px; }
.foot ul { list-style: none; margin: 0; padding: 0; }
.foot li + li { margin-top: 8px; }
.foot a { color: var(--ink-2); text-decoration: none; }
.foot a:hover { color: var(--ink); }
.foot-brand .brand { margin-bottom: 12px; }
.foot-brand p { max-width: 30ch; }
/* "Open castomUI" is the footer's one call to action, so it takes the accent fill rather than the ink
   of the help buttons beside it. #0052B6 under white text is 5:1 (AA); hover deepens to the brand
   green. Contrast themes drop the tint for the plain high-contrast button. */
.foot .btn-accent { background: var(--accent); border-color: var(--accent); color: #FFFFFF; }
.foot .btn-accent:hover { background: var(--brand-1); border-color: var(--brand-1); color: #FFFFFF; }
[data-theme^="contrast"] .foot .btn-accent, [data-theme^="contrast"] .foot .btn-accent:hover { background: var(--btn-bg); border-color: var(--btn-bg); color: var(--btn-fg); }
/* "Report a bug", "Open castomUI", "Contact us" (§0.12): one centred row right under the link columns,
   above the ecosystem shelf, with the call to action in the middle (Maciej, 2026-09-24). `.foot a`
   would repaint the buttons' text in the footer's link grey, so their own colours are restated here. */
.foot-help { display: flex; flex-wrap: wrap; justify-content: center; gap: 10px; margin-block-start: clamp(32px, 5vw, 48px); }
.foot-help .btn { min-height: 44px; }
/* A phone cannot fit the three in one row, and wrapping would push the call to action off-centre, so
   they stack in the same order at one width. */
@media (max-width: 520px) { .foot-help { flex-direction: column; align-items: center; } .foot-help .btn { width: min(100%, 280px); justify-content: center; } }
.foot .foot-help .btn-primary { color: var(--btn-fg); }
.foot .foot-help .btn-primary:hover { color: var(--on-fill); }
.foot .foot-help .btn-ghost { color: var(--ink); }

/* The family shelf, ported from castomapi.com (after apple.com's product shelves): large rounded
   tiles in ONE row that scrolls sideways and snaps tile by tile, starting at the content column and
   running out to the right edge of the window. The heading is two-tone — the name in ink, the line
   after it in the muted ink. Every tile links straight to that product's own website. Without
   site.js it is a native scrolling row; site.js makes it circular, adds the arrow buttons
   (`.shelf-nav`) and the faint background mark (`.shelf-bg`). */
.foot-family { position: relative; margin-top: clamp(48px, 7vw, 80px); padding-top: clamp(32px, 5vw, 48px); border-top: var(--rule-w) solid var(--rule); }
/* Two shelves since 2026-09-21 (ecosystem, then tools). The second is a continuation, not a new
   chapter of the footer, so it drops the rule line and most of the gap. */
.foot-family + .foot-family { margin-top: clamp(8px, 2vw, 24px); padding-top: 0; border-top: 0; }
.foot-family h2 { font-size: var(--t3); font-weight: 600; line-height: 1.15; letter-spacing: -.025em; text-transform: none; color: var(--ink); margin: 0 0 clamp(16px, 2.5vw, 28px); text-wrap: balance; }
.foot-family h2 span { color: var(--ink-3); }
/* The icon sits above the heading, not inline in it, so it centers as its own line with a tight
   gap underneath — the wide inline gutter used elsewhere in text would read as a mistake here. */
.foot-family-centered { text-align: center; }
.foot-family-centered .foot-fam-ico { display: block; margin: 0 auto 6px; --ci-size: 40px; }
.foot-family-centered h2 { text-align: center; }
.foot-family-centered h2 span { display: block; margin-top: 6px; }
/* The row bleeds to both window edges: the negative margin pulls it out of the content column, and
   the same amount as padding puts the first tile back on the column. `scroll-padding` makes every
   snap land a tile on that same line. The vertical padding leaves room for the hover lift and shadow. */
.foot-family ul { display: flex; gap: 20px; list-style: none; margin: 0 calc(50% - 50vw); padding: 12px calc(50vw - 50%) 28px; overflow-x: auto; overscroll-behavior-x: contain; scroll-snap-type: x mandatory; scroll-padding-inline: calc(50vw - 50%); scrollbar-width: none; }
.foot-family ul::-webkit-scrollbar { display: none; }
.foot-family li { flex: 0 0 min(298px, 65.6vw); margin: 0; scroll-snap-align: start; }
.foot-family li + li { margin-top: 0; }
/* Each tile is one link. Grid rows place the markup (mark, name, line, domain) the way an Apple tile
   reads: the name as a small uppercase eyebrow, the line as the headline, the mark large below, the
   domain as a pill at the foot. The mark is aria-hidden, so the reading order is untouched. */
.foot-family a { position: relative; display: grid; grid-template-rows: auto auto 1fr auto; justify-items: start; height: 100%; min-height: clamp(304px, 40vh, 368px); padding: clamp(24px, 3vw, 34px); overflow: hidden; color: var(--ink-2); text-decoration: none; background: rgba(1, 1, 1, .001); border: var(--rule-w) solid rgba(1, 1, 1, .001); box-shadow: var(--shadow); border-radius: calc(var(--radius) * 1.5); transition: transform var(--dur) var(--ease), background-color var(--dur) var(--ease), border-color var(--dur) var(--ease); }
/* The product's own hue names it, but a raw brand hue (livee's yellow, iKonekt's cyan) drops to 2:1 on
   the pale shelf, so it is deepened toward black in light and lifted toward white in dark, as the ad
   tiles do; the contrast palettes use plain ink (§0.3). */
.foot-family b { grid-row: 1; justify-self: center; font-size: var(--t-1); font-weight: 700; letter-spacing: .06em; text-transform: uppercase; color: color-mix(in srgb, var(--hue, var(--accent-ink)) 55%, #000); }
[data-theme="dark"] .foot-family b { color: color-mix(in srgb, var(--hue, var(--accent-ink)) 50%, #fff); }
@media (prefers-color-scheme: dark) { :root:not([data-theme]) .foot-family b { color: color-mix(in srgb, var(--hue, var(--accent-ink)) 50%, #fff); } }
[data-theme^="contrast"] .foot-family b { color: var(--ink); }
.foot-family span { grid-row: 2; margin-top: 10px; font-size: var(--t2); font-weight: 600; line-height: 1.2; letter-spacing: -.02em; color: var(--ink); text-wrap: balance; }
.foot-family svg { grid-row: 3; align-self: center; justify-self: center; width: clamp(96px, 11vw, 100px); height: auto; aspect-ratio: 1; margin: 24px 0; }
.foot-family small { grid-row: 4; justify-self: center; display: inline-flex; align-items: center; gap: 8px; padding: 8px 16px; border: var(--rule-w) solid var(--rule); border-radius: var(--radius-pill); font-size: var(--t-1); font-weight: 500; line-height: 1.2; color: var(--ink-2); transition: background-color var(--dur) var(--ease), border-color var(--dur) var(--ease), color var(--dur) var(--ease); }
/* Hover and keyboard focus, never an underline: the tile lifts and its near-transparent wash deepens, an accent line
   draws across its top edge, and the domain pill fills while its arrow nudges forward. The accent is
   the BRAND'S colour — each tile carries `--hue` inline (data/marks.py HUES, from the kit) — with the
   site accent as the fallback. The focus ring is an outline, outside the box, so it is never clipped. */
.foot-family a:hover, .foot-family a:focus-visible { color: var(--ink-2); text-decoration: none; transform: translateY(-4px); background: rgba(1, 1, 1, .031); border-color: rgba(1, 1, 1, .031); }
.foot-family a::before { content: ""; position: absolute; inset-block-start: 0; inset-inline: 0; height: 3px; background: var(--hue, var(--accent)); transform: scaleX(0); transform-origin: 0 50%; transition: transform var(--dur) var(--ease); pointer-events: none; }
.foot-family small::after { content: ""; display: inline-block; flex: none; width: .9em; height: .9em; background: currentColor; -webkit-mask: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='black' stroke-width='2.2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M5 12h14M13 6l6 6-6 6'/%3E%3C/svg%3E") center / contain no-repeat; mask: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='black' stroke-width='2.2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M5 12h14M13 6l6 6-6 6'/%3E%3C/svg%3E") center / contain no-repeat; transition: transform var(--dur) var(--ease); }
.foot-family a:hover::before, .foot-family a:focus-visible::before { transform: scaleX(1); }
/* The pill fills with the brand colour and its text turns white and bold on every tile — one
   consistent hover, by decision 2026-09-23 (it used to flip to black on light hues, which read as
   some tiles not reacting). Plain #fff, not --paper, so the dark theme stays white too. */
.foot-family a:hover small, .foot-family a:focus-visible small { background: var(--hue, var(--accent-ink)); border-color: var(--hue, var(--accent-ink)); color: #fff; font-weight: 700; }
.foot-family a:hover small::after, .foot-family a:focus-visible small::after { transform: translateX(3px); }
/* The contrast palettes keep one high-contrast accent, so a brand colour gives way to it there and the
   background mark (a tint) is dropped. `!important` because --hue arrives as an inline style. */
[data-theme="contrast"] .foot-family a, [data-theme="contrast-dark"] .foot-family a { --hue: var(--accent-ink) !important; }
[data-theme="contrast"] .foot-family .shelf-bg, [data-theme="contrast-dark"] .foot-family .shelf-bg { display: none; }
@media (prefers-contrast: more) {
  :root:not([data-theme]) .foot-family a { --hue: var(--accent-ink) !important; }
  :root:not([data-theme]) .foot-family .shelf-bg { display: none; }
}
/* The tile's own mark again, large and faint, as its background, turned and pushed off the bottom
   corner. site.js adds it; every other child of the link is positioned, so they paint above it. */
.foot-family .shelf-bg { position: absolute; inset-block-end: -18%; inset-inline-end: -18%; width: 74%; height: auto; aspect-ratio: 1; margin: 0; opacity: .08; transform: rotate(-14deg); transition: transform .6s var(--ease), opacity var(--dur) var(--ease); pointer-events: none; }
.foot-family a > :not(.shelf-bg) { position: relative; }
.foot-family a:hover .shelf-bg, .foot-family a:focus-visible .shelf-bg { opacity: .14; transform: rotate(-6deg) scale(1.06); }
/* The two arrow buttons site.js adds, at the end of the shelf. The row is circular, so neither is
   ever disabled. The chevron is a mask, flipped for "previous". */
.shelf-nav { display: flex; justify-content: flex-end; gap: 12px; }
.shelf-nav button { display: grid; place-items: center; width: 44px; height: 44px; padding: 0; border: var(--rule-w) solid var(--rule); border-radius: var(--radius-pill); background: var(--field); color: var(--ink); cursor: pointer; transition: background-color var(--dur) var(--ease), color var(--dur) var(--ease); }
.shelf-nav button::before { content: ""; width: 18px; height: 18px; background: currentColor; -webkit-mask: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='black' stroke-width='2.4' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M9 5l7 7-7 7'/%3E%3C/svg%3E") center / contain no-repeat; mask: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='black' stroke-width='2.4' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M9 5l7 7-7 7'/%3E%3C/svg%3E") center / contain no-repeat; }
.shelf-nav .shelf-prev::before { transform: scaleX(-1); }
.shelf-nav button:hover { background: var(--ink); color: var(--paper); }
@media (max-width: 600px) {
  .foot-family ul { gap: 12px; }
  .foot-family a { min-height: 272px; }
}
.foot-legal { margin: 32px 0 0; padding-top: 20px; border-top: var(--rule-w) solid var(--rule); color: var(--ink-3); line-height: 1.7; }
.foot-line { display: flex; flex-wrap: wrap; gap: 8px 24px; justify-content: space-between; align-items: center; margin-top: 12px; color: var(--ink-3); }
.foot-line a { display: inline-block; padding: 8px 0; }
.totop { position: fixed; inset-inline-end: 20px; inset-block-end: 20px; width: 48px; height: 48px; border-radius: var(--radius-pill); background: var(--ink); color: var(--paper); display: grid; place-items: center; box-shadow: var(--shadow); opacity: 0; transform: translateY(12px); pointer-events: none; transition: opacity var(--dur), transform var(--dur) var(--ease); z-index: 40; }
.totop[data-show] { opacity: 1; transform: none; pointer-events: auto; }
.totop svg { width: 20px; height: 20px; stroke: currentColor; stroke-width: 2.2; stroke-linecap: round; stroke-linejoin: round; }
.totop:hover { background: var(--fill-hover); color: var(--on-fill); }

/* ------------------------------------------------------------------ components */
.chip { display: inline-flex; align-items: center; gap: 6px; padding: 4px 10px; border-radius: var(--radius-pill); font-size: var(--t-1); font-weight: 600; background: color-mix(in srgb, var(--accent) 9%, var(--paper)); color: var(--accent-ink); border: var(--rule-w) solid transparent; line-height: 1.3; white-space: nowrap; }
[data-theme="contrast"] .chip, [data-theme="contrast-dark"] .chip { border-color: var(--rule); }
.chips { display: flex; flex-wrap: wrap; gap: 8px; align-items: center; }
ul.chips, ol.chips { list-style: none; margin: 0; padding: 0; }
/* A chip that is a button — the hero's example briefs. It FILLS the prompt and never submits (WCAG
   3.2.2), so it looks like a light choice rather than like a call to action: paper, a rule and the
   secondary ink, answering the pointer with the accent edge. The words may wrap, because a Polish
   "Gabinet stomatologiczny, Kraków" is wider than a 320 px phone. 44 px under a finger (2.5.8). */
button.chip { min-block-size: 36px; padding: 6px 14px; background: var(--paper-2); color: var(--ink-2); border-color: var(--rule); font-weight: 500; white-space: normal; text-align: start; cursor: pointer; transition: color var(--dur), border-color var(--dur), background var(--dur); }
button.chip:hover { color: var(--ink); border-color: var(--accent-ink); }
button.chip:active { background: var(--field); }
@media (pointer: coarse) { button.chip { min-block-size: 44px; } }
/* The quiet chip: "Another example" — a different kind of choice from the named briefs, so it drops
   the fill and takes a dashed edge. */
.chip-plain { background: transparent; color: var(--ink-2); border-color: var(--rule); border-style: dashed; }
button.chip-plain:hover { border-style: solid; }

.card { background: var(--paper-2); border: var(--rule-w) solid var(--rule); border-radius: var(--radius); padding: clamp(18px, 2.5vw, 28px); position: relative; }
a.card { color: inherit; text-decoration: none; display: block; transition: transform var(--dur) var(--ease), border-color var(--dur); }
a.card:hover { transform: translateY(-2px); border-color: var(--accent); }
.card h3 { font-size: var(--t1); margin: 0 0 6px; }
.card p { color: var(--ink-2); margin: 0; font-size: var(--t-1); line-height: 1.5; }
.card p + p { margin-block-start: .7em; }
.card .nav-ico { margin-bottom: 14px; }
/* A .note closing a tile is the tile's verdict line — on the example pages, the product, its status
   and its price as the registry has them. A second box inside the box read as a warning, so inside a
   card it becomes a ruled footer in full ink instead. */
.card .note { margin: 14px 0 0; padding: 12px 0 0; border: 0; border-block-start: var(--rule-w) dashed var(--rule); border-radius: 0; background: none; color: var(--ink); font-weight: 600; }

/* A tile that ends in a call-to-action pill instead of leaving the heading itself as the link:
   the whole tile is the link, and the pill only has to look pressable and answer the pointer --
   not a second link, so the tile keeps one target and one tab stop.
   Written as `a.card-link`, not `.card-link`: `a.card` above already sets `display: block`, and a
   plain class would lose to it and leave this a block box with an inert `flex-direction`. */
a.card-link { display: flex; flex-direction: column; align-items: flex-start;
  transition: transform var(--dur) var(--ease), border-color var(--dur), box-shadow var(--dur), background var(--dur); }
a.card-link:hover, a.card-link:focus-visible { transform: translateY(-3px); border-color: var(--accent); box-shadow: var(--shadow); }
.card-link :is(h2, h3) { color: var(--ink); }
.card-link p { flex: 1 1 auto; }

/* Pushed to the bottom of the tile by the paragraph above it, so a row of tiles of different
   heights still lines its calls to action up along one edge. */
.card-cta { display: inline-flex; align-items: center; gap: 8px; margin-block-start: 18px;
  padding: 9px 16px; border-radius: var(--radius-pill); border: var(--rule-w) solid var(--rule);
  background: var(--paper); color: var(--ink); font-size: var(--t-1); font-weight: 600; line-height: 1;
  transition: background var(--dur) var(--ease), color var(--dur) var(--ease), border-color var(--dur) var(--ease); }
/* The arrow is masked rather than typed, so it takes the pill's colour as it changes and cannot be
   read aloud or copied as a stray character. */
.card-cta::after { content: ""; width: 15px; height: 15px; flex: none; background: currentColor;
  -webkit-mask: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='black' stroke-width='2.4' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M5 12h14M13 6l6 6-6 6'/%3E%3C/svg%3E") center / contain no-repeat;
  mask: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='black' stroke-width='2.4' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M5 12h14M13 6l6 6-6 6'/%3E%3C/svg%3E") center / contain no-repeat;
  transition: transform var(--dur) var(--ease); }
.card-link:is(:hover, :focus-visible) .card-cta { background: var(--fill-hover); border-color: var(--fill-hover); color: var(--on-fill); }
.card-link:is(:hover, :focus-visible) .card-cta::after { transform: translateX(3px); }
/* A contrast theme has no brand palette; its own ink is the strongest thing it owns. */
[data-theme^="contrast"] .card-link:is(:hover, :focus-visible) .card-cta { background: var(--ink); border-color: var(--ink); color: var(--paper); }
@media (forced-colors: active) { .card-cta::after { forced-color-adjust: none; background: CanvasText; } }

.note { padding: 14px 18px; border: var(--rule-w) solid var(--rule); border-inline-start: 4px solid var(--accent); border-radius: var(--radius-sm); background: var(--paper-2); font-size: var(--t-1); color: var(--ink-2); margin: 20px 0; }
.note b { color: var(--ink); }

/* A row of buttons written as a plain paragraph of links — which is how every page body writes its
   calls to action. Inline, two buttons that wrap touch each other; as a flex row they keep a gap in
   both directions. Only a paragraph holding nothing but buttons is changed. */
p:has(> .btn):not(:has(> :not(.btn, br))) { display: flex; flex-wrap: wrap; align-items: center; gap: 12px; }

/* ---- FAQ ---------------------------------------------------------------------------------------
   Native <details>, so it works with scripting off and the builder can lift the same markup into
   FAQPage JSON-LD. The marker is a masked plus that turns into a cross when open: no glyph from a
   font subset, nothing for a screen reader to read, and it takes the colour of its state. */
/* One column left-aligned left half the section empty on a wide screen, so from 900 px the answers
   sit in two columns across the full width. Each question carries both rules, so the two columns
   stay ruled evenly whichever one opens; `align-items: start` keeps an opened answer from
   stretching its neighbour. */
.faq { display: grid; grid-template-columns: minmax(0, 1fr); column-gap: clamp(32px, 5vw, 64px); align-items: start; }
.faq details { border-block: var(--rule-w) solid var(--rule); margin-block-end: calc(-1 * var(--rule-w)); }
@media (min-width: 900px) { .faq { grid-template-columns: repeat(2, minmax(0, 1fr)); } }
/* An odd count in two columns leaves a hole under the last question (§0.13): it takes the whole row. */
@media (min-width: 900px) { .faq > details:last-child:nth-child(odd) { grid-column: 1 / -1; } }
.faq summary { list-style: none; cursor: pointer; display: flex; align-items: center; justify-content: space-between; gap: 20px;
  min-block-size: 56px; padding: 14px 0; font-weight: 600; font-size: var(--t0); line-height: 1.35; color: var(--ink); transition: color var(--dur); }
.faq summary::-webkit-details-marker { display: none; }
.faq summary:hover { color: var(--accent-ink); }
.faq summary::after { content: ""; flex: none; inline-size: 18px; block-size: 18px; background: var(--ink-3);
  -webkit-mask: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='black' stroke-width='2.2' stroke-linecap='round'%3E%3Cpath d='M12 4v16M4 12h16'/%3E%3C/svg%3E") center / contain no-repeat;
  mask: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='black' stroke-width='2.2' stroke-linecap='round'%3E%3Cpath d='M12 4v16M4 12h16'/%3E%3C/svg%3E") center / contain no-repeat;
  transition: transform var(--dur) var(--ease), background var(--dur); }
.faq details[open] > summary::after { transform: rotate(45deg); background: var(--accent-ink); }
.faq summary:focus-visible { outline-offset: 4px; }
.faq details > :not(summary) { margin: 0; padding: 0 0 18px; color: var(--ink-2); max-width: 68ch; }
.faq details > :not(summary) + :not(summary) { margin-block-start: -6px; }
@media (forced-colors: active) { .faq summary::after { forced-color-adjust: none; background: CanvasText; } }

/* ------------------------------------------------------------------ page sections */
.hero { padding: clamp(48px, 9vw, 120px) 0 clamp(40px, 6vw, 80px); position: relative; overflow-x: clip; }
/* DESIGN §3: the hero h1 is set smaller and tighter than a marketing headline would be, because it
   has to leave room beside it for the prompt it labels. The h1 text is never animated: on a phone it
   is the Largest Contentful Paint. */
.hero h1 { font-size: clamp(2rem, 1.1rem + 2.9vw, 3.4rem); font-weight: 640; letter-spacing: -.03em; line-height: 1.04; margin: 0 0 20px; max-width: 18ch; }
.hero .lead { font-size: var(--t1); margin-bottom: 26px; max-width: 52ch; }
.hero-ctas { display: flex; flex-wrap: wrap; gap: 12px; align-items: center; margin-bottom: 20px; }
/* The one-line microcopy under the prompt (cost, account, data, PLAN §2.1) and the lane note. In the
   hero a .note is a plain line, not a boxed notice: it is read after the button, not before it. */
.hero .note { margin: 12px 0 0; padding: 0; border: 0; border-radius: 0; background: none; color: var(--ink-3); max-width: 60ch; }
.hero .note a { color: var(--ink-2); }
/* The lane note arrives late, from GET /v1/lanes on the first focus, so its line is reserved from the
   first paint: text dropping in under the button must not push anything down (CLS 0). */
.lane-note { min-block-size: 1.5em; min-block-size: 1lh; margin: 6px 0 0; font-size: var(--t-1); color: var(--ink-2); }
.hero .chips { margin: 4px 0 6px; }
/* The ask above the prompt. The h1 carries the promise, so this line carries the mechanism and has to
   read as an instruction to the reader, not as a second headline: same weight as the lead, a rule over
   it to separate promise from demo. */
.hero-ask-h {
  margin: 0 0 12px; max-width: 46ch; font-size: var(--t0); font-weight: 560; color: var(--ink);
  border-block-start: 2px solid var(--ink); padding-block-start: 14px;
}
.hero-ask-h #write-ask { color: var(--accent-ink); }

/* The prompt as the scaffold's one-line pill: an input and a button on one row. */
.hero-ask {
  position: relative; display: flex; gap: 8px; align-items: center; margin: 4px 0 18px; max-width: 480px;
  padding: 6px 6px 6px 18px; background: var(--paper-2); border: var(--rule-w) solid var(--rule);
  border-radius: var(--radius-pill); box-shadow: var(--shadow);
  transition: border-color var(--dur), box-shadow var(--dur);
}
.hero-ask:focus-within { border-color: var(--accent); box-shadow: 0 0 0 4px var(--accent-soft), var(--shadow); }
.hero-ask input { flex: 1; min-width: 0; border: 0; background: transparent; color: var(--ink); font: inherit; font-size: var(--t-1); padding: 10px 0; outline: none; }
.hero-ask input::placeholder, .hero-ask textarea::placeholder { color: var(--ink-3); opacity: 1; }
.hero-ask .btn { flex: none; }
@media (max-width: 520px) {
  .hero-ask { flex-wrap: wrap; max-width: none; border-radius: var(--radius); padding: 10px; }
  .hero-ask .btn { width: 100%; }
}

/* ---- the brief: DESIGN §8.2 `.hero-ask.hero-ask--brief` -----------------------------------------
   A multi-sentence textarea (3 rows, growing to 8), an optional town field and the button, in one
   paper box. The box wears the focus ring for the whole form — `border-color: var(--accent)` and the
   4 px accent-soft halo — and each field inside still shows its own outline, because a ring on the
   container alone does not say which field has the caret (2.4.7).

   Grid, so the parts need no wrappers: the textarea takes the full first row, the town field (or
   its label wrapper) and the button share the second, and any paragraph — the "too short" message,
   a staging note, a row of buttons — takes a full row of its own. The screen-reader label is
   absolutely positioned and so takes no cell. The one-line pill above is the fallback when a page
   still carries it; a form with a textarea gets this whether or not it names the modifier. */
.hero-ask--brief, .hero-ask:has(textarea) {
  display: grid; grid-template-columns: minmax(0, 1fr) auto; align-items: end; gap: 8px 10px;
  max-width: 40rem; margin: 6px 0 14px; padding: 8px; border-radius: var(--radius);
}
.hero-ask--brief > *, .hero-ask:has(textarea) > * { grid-column: 1; min-width: 0; }
.hero-ask--brief > :is(textarea, p, ul, div:not(.hero-town)), .hero-ask:has(textarea) > :is(textarea, p, ul) { grid-column: 1 / -1; }
.hero-ask--brief > :is(.btn, button[type="submit"]), .hero-ask:has(textarea) > .btn { grid-column: -2 / -1; justify-self: end; }
.hero-ask textarea {
  display: block; inline-size: 100%; margin: 0; padding: 12px 12px 8px; border: 0; border-radius: var(--radius-sm);
  background: transparent; color: var(--ink); font: inherit; font-size: var(--t0); line-height: 1.5;
  min-block-size: 7.4em; resize: vertical;
}
/* Grows with what is typed, between three and eight lines, where the browser can size a field to its
   content; elsewhere rows="3" and the manual resize handle do the same job. */
@supports (field-sizing: content) {
  .hero-ask textarea { field-sizing: content; min-block-size: calc(3lh + 20px); max-block-size: calc(8lh + 20px); resize: none; }
}
.hero-ask--brief :is(textarea, input):focus-visible, .hero-ask:has(textarea) textarea:focus-visible { outline: 2px solid var(--accent-ink); outline-offset: -2px; border-radius: var(--radius-sm); }
.hero-ask textarea:disabled { background: var(--field); color: var(--ink-3); cursor: not-allowed; }
.hero-ask textarea[aria-invalid="true"] { box-shadow: inset 3px 0 0 var(--bad); }
/* The town: a visible label (1.3.1, 3.3.2) over an ordinary field, on the prompt's second row. */
.hero-ask--brief input:not([type="hidden"], [type="checkbox"], [type="radio"]) {
  flex: none; inline-size: 100%; min-block-size: 44px; padding: 10px 12px; font-size: var(--t-1);
  background: var(--field); border: var(--rule-w) solid transparent; border-radius: var(--radius-sm);
}
/* Written as a paragraph — label, field, hint — it shares the second row with the button: label and
   field on one line where there is room, the hint under them. */
.hero-ask.hero-ask--brief > p:has(input) { grid-column: 1; display: flex; flex-wrap: wrap; align-items: center; gap: 4px 10px; margin: 0; padding: 2px 4px 2px 8px; font-size: var(--t-1); color: var(--ink-2); }
.hero-ask--brief > p:has(input) label { font-weight: 600; color: var(--ink); }
.hero-ask--brief > p:has(input) label small { font-weight: 400; color: var(--ink-3); font-size: 1em; }
.hero-ask--brief > p:has(input) input { flex: 1 1 10rem; inline-size: auto; min-inline-size: 0; }
.hero-ask--brief > p:has(input) > small { flex-basis: 100%; color: var(--ink-3); font-size: 1em; }
.hero-town { display: grid; gap: 4px; font-size: var(--t-1); font-weight: 600; color: var(--ink-2); }
.hero-town small, .hero-town i { font-weight: 400; font-style: normal; color: var(--ink-3); }
.hero-ask .ask-note { grid-column: 1 / -1; }
/* Why is this form not writing? The staging copy says so in the box itself, above its fallbacks. */
.ask-note { margin: 4px 4px 0; padding: 0; padding-inline-start: 12px; font-size: var(--t-1); color: var(--ink-2); border-inline-start: 3px solid var(--accent); }
.ask-note b { color: var(--ink); }
.hero-ask > .hero-ctas { margin: 4px 4px 4px; }
@media (max-width: 719.98px) {
  .hero-ask--brief, .hero-ask:has(textarea) { grid-template-columns: minmax(0, 1fr); max-width: none; }
  .hero-ask--brief > *, .hero-ask:has(textarea) > * { grid-column: 1 / -1; }
  .hero-ask--brief > :is(.btn, button[type="submit"]), .hero-ask:has(textarea) > .btn { justify-self: stretch; inline-size: 100%; }
}

.hero-grid { display: grid; grid-template-columns: minmax(0, 1.15fr) minmax(0, 1fr); gap: clamp(24px, 5vw, 64px); align-items: center; }
@media (max-width: 960px) { .hero-grid { grid-template-columns: 1fr; } .hero h1 { max-width: none; } }
/* The home hero's photograph when it sits in an ordinary .hero-grid. .shot is built as a panel
   backdrop — position absolute, inset 0, with its own darkening gradient — so dropped into the grid
   it would cover the column beside it. Inside the grid it becomes an ordinary framed block. */
.hero-grid > .shot { position: static; inset: auto; margin: 0; border-radius: var(--radius); overflow: hidden; aspect-ratio: 4 / 5; }
.hero-grid > .shot::after { display: none; }
.shot > picture { display: contents; }
@media (max-width: 960px) { .hero-grid > .shot { aspect-ratio: 16 / 9; order: 2; } }
/* On a phone the thing worth landing on is the prompt (Maciej's rule; PLAN §2.2): the lead and the
   second row of buttons step aside, and the photograph — which the builder already swaps for a
   1-pixel image below 720 px, so it is never downloaded — is not given an empty box either. */
@media (max-width: 719.98px) { .hero .shot[data-hero], .hero .hero-peek { display: none; } }
@media (max-width: 640px) {
  .hero { padding-block: 20px 28px; }
  .hero .lead { display: none; }
  .hero-grid .hero-ctas { display: none; }
  .hero-ask .hero-ctas { display: flex; }
}

/* ---- the welcome hero: `.hero.hero-intro` ---------------------------------------------------------
   The opening castomo.com has carried for years, kept on purpose so a returning visitor recognises
   the house: kicker, the promise, one line, and the photograph in its big-cornered frame with the
   one square corner at the bottom left. It asks for nothing; the prompter is the next section, which
   is the order every family site uses (hero, then the input). Unlike the prompter, its lead and its
   buttons stay on a phone, because there they ARE the hero. */
.hero-intro { padding-block: clamp(28px, 5vw, 64px) clamp(24px, 4vw, 48px); }
.hero-intro > .wrap { --wrap: var(--wrap-hero); }
.hero-intro h1 { font-size: clamp(2.6rem, 1.2rem + 5vw, 5.6rem); line-height: .96; max-width: 11ch; margin-block-end: 24px; }
.hero-intro .lead { font-size: clamp(1.25rem, 1rem + 1.1vw, 2rem); line-height: 1.25; letter-spacing: -.015em; color: var(--ink); max-width: 24ch; }
.hero-intro .lead .nowrap { white-space: nowrap; }
.hero-intro .hero-ctas { margin-block: 8px 0; }
.hero-intro .hero-grid > .shot { aspect-ratio: auto; block-size: min(calc(100svh - var(--bar-h) - 2 * var(--s5)), 760px); min-block-size: 420px;
  border-radius: clamp(56px, 9vw, 160px); border-end-start-radius: var(--radius); }
@media (max-width: 960px) { .hero-intro .hero-grid > .shot { block-size: auto; min-block-size: 0; aspect-ratio: 16 / 9; border-radius: clamp(40px, 9vw, 80px); border-end-start-radius: var(--radius); } }
@media (max-width: 640px) {
  .hero-intro .lead { display: block; }
  .hero-intro.hero .hero-grid .hero-ctas { display: flex; }
  .hero-intro .hero-ctas .btn { flex: 1 1 auto; }
}
/* ---- the three doors under the welcome hero: `nav.doors` --------------------------------------------
   The ecosystem is large; the doors make the first choice small. Three equal links, the middle one
   (the job card, the page's own next section) marked as the usual way in. Each whole tile is one link,
   so the target is as large as the tile (WCAG 2.5.8) and a screen reader meets three links, not nine. */
.doors { padding-block: 0 clamp(32px, 5vw, 64px); }
.doors > .wrap { --wrap: var(--wrap-hero); }
.doors-h { font-size: var(--t-1); font-weight: bold; text-transform: uppercase; letter-spacing: 2px; color: var(--ink-3); margin: 0 0 16px; }
.doors-list { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: 16px; list-style: none; margin: 0; padding: 0; }
.doors-list li, .doors-list li + li { margin: 0; }
.door { display: grid; grid-template-rows: auto auto auto 1fr auto; gap: 6px; height: 100%; padding: clamp(20px, 2.4vw, 30px);
  background: var(--paper-2); border: var(--rule-w) solid var(--rule); border-radius: var(--radius); color: var(--ink); text-decoration: none;
  transition: transform var(--t-lift, .2s) var(--ease, ease), box-shadow var(--t-lift, .2s) var(--ease, ease), border-color var(--t-lift, .2s) var(--ease, ease); }
.door:hover, .door:focus-visible { transform: translateY(-3px); box-shadow: var(--shadow); border-color: var(--accent); text-decoration: none; color: var(--ink); }
.door-main { border-color: var(--ink); box-shadow: inset 0 3px 0 var(--accent); }
.door-ico { --ci-size: 40px; width: 40px; height: 40px; color: var(--accent); margin-block-end: 10px; }
.door-k { font-size: var(--t-1); font-weight: bold; text-transform: uppercase; letter-spacing: 1.5px; color: var(--accent-ink); }
.door b { font-size: var(--t2); font-weight: 640; letter-spacing: -.02em; line-height: 1.15; text-wrap: balance; }
.door-p { color: var(--ink-2); font-size: var(--t0); line-height: 1.5; }
.door-go { margin-block-start: 14px; font-weight: 600; font-size: var(--t-1); }
.door-go::after { content: " →"; }
@media (max-width: 860px) { .doors-list { grid-template-columns: minmax(0, 1fr); } .door { grid-template-rows: none; } }
@media print { .doors { display: none; } }

/* The prompter's heading is an h2 now that the promise has its own section above it. */
.hero-job h2 { font-size: clamp(1.8rem, 1.1rem + 2.2vw, 2.8rem); font-weight: 640; letter-spacing: -.03em; line-height: 1.06; margin: 0 0 16px; max-width: 20ch; }

/* ---- the hero as the job stage: `.hero.hero-job` (DESIGN §4, §8.2; PLAN §2.2) -------------------
   Markup: `.wrap.hero-grid` > ONE copy wrapper (h1, lead, prompt, chips, microcopy) + `.hero-peek`,
   the stage, which holds `figure.shot[data-hero]` and the empty `article.jobcard` (card.css draws the
   card; this file only places it). `.hero-stage` is accepted as the stage's name as well. Inside the
   stage the photograph and the card share one grid cell, so the card lies over the photograph with
   no absolute positioning and the stage is always exactly as tall as the taller of the two.

   Desktop, from 1081 px: a 12-column grid in the wider --wrap-hero. The copy takes columns 1–6; the
   stage takes 7–12 and runs out to the window's right edge. The card sits over the photograph's left
   edge, level with the prompt, overhanging the gutter by 40 px, at −2°. The photograph's height
   follows the window rather than a 4:5 frame — at 1366 × 768 a fixed ratio put the card below the
   fold (critique-taste #5) — and the card scales to 0.8 on short screens, so its foot and the
   button stay above the fold at 1280 × 720, 1366 × 768 and 1440 × 900.

   Tablet, 720–1080 px: one column; the photograph is a 16:9 band under the prompt with the card over
   its top-right corner. Phone: the prompt only — the rules just above hide the whole stage. */
.hero-job { padding-block: clamp(24px, 4vw, 48px) clamp(40px, 6vw, 72px); isolation: isolate; }
.hero-job > .wrap { --wrap: var(--wrap-hero); }
.hero-job h1 { max-width: 20ch; }
.hero-stage, .hero-peek { position: relative; display: grid; min-width: 0; }
.hero-stage > *, .hero-peek > * { grid-area: 1 / 1; min-width: 0; }
.hero-job :is(.hero-stage, .hero-peek) > .jobcard { position: relative; z-index: 2; justify-self: start; align-self: start;
  inline-size: 360px; max-inline-size: 100%; margin: 0; --peek-tilt: -2deg; rotate: var(--peek-tilt); transform-origin: 0 0; }
.hero-job .shot::after { display: none; }
@media (min-width: 1081px) {
  .hero-job .hero-grid { grid-template-columns: repeat(12, minmax(0, 1fr)); column-gap: var(--s4); row-gap: 0; align-items: start; }
  /* If a page writes several copy blocks instead of one wrapper, the first shares the photograph's row
     and the rest flow below it, which is plain rather than overlapping. */
  .hero-job .hero-grid > * { grid-column: 1 / span 6; }
  .hero-job .hero-grid > :first-child { grid-row: 1; align-self: start; padding-block-start: var(--s4); }
  .hero-job .hero-grid > :is(.shot, .hero-peek, .hero-stage) { grid-column: 7 / -1; grid-row: 1; align-self: start; }
  /* To the window's edge: the negative end margin is the space between the wrap and the window. 100vw
     counts the scrollbar gutter as well, which the hero's overflow-x: clip absorbs. */
  .hero-job .hero-grid > :is(.shot, .hero-peek, .hero-stage) { margin-inline-end: calc((100vw - min(var(--wrap-hero), 100vw - clamp(32px, 8vw, 64px))) / -2); }
  .hero-job .shot { position: relative; inset: auto; margin: 0; overflow: hidden; aspect-ratio: auto;
    block-size: min(calc(100svh - var(--bar-h) - 2 * var(--s5)), 820px); min-block-size: 480px;
    border-radius: 0; border-start-start-radius: 20px; border-end-start-radius: 20px; }
  .hero-job :is(.hero-stage, .hero-peek) > .jobcard { margin-block-start: var(--s5); margin-inline-start: -40px; }
}
/* The empty card is a full sheet — head, four blank job rows, the twelve weeks and the signature —
   and at 360 px it stands about 790 px tall. On a laptop screen that is taller than the photograph
   it lies on, so it is scaled down until its foot and the button below it stay above the fold at
   1280 × 720, 1366 × 768 and 1440 × 900. Scale, not height: nothing reflows and the sheet keeps its
   proportions. */
@media (min-width: 1081px) and (max-height: 1000px) { .hero-job :is(.hero-stage, .hero-peek) > .jobcard { scale: .85; } }
@media (min-width: 1081px) and (max-height: 820px) { .hero-job :is(.hero-stage, .hero-peek) > .jobcard { scale: .8; } }
@media (min-width: 720px) and (max-width: 1080px) {
  .hero-job .hero-grid { grid-template-columns: minmax(0, 1fr); gap: var(--s5); }
  .hero-job .hero-grid > :is(.shot, .hero-peek, .hero-stage) { grid-column: 1; grid-row: 2; }
  .hero-job .shot { position: relative; inset: auto; order: 0; margin: 0; aspect-ratio: 16 / 9; border-radius: var(--radius); overflow: hidden; }
  .hero-job :is(.hero-stage, .hero-peek) > .jobcard { justify-self: end; inline-size: 280px; margin: -40px 24px 0 0; transform-origin: 100% 0; }
}
/* The one load gesture (DESIGN §5.2): the empty card drops 12 px and settles from −3° to −2°, once,
   in under a second. Transforms only, so it never touches layout or the h1's paint; under either
   reduced-motion switch the global rule removes it and the card simply rests at −2°. */
@media (prefers-reduced-motion: no-preference) {
  .hero-job :is(.hero-stage, .hero-peek) > .jobcard[data-state="empty"] { animation: peek-settle var(--t-lift) var(--ease-paper) both; }
}
@keyframes peek-settle { from { translate: 0 -12px; rotate: calc(var(--peek-tilt) - 1deg); } to { translate: 0 0; rotate: var(--peek-tilt); } }
/* Once the visitor presses, the card IS the page (docs/design/jobcard-markup.md): the renderer marks
   the section data-card="writing|done|…", and the hero turns into one column — the prompt above, the
   full-size card below at up to 760 px, straight, with the photograph gone. Without this the live
   card stayed in the 360 px peek slot over the photograph, a sheet too narrow to read, and on phones
   (where the peek is hidden before the press) it would not have been drawn at all. */
.hero-job[data-card]:not([data-card="empty"]) .hero-grid { grid-template-columns: minmax(0, 1fr); row-gap: var(--s5); }
.hero-job[data-card]:not([data-card="empty"]) .hero-grid > * { grid-column: 1 / -1; grid-row: auto; }
.hero-job[data-card]:not([data-card="empty"]) .hero-grid > .hero-peek { display: grid; margin: 0; justify-self: center; inline-size: min(760px, 100%); }
.hero-job[data-card]:not([data-card="empty"]) .hero-peek > .shot { display: none; }
.hero-job[data-card]:not([data-card="empty"]) .hero-peek > .jobcard { inline-size: 100%; margin: 0; rotate: 0deg; scale: 1; translate: none; justify-self: stretch; animation: none; }
/* The room wash (DESIGN §2.5): once the visitor's palette arrives, the card's renderer sets --room on
   the hero and marks it data-room, and a 6 % (dark: 10 %) wash of that colour lies behind the stage —
   never behind the text column. Off in the contrast themes, in forced colours and in print. */
.hero-job::before { content: ""; position: absolute; z-index: -1; inset: 0 0 0 40%; pointer-events: none; opacity: 0;
  background: radial-gradient(closest-side, color-mix(in srgb, var(--room, transparent) 6%, transparent), transparent);
  transition: opacity var(--t-paint) var(--ease); }
.hero-job[data-room]::before { opacity: 1; }
[data-theme="dark"] .hero-job::before { background: radial-gradient(closest-side, color-mix(in srgb, var(--room, transparent) 10%, transparent), transparent); }
@media (prefers-color-scheme: dark) { :root:not([data-theme]) .hero-job::before { background: radial-gradient(closest-side, color-mix(in srgb, var(--room, transparent) 10%, transparent), transparent); } }
[data-theme^="contrast"] .hero-job::before { display: none; }
@media (prefers-contrast: more), (forced-colors: active) { .hero-job::before { display: none; } }
@media (max-width: 1080px) { .hero-job::before { inset: 40% 0 0 0; } }

/* The tryout's stage wrapper keeps its own stacking context; the blurred, drifting blobs that once
   sat behind it are gone (DESIGN §8.2) — the room wash above replaced them. */
.section.try { position: relative; isolation: isolate; }
.section.try > .wrap { position: relative; z-index: 1; }

/* ---- the answer, shown: `.anatomy` (PLAN §3 S2, DESIGN §8.3) ------------------------------------
   Specimen tabs over a finished example card, and beside it the numbered list of what each part of
   the card is. Wide screens: the card on the left at about three fifths, the list on the right,
   sticky, so it stays beside whichever part of a long card is in view. Narrow: card, then list.

   Each list item may name the card region it explains with data-part (jobs, tool, price, weeks,
   count, ai, keep); hovering it, or focusing a link inside it, outlines that region on the card —
   CSS :has(), no script, and no tab stops that do nothing (critique-taste #26). The outline takes the
   card's own stamp colour, because the card's paper stays light in the dark site theme. */
/* `.anatomy` names the section's wrapper; on the list itself (`ol.reasons.anatomy`) it is only a
   marker, and the list keeps its own layout. */
.anatomy:not(ol, ul) { display: grid; grid-template-columns: minmax(0, 1fr); gap: clamp(24px, 4vw, 44px); align-items: start; }
.anatomy > [role="tablist"] { display: flex; flex-wrap: wrap; gap: 4px; padding: 5px; inline-size: fit-content; max-inline-size: 100%;
  border-radius: 24px; background: var(--field); border: var(--rule-w) solid var(--rule); }
.anatomy [role="tab"] { appearance: none; border: var(--rule-w) solid transparent; background: transparent; min-block-size: 44px; padding: 8px 18px;
  border-radius: var(--radius-pill); font: inherit; font-size: var(--t-1); font-weight: 600; color: var(--ink-2); cursor: pointer;
  transition: background var(--dur), color var(--dur), box-shadow var(--dur); }
.anatomy [role="tab"]:hover { color: var(--ink); }
.anatomy [role="tab"][aria-selected="true"] { background: var(--paper-2); color: var(--ink); box-shadow: var(--shadow); }
[data-theme^="contrast"] .anatomy [role="tab"][aria-selected="true"] { border-color: var(--ink); }
.anatomy [role="tabpanel"] { min-inline-size: 0; }
.anatomy [role="tabpanel"]:focus-visible { outline-offset: 8px; }
@media (min-width: 1000px) {
  .anatomy:not(ol, ul) { grid-template-columns: minmax(0, 1.55fr) minmax(0, 1fr); }
  .anatomy > [role="tablist"] { grid-column: 1 / -1; }
  .anatomy > .reasons { grid-column: 2; grid-row: 2 / span 3; position: sticky; inset-block-start: calc(var(--bar-h) + 24px); }
}
.anatomy:has(.reasons [data-part="jobs"]:is(:hover, :focus-within)) .jc-jobs,
.anatomy:has(.reasons [data-part="tool"]:is(:hover, :focus-within)) .jc-job-tool,
.anatomy:has(.reasons [data-part="price"]:is(:hover, :focus-within)) :is(.jc-price, .jc-job .status),
.anatomy:has(.reasons [data-part="weeks"]:is(:hover, :focus-within)) .jc-weeks,
.anatomy:has(.reasons [data-part="count"]:is(:hover, :focus-within)) .jc-count,
.anatomy:has(.reasons [data-part="ai"]:is(:hover, :focus-within)) :is(.jc-ai, .jc-sign),
.anatomy:has(.reasons [data-part="keep"]:is(:hover, :focus-within)) :is(.jc-stub, .jc-terms) {
  outline: 2px dashed var(--stamp, var(--accent-ink)); outline-offset: 5px; border-radius: 3px; }

/* The frame the example card sits in (assets/anatomy.js). A finished card runs to three or four
   screens, so it scrolls inside a frame one screen tall instead of doubling the section it only
   illustrates; pointing at a line of the list turns the card to that part. The frame is a real
   scroll region with a tab stop, so a keyboard can read the whole card too. Its desk colour is the
   field token in every theme, while the card keeps its own paper. */
.anatomy-h { margin-block: clamp(36px, 5vw, 56px) clamp(16px, 2vw, 22px); }
.anatomy > [role="tabpanel"] { grid-column: 1; }
.anatomy-stage { position: relative; max-block-size: min(76vh, 820px); min-block-size: 220px; overflow: auto; overscroll-behavior: contain;
  padding: clamp(14px, 2.4vw, 28px); border-radius: var(--radius); border: var(--rule-w) solid var(--rule); background: var(--field);
  scrollbar-color: var(--rule) transparent; scrollbar-width: thin; }
.anatomy-stage:focus-visible { outline: 2px solid var(--accent-ink); outline-offset: 3px; }
.anatomy-stage[data-state="loading"] { cursor: progress; }
.anatomy-stage .jobcard { margin-inline: auto; }
/* On a live card the reason under a job is held to three lines so the jobs stay scannable. Here the
   card is being read, not acted on, and the frame scrolls — so the reasons print whole. */
.anatomy-stage .jc-job-why { display: block; -webkit-line-clamp: none; line-clamp: none; overflow: visible; }
.anatomy-wait { margin: 0; padding: clamp(28px, 6vw, 64px) 12px; text-align: center; color: var(--ink-2); font-size: var(--t-1); }
.anatomy-hint { margin: 0; font-size: var(--t-1); color: var(--ink-3); }
.anatomy-foot { display: flex; flex-wrap: wrap; align-items: center; justify-content: space-between; gap: 16px 32px; margin-block-start: clamp(24px, 3vw, 36px); }
.anatomy-foot > p { margin: 0; }
@media (min-width: 1000px) {
  .anatomy > [role="tabpanel"] { grid-row: 2 / span 3; }
  .anatomy > .reasons { grid-row: 2; position: static; }
  .anatomy > .anatomy-hint { grid-column: 2; grid-row: 3; padding-inline-start: 12px; }
}
@media (max-width: 999px) {
  .anatomy-stage { max-block-size: 68vh; }
  .anatomy-hint { display: none; }
}
/* Three specimen names do not fit a phone's width. Stacked they read as a menu, so the row stays a
   row and scrolls sideways inside its own pill. */
@media (max-width: 640px) {
  .anatomy > [role="tablist"] { flex-wrap: nowrap; overflow-x: auto; inline-size: auto; scrollbar-width: none; }
  .anatomy [role="tab"] { flex: none; white-space: nowrap; padding-inline: 14px; }
  .anatomy-stage { padding: 10px; }
}
@media print { .anatomy-stage { max-block-size: none; overflow: visible; } }

/* The numbered list: `ol.reasons`. The number is a counter in a ringed disc — structural, never a
   typed digit — or, where the markup carries one, an explicit .reason-n in the same place. */
.reasons { counter-reset: reason; list-style: none; margin: 0; padding: 0; display: grid; gap: 2px; }
.reasons > li { display: grid; grid-template-columns: 2.25rem minmax(0, 1fr); column-gap: 14px; align-items: start; padding: 12px; font-size: var(--t-1); color: var(--ink-2); line-height: 1.5; border-radius: var(--radius-sm); transition: background var(--dur); }
.reasons > li::before, .reason-n { counter-increment: reason; content: counter(reason); grid-column: 1; grid-row: 1 / span 3;
  inline-size: 2.25rem; block-size: 2.25rem; border-radius: 50%; border: 1.5px solid var(--accent-ink); color: var(--accent-ink);
  display: grid; place-items: center; font-weight: 700; font-size: var(--t-1); line-height: 1; font-variant-numeric: tabular-nums; }
.reasons > li:has(> .reason-n)::before { content: none; display: none; }
.reasons > li > * { grid-column: 2; margin: 0; }
.reasons > li > :is(h3, h4, b, strong):first-child, .reasons > li > .reason-n + :is(h3, h4, b, strong) { display: block; font-size: var(--t0); font-weight: 600; color: var(--ink); line-height: 1.35; margin-block: 5px 2px; }
.reasons > li p { font-size: var(--t-1); color: var(--ink-2); line-height: 1.5; }
.reasons > li:is(:hover, :focus-within) { background: var(--field); }
/* The callouts come in one by one as the list scrolls into view — scroll-driven, so it costs no script
   and nothing above the fold ever waits for it; only where the browser supports it and the reader
   has not asked for less motion. Everywhere else the list is simply there. */
@supports (animation-timeline: view()) {
  @media (prefers-reduced-motion: no-preference) {
    :is(.anatomy .reasons, .reasons.anatomy) > li { animation: callout-in linear both; animation-timeline: view(); animation-range: entry 0% entry 70%; }
  }
}
@keyframes callout-in { from { opacity: 0; translate: 0 14px; } to { opacity: 1; translate: 0 0; } }

/* ---- steps: `.steps` > `.step` (+ `.step-ico`, a 132 px watermark) ------------------------------
   `.steps` lays its own steps out, so a page need not add grid classes; a .grid holding .step cards
   works as well and keeps its own columns. */
.steps { counter-reset: step; list-style: none; margin: 0; padding: 0; display: grid; gap: 20px; grid-template-columns: repeat(auto-fit, minmax(min(100%, 15rem), 1fr)); }
.grid { counter-reset: step; }
/* A step is a card so the four of them read as one set rather than as four loose paragraphs, and so
   the watermark below has an edge to be clipped by. */
.step { position: relative; isolation: isolate; overflow: hidden; display: grid; grid-template-columns: 2.6rem 1fr; gap: 14px; align-items: start;
  padding: clamp(20px, 2.4vw, 28px); border: var(--rule-w) solid var(--rule); border-radius: var(--radius); background: var(--paper-2); }
.step::before { counter-increment: step; content: counter(step); width: 2.6rem; height: 2.6rem; border-radius: 50%; background: var(--ink); color: var(--paper); display: grid; place-items: center; font-weight: 700; font-variant-numeric: tabular-nums; }
.step > div { min-width: 0; }
.step :is(h2, h3) { font-size: var(--t1); letter-spacing: -.015em; margin: 6px 0 6px; }
.step p { margin: 0; color: var(--ink-2); }
/* Four across, the number's own column leaves the words about 140 px and they break every second
   word. There the number sits above and the text takes the card's full width. */
.grid-4 > .step { grid-template-columns: minmax(0, 1fr); gap: 10px; }
.grid-4 > .step :is(h2, h3) { margin-block-start: 0; }
/* Whatever follows a row of steps — usually the button to the longer page — keeps clear of it. */
:is(.steps, .grid.steps) + p { margin-block-start: clamp(22px, 3vw, 32px); }
/* The icon, blown up into a watermark bleeding off the card's top corner: it says what the step is
   about at a glance and never competes with the words. It is out of flow, so the heading keeps its
   own spacing and nothing reserves room for it.

   z-index -1 with `isolation: isolate` on the card is what keeps it behind the words: a negative
   child paints after its stacking context's own background but before the content. The card is also
   the positioned ancestor, so the icon hangs off the card's corner whatever the padding. Large line
   art thins optically (DESIGN §10), hence the light stroke. */
.step .step-ico { position: absolute; z-index: -1; pointer-events: none; inset-block-start: -18px; inset-inline-end: -22px;
  width: 132px; height: 132px; margin: 0; --ci-stroke: 1.2; color: var(--brand-2); opacity: .09; }
/* On a dark card the same ink barely registers, and under a contrast theme a watermark is noise. */
:is([data-theme="dark"], [data-theme="contrast-dark"]) .step .step-ico { color: var(--brand-3); opacity: .14; }
@media (prefers-color-scheme: dark) { :root:not([data-theme]) .step .step-ico { color: var(--brand-3); opacity: .14; } }
[data-theme^="contrast"] .step .step-ico { display: none; }
@media (prefers-contrast: more) { :root:not([data-theme]) .step .step-ico { display: none; } }
@media (max-width: 560px) { .step .step-ico { width: 104px; height: 104px; inset-block-start: -14px; inset-inline-end: -16px; } }
/* The watermark draws its own strokes as the step scrolls into view (PLAN §3 S3). The kit's icons
   carry pathLength="1", so a dash of 1 is the whole stroke. Scroll-driven and supported-only; under
   reduced motion, or in a browser without view timelines, the icon is simply drawn. */
@supports (animation-timeline: view()) {
  @media (prefers-reduced-motion: no-preference) {
    .step .step-ico:not(.ci-draw) [pathLength] { stroke-dasharray: 1; animation: step-draw linear both; animation-timeline: view(); animation-range: entry 10% cover 45%; }
  }
}
@keyframes step-draw { from { stroke-dashoffset: 1; } to { stroke-dashoffset: 0; } }

/* ---- the closing band: `.cta-band` ---------------------------------------------------------------
   One sentence and one action, centred. Behind it, bleeding off the corner, the castomo ring in thin
   line art — the open C's two arcs, never closed (DESIGN §0.3) — at 8 %, the one mark of the brand
   the page ends on. Hidden on phones and in the contrast themes, and never printed. */
.cta-band { position: relative; overflow: clip; isolation: isolate; text-align: center; padding: clamp(56px, 9vw, 112px) 0; border-block-start: var(--rule-w) solid var(--rule); }
.cta-band h2 { font-size: clamp(var(--t3), 4.5vw, var(--t5)); max-width: 20ch; margin-inline: auto; }
.cta-band p { max-width: 56ch; margin-inline: auto; color: var(--ink-2); text-wrap: pretty; }
.cta-band p:has(> .btn) { justify-content: center; margin-block-start: 28px; }
/* The section-head itself keeps a 68ch max-width (see above); without centring that box too, a
   short centred heading reads as left-shifted because its own box never moved off the left edge. */
.cta-band .section-head { margin-inline: auto; }
.cta-band .lead { margin-inline: auto; }
.cta-band .hero-ctas { justify-content: center; }
.cta-band::before { content: ""; position: absolute; z-index: -1; inline-size: 560px; block-size: 560px; inset-block-end: -230px; inset-inline-end: -150px;
  background: var(--accent-ink); opacity: .08; rotate: -18deg; pointer-events: none;
  -webkit-mask: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='black' stroke-width='.09'%3E%3Cpath d='M4.628 11.355A7.4 7.4 0 0 1 16.76 6.33M16.76 17.67A7.4 7.4 0 0 1 4.628 12.645'/%3E%3C/svg%3E") center / contain no-repeat;
  mask: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='black' stroke-width='.09'%3E%3Cpath d='M4.628 11.355A7.4 7.4 0 0 1 16.76 6.33M16.76 17.67A7.4 7.4 0 0 1 4.628 12.645'/%3E%3C/svg%3E") center / contain no-repeat; }
[data-theme^="contrast"] .cta-band::before { display: none; }
@media (max-width: 719.98px), (prefers-contrast: more), (forced-colors: active) { .cta-band::before { display: none; } }

/* ---- page heads -----------------------------------------------------------------------------------
   Every inner page opens on a .section whose .section-head holds the kicker, the h1 and the lead. It
   gets the page head's measure: an h1 capped at --t5 (DESIGN §3, long Polish titles run past forty
   characters), a little more air above than a section between two others, and a lead that ends the
   head rather than floating into what follows. */
main > .section:first-child { padding-block-start: clamp(40px, 6vw, 80px); }
.section-head h1 { font-size: clamp(var(--t4), 5.5vw, var(--t5)); letter-spacing: -.03em; line-height: 1.06; max-width: 22ch; margin-block: 0 .42em; }
.section-head .lead:last-child { margin-block-end: 0; }
.section-head .objection:first-child { margin-block-start: 0; }
/* A page head with a drawing beside it (tools/compose_art.py): the words keep their measure on the
   left, the drawing takes the rest, and the two share the head's margin below. Under 860px the
   drawing drops beneath the lead, the same break the cloned slides use. */
.head-art { display: grid; grid-template-columns: minmax(0, 1fr) minmax(0, 440px); gap: clamp(28px, 6vw, 96px); align-items: center; margin-block-end: clamp(24px, 4vw, 40px); }
.head-art .section-head { margin-block-end: 0; }
@media (max-width: 860px) { .head-art { grid-template-columns: 1fr; } }
/* The internal shelf of every drawing (/drawings/): a name under each, in the code font, because
   the name is what a page's art tag uses. */
.art-grid { margin-block-end: clamp(32px, 5vw, 56px); }
.art-grid .lg-art { padding: clamp(12px, 2vw, 24px); border: var(--rule-w) solid var(--rule); border-radius: var(--radius); }
.art-grid .lg-ink { max-block-size: 300px; }
.art-grid figcaption { margin-block-start: 12px; font-size: var(--t-1); color: var(--ink-2); }
.art-grid figcaption b { font-family: ui-monospace, SFMono-Regular, Menlo, monospace; font-weight: 600; color: var(--ink); }

/* ------------------------------------------------------------------ the showpiece panel
   A full-viewport photograph with the words on top — dash-b.com's shape, which is where this was
   borrowed from. The panel is always dark whatever theme the reader has chosen: the ground is a
   photograph, so the text colour cannot follow the palette without failing on half the pictures. */
.panel { position: relative; display: grid; align-content: end; min-block-size: 100svh; padding-block: clamp(96px, 14vh, 168px) clamp(52px, 9vh, 104px); overflow: hidden; isolation: isolate; color: #fff; }
.panel.p-mid { align-content: center; }
/* Only the stacking context here: .wrap already carries the page's gutter and max width, and
   setting a width on it again would run the headline into the left edge of the screen. */
.panel > .wrap { position: relative; z-index: 2; }

/* A real <img>, not a background-image: only an element gets to pick a width out of srcset, to be
   deferred with loading="lazy" and to be decoded off the main thread. */
.shot { position: absolute; inset: 0; z-index: 0; margin: 0; }
.shot img { inline-size: 100%; block-size: 100%; object-fit: cover; object-position: var(--focus, 50% 45%); background: var(--lqip) center / cover no-repeat; }
/* Two gradients, because the words need contrast and the photograph does not. The darkening is
   heavy where the text sits and gone by the far edge, so the picture is still a picture. */
.shot::after { content: ""; position: absolute; inset: 0; z-index: 1; --edge: linear-gradient(to right, rgba(6, 7, 10, .70), rgba(6, 7, 10, 0) 66%); background: linear-gradient(to top, rgba(6, 7, 10, .94) 0%, rgba(6, 7, 10, .78) 24%, rgba(6, 7, 10, .32) 58%, rgba(6, 7, 10, .46) 100%), var(--edge); }
/* High contrast asks for certainty, not atmosphere: the photograph stays, nearly blacked out, so
   the words measure against something close to solid ink instead of against a sunlit wall. */
[data-theme="contrast"] .shot::after, [data-theme="contrast-dark"] .shot::after { background: rgba(0, 0, 0, .88); }
[data-theme="contrast"] .panel .objection, [data-theme="contrast-dark"] .panel .objection { color: #fff; }

/* The sentence the visitor already thought, set as the thing being answered. It reads as a
   quotation rather than as a label — a label announces a section, this one starts an argument. */
.panel .objection { font-size: clamp(.95rem, 1.7vw, 1.2rem); font-weight: 500; color: var(--brand-3); letter-spacing: .005em; margin: 0; }
.panel-say { max-inline-size: 46rem; }
.panel :is(h1, h2) { margin-block-start: .42em; font-size: clamp(2.2rem, 6.4vw, 4.9rem); line-height: 1.02; letter-spacing: -.035em; font-weight: 700; color: #fff; text-wrap: balance; }
.panel-say p:not(.objection) { margin-block-start: 1.05em; max-inline-size: 36ch; font-size: clamp(1.04rem, 1.35vw, 1.22rem); color: rgba(255, 255, 255, .86); text-wrap: pretty; }
/* The same short line that stretches as every text link (§0.11), drawn on a dark panel. */
.panel-say .more { display: inline-flex; align-items: center; gap: .5em; margin-block-start: 1.5em; font-weight: 600; color: #fff; text-decoration: none; padding-block-end: 3px;
  background: linear-gradient(currentColor, currentColor) 0 100% / var(--u-rest) var(--u-thick) no-repeat; transition: background-size .6s var(--u-spring), gap var(--dur); }
.panel-say .more:is(:hover, :focus-visible) { background-size: 100% var(--u-thick); gap: .8em; }
.panel .hero-ctas { margin-block-start: 1.6em; }
.panel .btn-primary { background: var(--fill-hover); color: var(--on-fill); }
.panel .btn-primary:hover { background: var(--brand-3); color: #0B0C10; }
.panel .btn-ghost { border-color: rgba(255, 255, 255, .34); color: #fff; background: rgba(255, 255, 255, .07); }
.panel .btn-ghost:hover { border-color: #fff; background: rgba(255, 255, 255, .14); }
.panel :focus-visible { outline-color: #fff; }
@media (max-height: 720px) { .panel { min-block-size: auto; padding-block: clamp(88px, 12vh, 120px) clamp(44px, 8vh, 72px); } }

/* A .p-mid panel needs its own scrim. The showpiece gradient is weighted to the bottom, because that
   is where a .panel's words normally sit — by mid-height it has faded to about a third. Measured over
   the photographs these pages carry, white type in the middle of a bright one fell to 2.6:1. So a
   centred panel gets a band across its middle instead of a foot at its bottom, and a slightly longer
   reach on the edge layer, because the copy is left-aligned. */
.panel.p-mid .shot::after {
  --edge: linear-gradient(to right, rgba(6, 7, 10, .62), rgba(6, 7, 10, 0) 72%);
  background: linear-gradient(to top,
    rgba(6, 7, 10, .50) 0%,
    rgba(6, 7, 10, .74) 30%,
    rgba(6, 7, 10, .78) 58%,
    rgba(6, 7, 10, .44) 100%), var(--edge);
}

/* One motion idea, done once: as a panel arrives its photograph relaxes out of a slight overscale.
   No parallax — a scroll-linked transform on a full-bleed image is the cheapest way to make a fast
   page feel slow. Both switches are honoured, the reader's and the system's, and the resting state
   is the un-scaled one so a browser that never adds .in still shows the picture correctly. */
@media (prefers-reduced-motion: no-preference) {
  .js .panel .shot img { transform: scale(1.06); transition: transform 1.4s cubic-bezier(.16, 1, .3, 1); }
  .js .panel.in .shot img { transform: none; }
}
[data-motion="reduce"] .panel .shot img { transform: none !important; }

/* The photographs come from media.castomapi.com under licences that all require attribution, so a
   page with a photograph ends with a generated credit line. */
.credits { font-size: var(--t-1); color: var(--ink-3); max-inline-size: 80ch; }
.credits a { color: inherit; }
/* The credit line is the last thing on every page, so nothing can scroll it out from under the
   floating back-to-top and chat buttons (--castomo-size, 20px off the edge): it sat under back-to-top
   at every width (2026-09-24, found by tools/site-checks `float`). Its bottom padding clears them. */
.section-credits { padding-block: 18px calc(var(--castomo-size, 56px) + 36px); }
.foot-credits { margin: 24px 0 0; color: var(--ink-3); font-size: var(--t-1); }
.foot-credits a { color: var(--ink-3); }
.foot-credits a:hover { color: var(--ink); }

/* ------------------------------------------------------------------ the thread card
   What one contact looks like once it has landed. Not a screenshot — the same markup the console
   draws, so it cannot go stale against a design that has moved on. DESIGN §8.3 lists it for removal;
   it stays only while the current home body still uses it for the hand-over list. */
.thread { border: var(--rule-w) solid var(--rule); border-radius: var(--radius); background: var(--paper-2); overflow: hidden; font-size: var(--t-1); min-width: 0; }
.thread-h { display: flex; flex-wrap: wrap; align-items: center; gap: 8px 10px; padding: 12px 14px; background: var(--field); border-bottom: var(--rule-w) solid var(--rule); }
.thread-h b { font-weight: 600; font-size: var(--t0); }
.thread-h .chip { margin-inline-start: auto; }
.thread ul { list-style: none; margin: 0; padding: 0; }
/* Every cell is placed explicitly. Left to auto-placement the avatar's row span pushes the message
   into the 2rem column, which wraps it one word per line. */
.thread li { display: grid; grid-template-columns: 2rem auto minmax(0, 1fr); column-gap: 12px; row-gap: 2px; padding: 12px 14px; border-top: var(--rule-w) solid var(--rule); }
.thread li:first-child { border-top: 0; }
.thread li i { grid-column: 1; grid-row: 1 / span 2; width: 2rem; height: 2rem; border-radius: 50%; background: var(--accent-soft); color: var(--accent-ink); display: grid; place-items: center; font-style: normal; font-weight: 700; font-size: var(--t-1); }
[data-theme="contrast"] .thread li i, [data-theme="contrast-dark"] .thread li i { border: var(--rule-w) solid var(--rule); }
.thread li b { grid-column: 2; grid-row: 1; font-weight: 600; color: var(--ink); }
.thread li time { grid-column: 3; grid-row: 1; justify-self: start; align-self: baseline; color: var(--ink-3); font-size: var(--t-1); }
.thread li span { grid-column: 2 / -1; grid-row: 2; color: var(--ink-2); line-height: 1.45; }
.thread-f { padding: 10px 14px; border-top: var(--rule-w) solid var(--rule); color: var(--ink-3); display: flex; flex-wrap: wrap; gap: 6px 14px; }

/* ------------------------------------------------------------------ print (the site; card.css prints the card) */
@media print {
  .bar, .subnav, .foot, .totop, .player, .skip, .board-filter, .board-draft, .hero-peek, .deco-big, .lane-note, .try-chips { display: none !important; }
  body { background: #fff; color: #000; }
  a { color: #000; }
  .card, .thread, .board-tile, .step, .faq details { break-inside: avoid; }
  /* A chapter's rule is drawn in --ink, which stays light when the reader prints from a dark theme;
     on paper it is redrawn in black, and the heading is never left alone at the foot of a page. */
  .board-chapter-head { border-block-start-color: #000; break-after: avoid; break-inside: avoid; }
  .cta-band::before, .hero-job::before { display: none; }
  .band-ink { background: #fff; color: #000; }
}

/* ------------------------------------------------------------------ contact form
   Same shape as every other field on the site — a --paper-2 box on the page's own
   ground, the same focus ring everything else wears — so a form assembled for one
   page still reads as part of the same product. */
.contact-form { max-width: 42rem; margin: 24px 0; display: grid; gap: 16px; }
.contact-field { display: grid; gap: 6px; }
.contact-field label { font-size: var(--t-1); font-weight: 600; color: var(--ink); display: flex; align-items: baseline; gap: 8px; }
.contact-field .opt { font-weight: 400; font-size: var(--t-1); color: var(--ink-3); }
.contact-field input, .contact-field select, .contact-field textarea {
  font: inherit; font-size: var(--t0); color: var(--ink);
  background: var(--paper-2); border: var(--rule-w) solid var(--rule);
  border-radius: var(--radius-sm); padding: 10px 12px; width: 100%; min-block-size: 44px;
  transition: border-color var(--dur), background var(--dur);
}
.contact-field textarea { resize: vertical; min-height: 8rem; line-height: 1.55; }
.contact-field select {
  appearance: none; -webkit-appearance: none; padding-inline-end: 2.4rem;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23888' stroke-width='2.2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='m6 9 6 6 6-6'/%3E%3C/svg%3E");
  background-repeat: no-repeat; background-size: 1.1rem; background-position: right 12px center;
}
[dir="rtl"] .contact-field select { background-position: left 12px center; }
.contact-field input:hover, .contact-field select:hover, .contact-field textarea:hover { border-color: var(--accent); }
.contact-field input:focus-visible, .contact-field select:focus-visible, .contact-field textarea:focus-visible {
  outline: 3px solid var(--accent-ink); outline-offset: 2px; border-color: var(--accent-ink);
}
.contact-hint { margin: 0; font-size: var(--t-1); color: var(--ink-3); line-height: 1.5; }
.contact-status[data-kind="ok"] { color: var(--ok); font-weight: 600; }
.contact-status[data-kind="bad"] { color: var(--bad); font-weight: 600; }
.contact-status a { color: inherit; }
.contact-actions { display: flex; align-items: center; flex-wrap: wrap; gap: 10px 16px; }
.contact-actions .contact-hint { flex: 1 1 14rem; }
.btn[disabled], .btn[aria-disabled="true"] { opacity: .5; cursor: not-allowed; }
/* The honeypot: off-screen above the page, not beside it — a negative inline offset
   lands inside the scrollable area in a right-to-left document. */
.contact-trap { position: absolute; inline-size: 1px; block-size: 1px; overflow: hidden; clip-path: inset(50%); inset-block-start: 0; inset-inline-start: 0; transform: translateY(-200%); }

/* Icons that sit INSIDE a heading render as blocks (the reset makes every expanded <svg> one), so the
   icon takes its own line above the words — bigger, and with real space beneath. The .step-ico here
   is its inline size; inside a .step card the watermark rule above takes over. */
.step-ico { --ci-stroke: 2; width: 40px; height: 40px; margin-bottom: 12px; color: var(--accent-ink); }
[data-theme^="contrast"] .step-ico { color: var(--ink); }

/* ------------------------------------------------------------------ castomo-icons placements
   Big marks (120px+, the `big` group) are purely decorative: absolutely positioned inside a
   position:relative host, pointer-events:none so they never intercept a click or a text selection,
   and coloured from the accent tokens so they follow theme/contrast switching for free. They never
   sit on top of running text, at most two per page, and they go below 720px (DESIGN §10). */
.card-ico, .aside-ico, .cta-ico, .foot-fam-ico {
  color: var(--accent-ink); vertical-align: -6px; margin-inline-end: 6px; flex: none; --ci-stroke: 2;
}
/* The ecosystem heading's planet is drawn larger than the icons beside body text, and a stroke that
   reads as fine at 26px reads as heavy blown up — so it is thinned to keep the same optical weight. */
.foot-fam-ico { --ci-stroke: 0.8; }
[data-theme^="contrast"] :is(.card-ico, .aside-ico, .cta-ico, .foot-fam-ico) { color: var(--ink); }
.card-ico { width: 26px; height: 26px; }
/* The selector covers h2 as well as h3 because the a11y work promoted the <h3>s that preceded a
   page's first <h2>; the gap is 10px because the icon sits inline in this heading. */
.card :is(h2, h3) { display: flex; align-items: center; gap: 10px; }
.aside-ico { width: 24px; height: 24px; margin-bottom: 10px; }

.page-head, .hero, .cta-band, .try, .foot-family { position: relative; }
.deco-big { position: absolute; pointer-events: none; color: var(--accent); opacity: .9; z-index: 0; --ci-big: 160px; --ci-stroke: 2.8; }
[data-theme^="contrast"] .deco-big { color: var(--ink); opacity: 1; }
.page-head > .wrap, .hero > .wrap, .cta-band > .wrap, .try > .wrap { position: relative; z-index: 1; }
.deco-page { top: -4px; inset-inline-end: 0; }
.deco-page-small { position: absolute; pointer-events: none; color: var(--accent); opacity: .95; top: 2px; inset-inline-end: 0; --ci-size: 36px; --ci-stroke: 2; }
[data-theme^="contrast"] .deco-page-small { color: var(--ink); opacity: .85; }
/* The arrow from the empty card's first row into the prompt (PLAN §2.2): just past the prompt's
   right edge, mirrored so the arc ends pointing left, into the box. */
.deco-hero-arrow { --ci-big: 118px; top: 50%; inset-inline-start: calc(100% + 6px); opacity: 1; transform: translateY(-78%) scaleX(-1) rotate(-6deg); }
.deco-cta { --ci-big: 150px; bottom: -10px; inset-inline-start: 2%; opacity: .85; }
@media (max-width: 720px) { .deco-big, .deco-page-small { display: none; } }
/* The prompt box widens as the hero reflows; hide the arrow before it could reach the card or the edge. */
@media (max-width: 1100px) { .deco-hero-arrow { display: none; } }

/* ------------------------------------------------------------------ the price ledger
   The comparison table. It scrolls inside its own box on a phone, never the page; the soft shades at
   either edge appear only while there is more table that way (the `local` covers scroll with the
   content and hide the fixed shades at the ends), so a reader can see the table goes on. */
.compare-wrap { overflow-x: auto; margin-block-start: clamp(56px, 8vw, 88px); border: var(--rule-w) solid var(--rule); border-radius: var(--radius); background-color: var(--paper-2);
  background-image: linear-gradient(to right, var(--paper-2) 30%, transparent), linear-gradient(to left, var(--paper-2) 30%, transparent),
    radial-gradient(farthest-side at 0 50%, color-mix(in srgb, var(--ink) 18%, transparent), transparent),
    radial-gradient(farthest-side at 100% 50%, color-mix(in srgb, var(--ink) 18%, transparent), transparent);
  background-position: left center, right center, left center, right center;
  background-size: 40px 100%, 40px 100%, 14px 100%, 14px 100%;
  background-repeat: no-repeat; background-attachment: local, local, scroll, scroll; }
.compare { inline-size: 100%; min-inline-size: 720px; border-collapse: collapse; font-size: var(--t-1); }
.compare caption { text-align: start; padding: 22px 24px 6px; font-size: var(--t2); font-weight: 700; letter-spacing: -.02em; color: var(--ink); }
.compare th, .compare td { padding: 13px 18px; text-align: center; border-block-end: var(--rule-w) solid var(--rule); vertical-align: middle; }
.compare th[scope="row"] { text-align: start; font-weight: 500; color: var(--ink-2); inline-size: 40%; }
.compare thead th { position: sticky; inset-block-start: 0; background: var(--paper-2); font-size: var(--t0); color: var(--ink); padding-block: 18px 14px; }
.compare thead th span { display: block; margin-block-start: 4px; font-size: var(--t-1); font-weight: 500; color: var(--ink-3); }
.compare thead th.is-featured, .compare td.is-featured { background: var(--accent-soft); }
.compare tbody tr.group th { text-align: start; padding-block: 20px 10px; font-size: var(--t-1); font-weight: 700; letter-spacing: .06em; text-transform: uppercase; color: var(--accent-ink); background: var(--paper-2); }
.compare tbody tr:last-child th, .compare tbody tr:last-child td { border-block-end: 0; }
.compare .yes, .compare .no { display: inline-block; width: 22px; height: 22px; vertical-align: middle; }
.compare .yes { position: relative; border-radius: 50%; background: var(--accent); }
.compare .yes::after { content: ""; position: absolute; inset: 5px; background: var(--on-fill); -webkit-mask: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='black' stroke-width='3.6' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M4 12.5 9.5 18 20 6.5'/%3E%3C/svg%3E") center / contain no-repeat; mask: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='black' stroke-width='3.6' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M4 12.5 9.5 18 20 6.5'/%3E%3C/svg%3E") center / contain no-repeat; }
.compare .no { width: 14px; height: 2px; border-radius: 2px; background: var(--ink-3); opacity: .5; }
/* A reader who has just decided should not have to scroll back up to act, so a comparison ends in
   buttons (Maciej's 33ac487); the buttons carry the icons the build gives every button. */
.compare tfoot th, .compare tfoot td { border-block-end: 0; padding-block: 20px 22px; }
.compare tfoot .btn { inline-size: 100%; justify-content: center; }

/* ---------------------------------------------------------------- the status chip (DESIGN §2.4)
 *
 * One chip for every place a status is printed — the board, the ledger, the family shelf — so they
 * cannot drift. Always a dot AND a word (WCAG 1.4.1): the word is in ink, the shape and colour of the
 * mark before it say the same thing again for a reader who scans. runs: a solid green dot. team: a
 * solid square in the accent. preview: a hollow dashed ring inside a dashed outline, the one chip
 * that looks unfinished, because it is. partner: no mark, a dotted outline — it is not ours.
 * The marks are drawn with borders, not backgrounds, so forced-colours mode keeps them.
 */
.status { display: inline-flex; align-items: center; gap: 7px; max-inline-size: 100%; padding: 3px 11px 3px 9px; border-radius: var(--radius-pill);
  font-size: var(--t-1); font-weight: 600; line-height: 1.35; background: var(--paper-2); color: var(--ink); border: var(--rule-w) solid var(--rule); }
.status::before { content: ""; flex: none; box-sizing: border-box; inline-size: 9px; block-size: 9px; border-radius: 50%; border: 4.5px solid var(--ok); }
.status[data-status="team"]::before { border-radius: 1px; border-color: var(--accent-ink); }
.status[data-status="preview"] { color: var(--ink-2); border-style: dashed; border-color: var(--ink-2); }
.status[data-status="preview"]::before { inline-size: 10px; block-size: 10px; border: 1.5px dashed var(--ink-2); }
.status[data-status="partner"] { color: var(--ink-2); border-style: dotted; border-color: var(--ink-3); padding-inline-start: 11px; }
.status[data-status="partner"]::before { content: none; }

/* ---------------------------------------------------------------- the runtime board (PLAN §3 S4)
 *
 * tools/build_site.py emits this markup from data/offer.py: the board and board_page tags. Each tile
 * is a whole-card link — the job's own link is stretched over the tile, so the mark, the words and
 * the space round them all answer, and there is still one link, one tab stop and one name. Tiles are
 * neutral at rest; the product's own hue (--hue, inline from data/marks.py) shows only on intent: a
 * 3 px rule drawn across the top, the tile's edge, and the domain pill filling (DESIGN §10).
 */
.board { margin-block-start: clamp(28px, 4vw, 44px); }
.board-strip { margin: 0 0 18px; font-size: var(--t-1); color: var(--ink-2); }
.board-strip b { color: var(--ink); font-variant-numeric: tabular-nums; }

/* CSS-only filtering. The radios sit in the fieldset above the groups, so the board reads its own
 * state with :has() — no JavaScript, and the page works identically with it switched off. */
.board-filter { border: 0; margin: 0 0 clamp(24px, 3vw, 36px); padding: 0; display: flex; flex-wrap: wrap; gap: 8px; }
.board-filter input { position: absolute; opacity: 0; pointer-events: none; }
.board-filter label { display: inline-flex; align-items: center; min-block-size: 44px; padding: 8px 16px;
  border-radius: var(--radius-pill); border: var(--rule-w) solid var(--rule); background: var(--paper-2);
  font-size: var(--t-1); font-weight: 600; color: var(--ink-2); cursor: pointer; transition: border-color var(--dur), color var(--dur), background var(--dur); }
.board-filter label:hover { border-color: var(--ink-3); color: var(--ink); }
.board-filter input:checked + label { background: var(--btn-bg); border-color: var(--btn-bg); color: var(--btn-fg); }
.board-filter input:focus-visible + label { outline: 3px solid var(--accent-ink); outline-offset: 2px; }

/* The ladder (POSITIONING §2): Start · Scale-up · Growth sit ABOVE the seven groups as chapters, so
   a visitor places their own business before reading a product name. The rule above each chapter
   does the separating, which is why a chapter's first group gets no extra top margin of its own. */
.board-chapter + .board-chapter { margin-block-start: clamp(44px, 6vw, 76px); }
.board-chapter-head { margin-block-end: clamp(24px, 3vw, 36px); max-inline-size: 60ch;
  border-block-start: 2px solid var(--ink); padding-block-start: 14px; }
.board-chapter-head :is(h2, h3, h4) { font-size: var(--t3); letter-spacing: -.025em; margin: 0 0 6px; }
.board-chapter-head p { margin: 0; color: var(--ink-2); max-inline-size: 52ch; }
/* The rung's name is also the counter a reader keeps: numbered so "where am I on this ladder" has an
   answer, and decorative to a screen reader because the heading text already answers it. */
.board-chapter-head :is(h2, h3, h4)::before { content: counter(rung, decimal-leading-zero) " ";
  font: 600 var(--t-1)/1 ui-monospace, "SF Mono", Menlo, Consolas, "Liberation Mono", monospace; color: var(--ink-3);
  margin-inline-end: 10px; vertical-align: .35em; letter-spacing: .04em; }
.board { counter-reset: rung; }
.board-chapter { counter-increment: rung; }

.board-group + .board-group { margin-block-start: clamp(36px, 5vw, 56px); }
.board-chapter-head + .board-group { margin-block-start: 0; }
.board-group-head { margin-block-end: 18px; max-inline-size: 60ch; }
.board-group-head :is(h2, h3, h4, h5) { font-size: var(--t2); letter-spacing: -.02em; margin: 0 0 4px; }
.board-group-head p { margin: 0; color: var(--ink-2); font-size: var(--t-1); }
/* Wide screens: the group's name and line on the left, sticky while its tiles pass, the tiles on the
   right — the board reads as a ledger of outcomes, not as a wall of logos. */
@media (min-width: 1100px) {
  .board-group { display: grid; grid-template-columns: minmax(0, 15rem) minmax(0, 1fr); column-gap: clamp(28px, 4vw, 56px); align-items: start; }
  .board-group-head { grid-column: 1; grid-row: 1 / span 2; position: sticky; inset-block-start: calc(var(--bar-h) + 24px); }
  .board-tiles, .board-draft { grid-column: 2; }
}

/* A flex row, not auto-fill: a board holds as many tiles as the jobs it lists, and an odd count must
   widen its last tile rather than leave a hole beside it (§0.13). */
.board-tiles { list-style: none; margin: 0; padding: 0;
  display: flex; flex-wrap: wrap; gap: clamp(14px, 1.6vw, 20px); }
.board-tiles > * { flex: 1 1 max(290px, calc(50% - 20px)); min-width: 0; }
.board-tile { display: grid; grid-template-columns: 40px minmax(0, 1fr); column-gap: 14px; row-gap: 4px; align-content: start; overflow: hidden;
  transition: transform var(--dur) var(--ease), box-shadow var(--dur) var(--ease), border-color var(--dur) var(--ease); }
.board-tile > * { grid-column: 1 / -1; margin: 0; }
/* The family marks ship as bare <svg> with no intrinsic size; without a size they render at the
 * container's full width. 40 px on the board (DESIGN §10). */
.board-mark { grid-column: 1; grid-row: 1 / span 2; inline-size: 40px; block-size: 40px; border-radius: 10px; display: block; align-self: start; }
.board-job { grid-column: 2; align-self: end; font-size: var(--t1); line-height: 1.25; letter-spacing: -.015em; }
.board-product { grid-column: 2; font-size: var(--t-1); font-weight: 600; color: var(--ink-3); }
.board-job a { color: var(--ink); text-decoration: none; }
.board-job a::after { content: ""; position: absolute; inset: 0; z-index: 1; border-radius: var(--radius); }
.board-blurb { margin-block-start: 10px; font-size: var(--t-1); color: var(--ink-2); line-height: 1.5; }
.board-meta { display: flex; flex-wrap: wrap; align-items: center; gap: 8px 12px; margin-block-start: 12px; }
.board-price { font-size: var(--t-1); font-weight: 600; color: var(--ink); font-variant-numeric: tabular-nums; }
.board-price--ask { font-weight: 500; color: var(--ink-2); font-variant-numeric: normal; }
.board-domain { justify-self: start; display: inline-flex; align-items: center; gap: 6px; margin-block-start: 12px; padding: 5px 12px;
  border: var(--rule-w) solid var(--rule); border-radius: var(--radius-pill); font-size: var(--t-1); color: var(--ink-2);
  transition: background-color var(--dur) var(--ease), border-color var(--dur) var(--ease), color var(--dur) var(--ease); }
.board-domain::after { content: ""; inline-size: .9em; block-size: .9em; flex: none; background: currentColor; transition: transform var(--dur) var(--ease);
  -webkit-mask: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='black' stroke-width='2.2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M5 12h14M13 6l6 6-6 6'/%3E%3C/svg%3E") center / contain no-repeat;
  mask: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='black' stroke-width='2.2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M5 12h14M13 6l6 6-6 6'/%3E%3C/svg%3E") center / contain no-repeat; }
.board-tile::before { content: ""; position: absolute; inset-block-start: 0; inset-inline: 0; block-size: 3px; background: var(--hue, var(--accent)); transform: scaleX(0); transform-origin: 0 50%; transition: transform var(--dur) var(--ease); pointer-events: none; }
.board-tile:hover { transform: translateY(-3px); box-shadow: var(--shadow); border-color: var(--hue, var(--accent)); }
.board-tile:hover::before { transform: scaleX(1); }
.board-tile:hover .board-domain { border-color: var(--hue, var(--accent-ink)); color: var(--ink); }
.board-tile:hover .board-domain::after { transform: translateX(3px); }
/* Keyboard focus lands on the job's link; the whole tile shows it, like the hover. Where :has() is
   missing the link keeps its own ring, so focus is never invisible. */
@supports selector(:has(a)) {
  .board-job a:focus-visible { outline: none; }
  .board-tile:has(.board-job a:focus-visible) { outline: 3px solid var(--accent-ink); outline-offset: 3px; transform: translateY(-3px); box-shadow: var(--shadow); border-color: var(--hue, var(--accent)); }
  .board-tile:has(.board-job a:focus-visible)::before { transform: scaleX(1); }
  .board-tile:has(.board-job a:focus-visible) .board-domain { border-color: var(--hue, var(--accent-ink)); color: var(--ink); }
}
/* The pill fills with the product's colour only where the browser can pick the text colour from that
   fill: OKLCH lightness below .58 writes white, above writes black. */
@supports (color: oklch(from red l c h)) {
  .board-tile:hover .board-domain, .board-tile:has(.board-job a:focus-visible) .board-domain { background: var(--hue, var(--accent-ink)); color: oklch(from var(--hue, var(--accent-ink)) clamp(0, (.58 - l) * 1000, 1) 0 0); }
}
[data-theme^="contrast"] .board-tile { --hue: var(--accent-ink) !important; }
@media (prefers-contrast: more) { :root:not([data-theme]) .board-tile { --hue: var(--accent-ink) !important; } }
.board-draft { margin: 16px 0 0; }

.board:has(#bf-runs:checked) .board-tile:not([data-status="runs"], [data-status="runs-quote"]),
.board:has(#bf-team:checked) .board-tile:not([data-status="team"]),
.board:has(#bf-preview:checked) .board-tile:not([data-status="preview"], [data-status="partner"]) { display: none; }
.board:has(#bf-runs:checked) .board-group:not(:has(.board-tile:is([data-status="runs"], [data-status="runs-quote"]))),
.board:has(#bf-team:checked) .board-group:not(:has(.board-tile[data-status="team"])),
.board:has(#bf-preview:checked) .board-group:not(:has(.board-tile:is([data-status="preview"], [data-status="partner"]))) { display: none; }

/* The ledger (DESIGN §8.2): a .compare table of what can be ordered and at what price, each price in
 * its own currency and never summed. On a section of its own it follows the heading directly. The
 * foot row is the "no total" line: a column of prices invites a sum the page cannot honour, so the
 * table ends by saying why it has none — ruled off, in the secondary ink, not as an afterthought. */
.compare-wrap:has(> .ledger) { margin-block-start: 0; }
.ledger { min-inline-size: 760px; }
.ledger thead th { font-size: var(--t-1); font-weight: 700; letter-spacing: .06em; text-transform: uppercase; color: var(--ink-2); padding-block: 16px 12px; }
.ledger th, .ledger td { text-align: start; }
.ledger th[scope="row"] { inline-size: 30%; color: var(--ink); font-weight: 600; font-size: var(--t0); line-height: 1.35; }
.ledger .status { white-space: nowrap; }
.ledger-price { font-weight: 600; color: var(--ink); font-variant-numeric: tabular-nums; }
.ledger td:last-child { inline-size: 1%; white-space: nowrap; }
.ledger .btn { inline-size: 100%; justify-content: space-between; }
.ledger tfoot td { padding: 16px 18px; border-block-start: var(--rule-w) solid var(--rule); font-size: var(--t-1); color: var(--ink-2); text-align: start; }

/* ---------------------------------------------------------------- the ink band's own cards
 *
 * The trust columns (PLAN §3 S8) sit on .band-ink. A paper card dropped on that band read as three
 * white holes in it; here a card stays part of the band instead — a panel a shade off the band's own
 * ground, ruled a shade lighter, holding the band's own text colour. Everything is mixed from the two
 * tokens the band itself uses, so the pair inverts correctly in all four themes. The first
 * declaration of each pair is the fallback for a browser without color-mix().
 */
.band-ink .card { background: var(--ink); background: color-mix(in srgb, var(--paper) 6%, var(--ink)); border-color: var(--paper); border-color: color-mix(in srgb, var(--paper) 22%, var(--ink)); color: var(--paper); }
.band-ink .card :is(h2, h3, h4) { color: var(--paper); }
.band-ink .card p { color: var(--paper); color: color-mix(in srgb, var(--paper) 86%, var(--ink)); }
.band-ink .card a:not(.btn) { color: var(--paper); }
.band-ink .card .note { color: var(--paper); background: none; }
.band-ink .grid { gap: clamp(14px, 2vw, 24px); }

/* The section kicker sits on the band itself. Both --accent-ink and --brand-3 are tuned for a paper
 * surface and fail on the band wherever it inverts (measured 2.01:1 in dark, 3.28:1 in light;
 * critique-taste #18). Tinting the band's own text colour toward the warm stop keeps the kicker
 * distinct and cannot fall below the body text it sits with. */
.band-ink .objection { color: var(--paper); color: color-mix(in srgb, var(--brand-3) 38%, var(--paper)); }
[data-theme^="contrast"] .band-ink .objection { color: var(--paper); }
/* The section numeral and the margin annotation take --ink-3 on paper, which is the band's own colour
   family here and would all but vanish. Both are mixed from the band's text colour instead, so they
   stay the quiet voice in every theme without dropping under the body text's contrast floor. */
.band-ink :is(.sec-n, .proof-note, .proof-note b) { color: var(--paper); color: color-mix(in srgb, var(--paper) 72%, var(--ink)); }
.band-ink .proof-note a { color: var(--paper); }
[data-theme^="contrast"] .band-ink :is(.sec-n, .proof-note, .proof-note b) { color: var(--paper); }
/* The craft cards (home, "A great product deserves a better delivery"): six short proofs, so the
   heading may run to two lines and the icon stays on the first one. */
.craft h3 { display: grid; grid-template-columns: auto minmax(0, 1fr); column-gap: 12px; align-items: start; font-size: var(--t1); line-height: 1.25; letter-spacing: -.015em; margin: 0 0 10px; }
.craft p { margin: 0; font-size: var(--t-1); line-height: 1.55; }

/* ------------------------------------------------------------------ the written pages, continued
   Legal and explanatory pages are long prose with numbered h2s and a contents list. The contents list
   is a <nav class="prose"> — boxed, in two columns where there is room. A heading reached from it is
   kept clear of the sticky bar and the section row, and marked while it is the :target, with an
   underline rather than a bar, so nothing moves when it is. */
nav.prose { padding: clamp(18px, 2.4vw, 26px) clamp(20px, 2.8vw, 30px); border: var(--rule-w) solid var(--rule); border-radius: var(--radius); background: var(--paper-2); }
.wrap > nav.prose { max-width: 56rem; }
nav.prose h2 { font-size: var(--t-1); font-weight: 700; letter-spacing: .08em; text-transform: uppercase; color: var(--ink-2); margin: 0 0 12px; }
nav.prose ol, nav.prose ul { columns: 2 17rem; column-gap: 36px; margin: 0; padding-inline-start: 1.5em; color: var(--ink-3); }
nav.prose li { break-inside: avoid; }
nav.prose li + li { margin-block-start: .4em; }
.prose :is(h2, h3)[id] { scroll-margin-block-start: calc(var(--bar-h) + 64px); }
.prose :is(h2, h3):target { text-decoration: underline 3px var(--accent); text-underline-offset: .22em; }
.prose p, .prose li { text-wrap: pretty; }
.prose code { padding: 1px 5px; border-radius: 4px; background: var(--field); color: var(--ink); font-size: .88em; }
:lang(pl) .prose { hyphens: auto; }

/* ------------------------------------------------------------------ forced colours
   Windows high-contrast replaces every colour with the system's few. What carries meaning here with
   a background alone has to be told to keep a visible shape: the checked filter and the selected
   specimen tab get an outline. The status marks are borders already, and borders survive. */
@media (forced-colors: active) {
  .board-filter input:checked + label, .anatomy [role="tab"][aria-selected="true"] { outline: 2px solid CanvasText; outline-offset: 2px; }
  .board-domain::after { forced-color-adjust: none; background: CanvasText; }
}

/* ------------------------------------------------------------------ chapters, proof notes, navigator
   Three reading devices laid over the sections PLAN §3 already fixed, so a page that argues for a
   long time still reads as a few chapters instead of one undifferentiated scroll.

   The numerals are decorative on purpose (the spans carry aria-hidden in the markup): the objection
   kicker and the h2 under it already name the section, and a reader who wants the ordering gets it
   spoken properly from the navigator's list. Announcing "zero one" before every heading would only
   put a second, weaker table of contents in the way. */
.sec-n {
  font-family: ui-monospace, "SF Mono", Menlo, Consolas, "Liberation Mono", monospace;
  font-size: var(--t-1); font-weight: 500; letter-spacing: .12em; color: var(--ink-3);
  margin-inline-end: .8em;
}

/* A chapter is two sections sharing one background. They keep the rule and the extra padding only at
   the chapter's own edges, so the pair reads as one block rather than two stacked bands. */
.section.band + .section.band { border-block-start: 0; padding-top: 0; }

/* The margin annotation: a mono pointer to a page that proves the section a second way. It sits at
   the end of its section, and only on wide screens does it move out to the margin the spec draws it
   in — below that there is no margin to move it into. Its "[ 02.A ]" marker is aria-hidden for the
   same reason the numerals are: it indexes the section numerals, which are themselves decorative,
   and the link beside it already says where it goes. */
.proof-note {
  margin-block-start: var(--s3);
  font-family: ui-monospace, "SF Mono", Menlo, Consolas, "Liberation Mono", monospace;
  font-size: var(--t-1); color: var(--ink-3);
}
.proof-note b { color: var(--ink-3); font-weight: 500; letter-spacing: .1em; margin-inline-end: .7em; }
.proof-note a { color: var(--accent-ink); }
@media (min-width: 1080px) {
  .proof-note { text-align: end; }
}

/* The sticky section navigator (bottom-left). It is an overlay, so it stays out of the way of the
   one thing the page is for: it appears only once the hero prompt and the empty card have scrolled
   away, and it is not shown at all on a phone, where a fixed chip would sit on the content. No
   transition anywhere in it, so there is no motion to suppress for a reader who asked for none. */
.secnav { position: fixed; inset-inline-start: 20px; inset-block-end: 20px; z-index: 40; display: none; }
@media (min-width: 900px) {
  .secnav[data-show] { display: block; }
}
.secnav-chip {
  display: inline-flex; align-items: center; gap: .55em; min-block-size: 44px; padding: 8px 16px;
  border: var(--rule-w) solid var(--rule); border-radius: var(--radius-pill);
  background: var(--paper-2); color: var(--ink-2); box-shadow: var(--shadow);
  font-family: ui-monospace, "SF Mono", Menlo, Consolas, "Liberation Mono", monospace;
  font-size: var(--t-1); font-variant-numeric: tabular-nums; cursor: pointer;
}
.secnav-chip:hover { color: var(--ink); border-color: var(--ink-3); }
.secnav-chip .secnav-n { color: var(--accent-ink); font-weight: 600; margin: 0; }
.secnav-list {
  position: absolute; inset-block-end: calc(100% + 8px); inset-inline-start: 0; margin: 0; padding: 8px;
  min-inline-size: 17rem; max-inline-size: min(24rem, calc(100vw - 40px));
  max-block-size: min(60vh, 34rem); overflow: auto; list-style: none;
  background: var(--paper-2); border: var(--rule-w) solid var(--rule);
  border-radius: var(--radius); box-shadow: var(--shadow);
}
.secnav-chap {
  padding: 12px 10px 4px; font-size: var(--t-1); font-weight: 700; text-transform: uppercase;
  letter-spacing: 2px; color: var(--accent-ink);
}
.secnav-list li:first-child.secnav-chap { padding-top: 4px; }
.secnav-list a {
  display: flex; gap: .7em; padding: 8px 10px; border-radius: var(--radius-sm);
  color: var(--ink-2); text-decoration: none; font-size: var(--t-1); line-height: 1.35;
}
.secnav-list a:hover { background: var(--field); color: var(--ink); }
.secnav-list a[aria-current="true"] { color: var(--ink); font-weight: 600; }
.secnav-list .secnav-n {
  font-family: ui-monospace, "SF Mono", Menlo, Consolas, "Liberation Mono", monospace;
  font-variant-numeric: tabular-nums; color: var(--ink-3); margin: 0;
}
.secnav-list a[aria-current="true"] .secnav-n { color: var(--accent-ink); }

@media print { .secnav { display: none !important; } }
@media (forced-colors: active) {
  .secnav-list a[aria-current="true"] { outline: 2px solid CanvasText; outline-offset: -2px; }
}

/* ---------------------------------------------------------------- the pages cloned from the 2023 site
   Bodies come from tools/clone_legacy.py. The old site set each thought as one tall slide with a
   picture on one side and the words on the other, alternating; that shape is kept, in this site's
   type and colours. Everything here is scoped under .lg so it cannot reach the rest of the site. */
.lg-slide { padding-block: clamp(48px, 9vw, 120px); border-block-end: var(--rule-w) solid var(--rule); }
.lg-slide-grid { display: grid; grid-template-columns: minmax(0, 1fr) minmax(0, 1fr); gap: clamp(28px, 6vw, 96px); align-items: center; }
.lg-flip .lg-fig { order: 2; }
.lg-fig { margin: 0; }
.lg-fig img { display: block; inline-size: 100%; block-size: auto; aspect-ratio: 3 / 2; object-fit: cover; border-radius: var(--radius); box-shadow: var(--shadow); }
/* The drawings are inlined by tools/inline_art.py, which turns every hard-coded colour of the old
   export into one of these roles. So the whole set is recoloured from here, follows the theme
   tokens, and sits on nothing at all — no plate, no request. */
.lg-art .lg-ink {
  --art-ink: var(--ink);
  --art-1: var(--brand-1);
  --art-2: var(--brand-2);
  --art-3: var(--brand-3);
  --art-sun: #F4D13B;
  --art-paper: var(--paper-2);
  --art-mute: var(--ink-3);
  display: block; inline-size: 100%; block-size: auto; max-block-size: 420px;
  fill: var(--art-ink);
  overflow: visible;
}
/* The drawing draws itself when it scrolls into view: every shape carries pathLength="1", so one
   dash from 1 to 0 traces its outline whatever its real length, and the fills fade in behind the
   line. Only with scripting (`.js`), because `.in` is what site.js adds; without it, and for a
   reader who asked for less motion, the drawing is simply there. */
.js .lg-art .lg-ink :is(path, polygon, circle, rect, line, ellipse) {
  stroke: var(--art-ink); stroke-width: 1.5px; vector-effect: non-scaling-stroke;
  stroke-linejoin: round; stroke-linecap: round;
  stroke-dasharray: 1; stroke-dashoffset: 1; fill-opacity: 0;
}
.js .lg-art.in .lg-ink :is(path, polygon, circle, rect, line, ellipse) {
  animation: art-trace .8s var(--ease) forwards, art-fill .6s ease-out forwards;
  animation-delay: calc(var(--i, 0) * 14ms), calc(.35s + var(--i, 0) * 14ms);
}
@keyframes art-trace { to { stroke-dashoffset: 0; } }
@keyframes art-fill { to { fill-opacity: 1; stroke-opacity: 0; } }
[data-motion="reduce"] .lg-art .lg-ink :is(path, polygon, circle, rect, line, ellipse) { animation: none; stroke: none; stroke-dasharray: none; fill-opacity: 1; }
@media (prefers-reduced-motion: reduce) {
  :root:not([data-motion]) .lg-art .lg-ink :is(path, polygon, circle, rect, line, ellipse) { animation: none; stroke: none; stroke-dasharray: none; fill-opacity: 1; }
}
.lg-text h1, .lg-text h2 { font-size: clamp(2rem, 1.1rem + 2.6vw, 3.4rem); line-height: 1.02; margin-block-end: 20px; }
.lg-lead { font-size: var(--t1); line-height: 1.4; color: var(--ink); max-inline-size: 38ch; margin-block-end: 16px; }
.lg-text p:not([class]), .lg-text ul { color: var(--ink-2); max-inline-size: 52ch; }
.lg-ctas { display: flex; flex-wrap: wrap; gap: 12px; margin-block-start: 28px; }

.lg-hero { position: relative; display: grid; align-items: end; min-block-size: min(calc(100svh - var(--bar-h)), 820px); background: #000 center / cover no-repeat; color: #FFFFFF; }
.lg-hero::before { content: ""; position: absolute; inset: 0; background: linear-gradient(to top, rgba(0, 0, 0, .88) 0%, rgba(0, 0, 0, .35) 45%, rgba(0, 0, 0, 0) 70%); }
.lg-hero-in { position: relative; padding-block: clamp(32px, 6vw, 72px); }
.lg-hero h1 { color: #FFFFFF; font-size: clamp(2.2rem, 1.2rem + 3.4vw, 4.4rem); line-height: 1; max-inline-size: 16ch; margin-block-end: 14px; }
.lg-hero .lg-lead { color: rgba(255, 255, 255, .86); }
.lg-hero .objection { color: var(--brand-3); }
/* The deep stop: white on the mid stop (#0052B6) is 3.3:1, short of AA for button-sized text. */
.lg-hero .btn-primary { background: var(--brand-1); color: #FFFFFF; border-color: var(--brand-1); }
[data-theme^="contrast"] .lg-hero .btn-primary { background: var(--btn-bg); color: var(--btn-fg); border-color: var(--btn-bg); }

.lg-features h1 { font-size: clamp(2.2rem, 1.2rem + 3.4vw, 4.4rem); line-height: 1; margin-block-end: clamp(28px, 5vw, 56px); }
.lg-groups { columns: 3 300px; column-gap: 24px; }
.lg-group { break-inside: avoid; margin-block-end: 24px; padding: clamp(18px, 2.5vw, 28px); background: var(--paper-2); border: var(--rule-w) solid var(--rule); border-radius: var(--radius); }
.lg-group h2 { font-size: var(--t1); margin-block-end: 12px; }
.lg-group ul { margin: 0; padding-inline-start: 1.1em; color: var(--ink-2); font-size: var(--t-1); }
.lg-group li + li { margin-block-start: 8px; }

.lg-duos-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 24px; }
.lg-duo { display: block; padding: clamp(24px, 4vw, 48px); border: var(--rule-w) solid var(--rule); border-radius: var(--radius); background: var(--paper-2); color: var(--ink-2); font-size: clamp(1.25rem, 1rem + 1.2vw, 2rem); line-height: 1.15; text-decoration: none; transition: border-color var(--dur) var(--ease), transform var(--dur) var(--ease); }
.lg-duo b { display: block; color: var(--accent-ink); }
.lg-duo:hover { border-color: var(--ink); transform: translateY(-2px); }

.lg-apply-in { max-inline-size: 760px; text-align: center; }
.lg-apply-in h1 { font-size: clamp(2rem, 1.1rem + 2.6vw, 3.4rem); line-height: 1.02; margin-block-end: 20px; }
.lg-form { margin-block-start: 32px; text-align: start; }
.lg-fields { display: grid; grid-template-columns: 1fr 1fr; gap: 16px; margin-block-end: 16px; }
.lg-field { display: block; }
.lg-field span { display: block; font-size: var(--t-1); color: var(--ink-2); margin-block-end: 6px; }
.lg-field input, .lg-field textarea { inline-size: 100%; padding: 12px 14px; font: inherit; color: var(--ink); background: var(--paper-2); border: var(--rule-w) solid var(--ink-3); border-radius: var(--radius-sm); }
.lg-field input:focus-visible, .lg-field textarea:focus-visible { outline: 2px solid var(--accent-ink); outline-offset: 2px; }
.lg-form .lg-ctas { justify-content: center; }

.lg-posts-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(240px, 1fr)); gap: 24px; }
.lg-post-img img { display: block; inline-size: 100%; aspect-ratio: 3 / 2; object-fit: cover; border-radius: var(--radius); margin-block-end: 16px; }
.lg-post h3 { font-size: var(--t1); line-height: 1.2; margin-block: 6px 10px; }
.lg-post h3 a { color: inherit; text-decoration: none; }
.lg-post p:not([class]) { color: var(--ink-2); font-size: var(--t-1); }
.lg-more::after { content: " →"; }

@media (max-width: 860px) {
  .lg-slide-grid, .lg-duos-grid, .lg-fields { grid-template-columns: 1fr; }
  .lg-flip .lg-fig { order: 0; }
  .lg-art .lg-ink { max-block-size: 260px; }
}
@media print { .lg-form, .lg-ctas { display: none; } }
/* A list of links flows down in newspaper columns rather than across a grid, so 7 or 10 items never
   leave an empty cell at the end of a row (§0.13); the columns simply end at different depths by at
   most one line. */
.lg-links { list-style: none; padding: 0; margin: 0 0 24px; columns: 260px 4; column-gap: 24px; }
.lg-links li { border-block-end: var(--rule-w) solid var(--rule); break-inside: avoid; }
.lg-links a { display: block; padding: 12px 0; color: var(--ink); text-decoration: none; }
.lg-links a::after { content: " →"; color: var(--accent-ink); }
.lg-links a:hover { color: var(--accent-ink); }
.lg-links + .section-head { margin-block-start: clamp(32px, 6vw, 64px); }

/* The pager: previous and next in a section that reads like a book (P.READ_ON in data/pages.py). Two
   cards, back on the left and on on the right; each carries the drawing its page opens with, standing
   in a disc of the brand's light stop. On hover or focus the drawing lifts and leans toward the way
   the card goes, the disc swells, and the arrow moves (icons.css: back / nudge). */
.read-on { padding-block: clamp(28px, 4vw, 48px); border-top: var(--rule-w) solid var(--rule); }
.pager-row { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: clamp(12px, 2vw, 24px); }
.pager-card {
  display: flex; align-items: center; gap: clamp(14px, 2vw, 24px); min-width: 0; min-height: 124px;
  padding: 14px clamp(18px, 2.4vw, 32px) 14px 14px; border: var(--rule-w) solid var(--rule); border-radius: var(--radius-tile, 24px);
  background: var(--paper-2); color: var(--ink); text-decoration: none;
  transition: border-color var(--dur, .2s), box-shadow .3s var(--ease), transform .3s var(--ease);
}
.pager-next { flex-direction: row-reverse; padding: 14px 14px 14px clamp(18px, 2.4vw, 32px); text-align: end; }
.pager-card:is(:hover, :focus-visible) { border-color: var(--accent); box-shadow: var(--shadow); transform: translateY(-2px); }
.pager-card:focus-visible { outline: 3px solid var(--accent); outline-offset: 3px; }
.pager-art { position: relative; flex: none; width: clamp(88px, 9vw, 116px); aspect-ratio: 1; }
.pager-art::before {
  content: ""; position: absolute; inset: 8%; border-radius: 50%;
  background: color-mix(in srgb, var(--brand-3) 50%, var(--paper-2)); transition: transform .5s cubic-bezier(.34, 1.56, .64, 1);
}
.pager-art svg { position: relative; display: block; width: 100%; height: auto; transform-origin: 50% 90%; transition: transform .5s cubic-bezier(.34, 1.56, .64, 1); }
.pager-card:is(:hover, :focus-visible) .pager-art::before { transform: scale(1.1); }
.pager-prev:is(:hover, :focus-visible) .pager-art svg { transform: translate(-4%, -7%) rotate(-5deg) scale(1.06); }
.pager-next:is(:hover, :focus-visible) .pager-art svg { transform: translate(4%, -7%) rotate(5deg) scale(1.06); }
.pager-text { display: grid; gap: 6px; min-width: 0; }
.pager-way {
  display: inline-flex; align-items: center; gap: 6px; color: var(--ink-2);
  font-size: var(--t-1); font-weight: 700; letter-spacing: .08em; text-transform: uppercase;
}
.pager-next .pager-way { flex-direction: row-reverse; justify-self: end; }
.pager-way .btn-ico { width: 18px; height: 18px; color: var(--accent-ink); }
.pager-text b { font-size: clamp(1.25rem, 2.1vw, 1.7rem); font-weight: 700; line-height: 1.15; letter-spacing: -.02em; overflow-wrap: anywhere; }
@media (max-width: 640px) {
  .pager-row { grid-template-columns: minmax(0, 1fr); }
  .pager-gap { display: none; }
  .pager-card { min-height: 0; }
  .pager-art { width: 76px; }
}
@media (prefers-reduced-motion: reduce) { .pager-card, .pager-art::before, .pager-art svg { transition: none; } }
[data-motion="reduce"] :is(.pager-card, .pager-art::before, .pager-art svg) { transition: none; }
.nav-menu[data-cols="2"] { --menu-w: 40rem; }
@media print { .read-on { display: none; } }

/* Where a menu opens from. With five menus on the bar a wide one can no longer hang from its own
   summary: from "Services" a 56rem panel runs off the right edge at 1280. A three-column menu is
   therefore anchored to the bar and centred under it, and the last menu opens from its end. Only
   above the drawer breakpoint: in the drawer every menu is static already. */
@media (min-width: 1201px) {
  .nav-group[data-anchor="bar"] { position: static; }
  .nav-group[data-anchor="bar"] > .nav-menu { inset-inline: 0; margin-inline: auto; top: calc(100% + 6px); }
  .nav-group[data-anchor="end"] > .nav-menu { inset-inline-start: auto; inset-inline-end: 0; }
}

/* ==== Video (docs/ECOSYSTEM-GUIDELINES.md §7A) ==========================================
   Five slots share one frame: `.vframe` is a 16:9 box with 22 px corners that holds a native
   <video> (preload="none" + poster, so nothing downloads until it plays) and our own round
   play/pause button. assets/video.js wires the behaviour; without it the poster simply shows. */
:root { --radius-tile: 22px; --v-scrim: rgba(11, 12, 16, .72); }
.vframe { position: relative; aspect-ratio: 16 / 9; margin: 0; overflow: hidden; border-radius: var(--radius-tile); background: var(--field); box-shadow: var(--shadow); isolation: isolate; }
.vframe video { display: block; width: 100%; height: 100%; object-fit: cover; }
/* The button's icon follows the video's state (data-state on the frame, set by video.js): a
   triangle when paused, two bars when playing, a curved arrow when an ad has played to its end. */
.vbtn { position: absolute; inset-inline-start: 14px; inset-block-end: 14px; z-index: 2; display: grid; place-items: center; width: 44px; height: 44px; padding: 0; border: 0; border-radius: var(--radius-pill); background: var(--v-scrim); color: #fff; cursor: pointer; transition: transform var(--dur) var(--ease), background-color var(--dur) var(--ease); }
.vbtn:hover { transform: scale(1.06); background: rgba(11, 12, 16, .88); }
.vbtn::before { content: ""; width: 18px; height: 18px; background: currentColor; -webkit-mask: var(--v-icon) center / contain no-repeat; mask: var(--v-icon) center / contain no-repeat; }
.vframe { --v-icon: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cpath d='M7 4.5v15l13-7.5z'/%3E%3C/svg%3E"); }
.vframe[data-state="playing"] { --v-icon: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cpath d='M6 4h4v16H6zM14 4h4v16h-4z'/%3E%3C/svg%3E"); }
.vframe[data-state="ended"] { --v-icon: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='black' stroke-width='2.6' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M4 12a8 8 0 1 0 2.3-5.6M4 4v4h4'/%3E%3C/svg%3E"); }
/* The big centred button: for frames whose whole purpose is "press to watch" (the hero poster and
   the how-it-works steps before they are started). It hides once the video has started. */
.vbtn-big { inset: 50% auto auto 50%; width: 76px; height: 76px; transform: translate(-50%, -50%); }
.vbtn-big:hover { transform: translate(-50%, -50%) scale(1.06); }
.vbtn-big::before { width: 28px; height: 28px; }
.vframe[data-started] .vbtn-big { opacity: 0; pointer-events: none; }
.vmute { position: absolute; inset-inline-end: 14px; inset-block-end: 14px; z-index: 2; padding: 10px 16px; border: 0; border-radius: var(--radius-pill); background: var(--v-scrim); color: #fff; font: inherit; font-size: var(--t-1); font-weight: 700; cursor: pointer; }

/* 1 · Hero: a text button beside the primary CTA, and the poster of the story as the hero image. */
.btn-play { display: inline-flex; align-items: center; gap: 10px; }
.btn-play::before { content: ""; width: 26px; height: 26px; flex: none; border-radius: 50%; background: var(--ink) url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='white'%3E%3Cpath d='M9 7v10l8-5z'/%3E%3C/svg%3E") center / 18px no-repeat; }
[data-theme$="dark"] .btn-play::before { background-color: var(--accent); }

/* The story player: a native <dialog> in the top layer, the video on the left and the ten steps on
   the right; on phones the list drops under the video. Full native controls here, because the
   visitor asked for the film. */
.vplayer { width: min(1180px, 100% - 32px); max-height: calc(100dvh - 32px); padding: 0; border: 0; border-radius: var(--radius-tile); background: #0B0C10; color: #E8EAEE; box-shadow: 0 30px 80px rgba(0, 0, 0, .5); }
.vplayer::backdrop { background: rgba(5, 6, 9, .78); }
.vplayer-grid { display: grid; grid-template-columns: minmax(0, 1fr) 300px; }
.vplayer video { display: block; width: 100%; height: 100%; min-height: 0; aspect-ratio: 16 / 9; object-fit: contain; background: #000; }
@media (min-width: 801px) { .vplayer-grid > video { aspect-ratio: auto; } }
.vplayer-side { display: flex; flex-direction: column; min-height: 0; padding: 20px; border-inline-start: 1px solid #262A36; }
.vplayer-side h2 { margin: 0 44px 12px 0; font-size: var(--t1); color: #fff; }
.vplayer ol { flex: 1; margin: 0; padding: 0; overflow-y: auto; list-style: none; counter-reset: vs; }
.vplayer li + li { margin-top: 4px; }
.vplayer li button { display: grid; grid-template-columns: 2.2em 1fr; width: 100%; padding: 10px 12px; border: 0; border-radius: 12px; background: transparent; color: #B4B8C6; font: inherit; text-align: start; cursor: pointer; }
.vplayer li button:hover { background: #1B1E27; color: #fff; }
.vplayer li button[aria-current="true"] { background: #1B1E27; color: #fff; font-weight: 700; }
.vplayer li button b { color: #008FFF; }
.vplayer-close { position: absolute; inset-block-start: 12px; inset-inline-end: 12px; z-index: 3; width: 40px; height: 40px; border: 0; border-radius: 50%; background: #1B1E27; color: #fff; font-size: 22px; line-height: 1; cursor: pointer; }
@media (max-width: 800px) {
  .vplayer-grid { grid-template-columns: 1fr; }
  .vplayer-side { border-inline-start: 0; border-block-start: 1px solid #262A36; max-height: 42dvh; }
}

/* 2 · The story, told by scroll (§6.2 pin and swap). Without the scene class (no JS, a phone,
   reduced motion) it is ten stacked cards, each with its own poster and play button. With it, the
   section becomes a tall track: the stage sticks under the bar, the step list on the left follows
   the reader's scroll, and the ten frames share one cell on the right, cross-fading. */
.story-steps { display: grid; gap: 28px; margin: 32px 0 0; padding: 0; list-style: none; }
.story-step { display: grid; gap: 14px; }
.story-step h3 { margin: 0; font-size: var(--t2); line-height: 1.15; }
.story-step h3 span { display: block; margin-bottom: 4px; font-size: var(--t-1); letter-spacing: .12em; text-transform: uppercase; color: var(--accent-ink); }
.story-step p { margin: 0; color: var(--ink-2); }
.story.is-scene { height: calc(var(--steps, 10) * 34vh + 100vh); padding-block: 0; }
.story.is-scene .story-stage { position: sticky; top: var(--bar-h); display: grid; grid-template-columns: minmax(280px, 5fr) 7fr; gap: clamp(24px, 4vw, 56px); align-items: center; height: calc(100vh - var(--bar-h)); }
.story.is-scene .story-steps { position: relative; gap: 0; margin-top: 20px; }
.story.is-scene .story-step { display: block; }
.story.is-scene .story-step button.story-go { display: block; width: 100%; padding: 6px 0 6px 16px; border: 0; border-inline-start: 3px solid var(--rule); background: none; color: var(--ink-3); font: inherit; font-size: var(--t0); font-weight: 600; text-align: start; cursor: pointer; transition: color var(--dur) var(--ease), border-color var(--dur) var(--ease); }
.story.is-scene .story-step.is-active button.story-go { color: var(--ink); border-color: var(--accent); }
.story.is-scene .story-step h3, .story.is-scene .story-step p { display: none; }
.story.is-scene .story-step.is-active p { display: block; padding: 4px 0 12px 19px; font-size: var(--t-1); }
.story.is-scene .story-frames { display: grid; }
.story.is-scene .story-frames > .vframe { grid-area: 1 / 1; opacity: 0; transition: opacity .45s var(--ease); pointer-events: none; }
.story.is-scene .story-frames > .vframe.is-active { opacity: 1; pointer-events: auto; }
.story-go, .story-frames { display: none; }
.story.is-scene .story-go { display: block; }
.story .story-step > .vframe { order: 3; }
.story.is-scene .story-step > .vframe { display: none; }

/* 3 · The products: a bento of ad tiles. Each tile is one link, stretched over the tile by its
   ::after, with the play button raised above it so a phone can start the ad without leaving. */
.ad-bento { display: grid; grid-template-columns: repeat(12, minmax(0, 1fr)); gap: 20px; margin-top: 32px; }
.ad-tile { position: relative; grid-column: span 4; display: grid; gap: 12px; align-content: start; padding: 14px 14px 22px; border-radius: var(--radius-tile); background: rgba(1, 1, 1, .001); box-shadow: var(--shadow); transition: transform var(--dur) var(--ease), background-color var(--dur) var(--ease); }
.ad-tile:nth-child(-n+2) { grid-column: span 6; }
.ad-tile:hover, .ad-tile:focus-within { transform: translateY(-4px); background: rgba(1, 1, 1, .031); }
.ad-tile .vframe { border-radius: 14px; box-shadow: none; }
/* Some brand hues are pale (dash-b, livee, iKonekt): mixed toward black they read as small text on paper. */
.ad-tile b { padding-inline: 8px; font-size: var(--t-1); letter-spacing: .08em; text-transform: uppercase; color: color-mix(in srgb, var(--hue, var(--accent-ink)) 50%, #000); }
/* On the dark themes a brand hue is too dark to read as small text; mixed halfway to white it passes. */
[data-theme$="dark"] .ad-tile b { color: color-mix(in srgb, var(--hue, var(--accent-ink)) 55%, #fff); }
@media (prefers-color-scheme: dark) { :root:not([data-theme]) .ad-tile b { color: color-mix(in srgb, var(--hue, var(--accent-ink)) 55%, #fff); } }
.ad-tile a { padding-inline: 8px; color: var(--ink); font-size: var(--t1); font-weight: 600; line-height: 1.25; text-decoration: none; }
.ad-tile a::after { content: ""; position: absolute; inset: 0; border-radius: inherit; }
.ad-tile a:focus-visible { outline: none; }
.ad-tile:has(a:focus-visible) { outline: 3px solid var(--accent-ink); outline-offset: 3px; }
@media (max-width: 900px) { .ad-tile, .ad-tile:nth-child(-n+2) { grid-column: span 6; } }
@media (max-width: 600px) { .ad-bento { gap: 12px; } .ad-tile, .ad-tile:nth-child(-n+2) { grid-column: span 12; } }

/* 4 · The footer shelf with ads: the same tile as today, the mark swapped for the ad's poster. It
   plays on hover or keyboard focus only; a tap still goes straight to the product's site. */
.foot-family .vframe { grid-row: 3; align-self: center; width: 100%; margin: 20px 0; border-radius: 14px; box-shadow: none; }

/* 5 · How it works: one chapter per step, text and film side by side, the side swapping each step. */
.film-step { padding-block: clamp(40px, 6vw, 72px); }
.film-step:nth-child(even) { background: var(--paper-2); }
.film-step .wrap { display: grid; grid-template-columns: 5fr 7fr; gap: clamp(24px, 4vw, 56px); align-items: center; }
.film-step:nth-child(even) .wrap { grid-template-columns: 7fr 5fr; }
.film-step:nth-child(even) .wrap > :first-child { order: 2; }
.film-step h3 { margin: 0 0 14px; font-size: var(--t4); line-height: 1.05; letter-spacing: -.02em; }
.film-step .lead { font-weight: 600; }
@media (max-width: 800px) {
  .film-step .wrap, .film-step:nth-child(even) .wrap { grid-template-columns: 1fr; }
  .film-step:nth-child(even) .wrap > :first-child { order: 0; }
}
/* The internal templates page only (content/en/video-templates.html). */
.vt-note { margin-top: 8px; font-size: var(--t-1); color: var(--ink-3); }
.vt-hero-frame { align-self: center; }

/* The bug-report panel on the contact page (§0.12): shown only when a footer's "Report a bug" brought
   the visitor here, and lists every fact the prepared message will carry. */
.bug-report { margin-block: 8px 40px; padding: clamp(22px, 3vw, 34px); border-radius: var(--radius); background: var(--paper-2); border: var(--rule-w) solid var(--rule); }
.bug-report h2 { margin-block-start: 0; }
.bug-facts { display: grid; grid-template-columns: max-content minmax(0, 1fr); gap: 6px 18px; margin: 18px 0; font-size: var(--t-1); }
.bug-facts dt { font-weight: 600; color: var(--ink); }
.bug-facts dd { margin: 0; color: var(--ink-2); overflow-wrap: anywhere; }
.bug-note { font-size: var(--t-1); color: var(--ink-3); }

/* The visible breadcrumb above every page but the home (§0.9); the same trail is the page's
   BreadcrumbList. It is the quietest line on the page: small, grey, and gone from print. */
.crumbs-bar { padding-block: 14px 0; }
.crumbs-bar li[aria-current] { color: var(--ink-2); }
@media print { .crumbs-bar { display: none; } }

/* §0.2 icons on the script-built and video buttons: the section chip's chevron, the story steps'
   play mark and the frames' sound mark. Each moves on hover or focus and holds still under reduced
   motion, like every other button icon. */
.secnav-chip { gap: 6px; }
.secnav-ico { width: 1em; height: 1em; flex: none; fill: none; stroke: currentColor; stroke-width: 2.2; stroke-linecap: round; stroke-linejoin: round; transition: translate .3s var(--u-spring), rotate .35s var(--ease); }
.secnav-chip:is(:hover, :focus-visible) .secnav-ico { translate: 0 2px; }
.secnav-chip[aria-expanded="true"] .secnav-ico { rotate: 180deg; }
.vmute { display: inline-flex; align-items: center; gap: 8px; }
.story-go { display: inline-flex; align-items: center; gap: 8px; }
@media (prefers-reduced-motion: reduce) { .secnav-ico { transition: none; } }
[data-motion="reduce"] .secnav-ico { transition: none; }

/* Static sub menu = the castomo.com/demos filter pills, copied from its computed styles (Maciej,
   2026-09-24): 12px/900 caps-free label, 1px tracking, 10px 30px padding, 22px pill, soft grey rest,
   brand gradient for the current page (FABLS: dark green, so white passes AA across it). The grey is mixed from the page's own ink and paper, so it
   follows every theme; high contrast swaps the gradient for solid ink, because white on the light
   end of the gradient is under 4.5:1 (§0.3). */
:root {
  --pill-bg: color-mix(in srgb, var(--ink) 4.5%, var(--paper));
  --pill-bg-hover: color-mix(in srgb, var(--ink) 9%, var(--paper));
  --pill-on: linear-gradient(-45deg, #003C87, #0052B6, #003C87);
  --pill-on-ink: #fff;
}
.subnav ul { gap: 8px; padding-block: 10px; }
.subnav li > a:not([class]) {
  padding: 10px 30px; border: 1px solid var(--paper); border-radius: 22px;
  background: var(--pill-bg); color: var(--ink);
  font-size: 12px; font-weight: 900; line-height: 1.3; letter-spacing: 1px;
  transition: background-color .25s ease-in-out, color .25s ease-in-out;
}
.subnav li > a:not([class]):hover { background-color: var(--pill-bg-hover); color: var(--ink); }
.subnav li > a:not([class])[aria-current="page"] { background: var(--pill-on); border-color: transparent; color: var(--pill-on-ink); font-weight: 900; transition-duration: .7s; }
.subnav li > a:not([class]):focus-visible { border-radius: 22px; outline-offset: 2px; }
[data-theme^="contrast"] .subnav li > a:not([class])[aria-current="page"] { background: var(--ink); color: var(--paper); }
@media (prefers-contrast: more) { :root:not([data-theme]) .subnav li > a:not([class])[aria-current="page"] { background: var(--ink); color: var(--paper); } }
@media (forced-colors: active) { .subnav li > a:not([class])[aria-current="page"] { forced-color-adjust: none; background: Highlight; color: HighlightText; } }

/* Breadcrumbs: smaller, with air above and below, and no resting underline; the line only grows on
   hover and focus (Maciej, 2026-09-24). */
.crumbs { font-size: 13px; }
.crumbs-bar { padding-block: 18px 6px; }
.crumbs a:not([class]) { background-size: 0% var(--u-thick); }
.crumbs a:not([class]):is(:hover, :focus-visible, :active) { background-size: 100% var(--u-thick); }

/* Header polish (Maciej, 2026-09-24, guidelines §0.21).
   The logo is the icon alone: no plate, no hairline frame. The rect stays in the markup (it sets the
   viewBox's ground for the kit's cut) but paints nothing. */
.brand svg > rect, :is([data-theme="dark"], [data-theme="contrast-dark"]) .brand svg > rect { fill: none; stroke: none; }
@media (prefers-color-scheme: dark) { :root:not([data-theme]) .brand svg > rect { fill: none; stroke: none; } }

/* Idle logo: site.js sets data-idle on the header mark every 5–7 s, picking one of these cuts at
   random (never the same twice running); each plays once and the mark rests until the next. The
   global reduced-motion rules remove them, and site.js stops scheduling under reduced motion. */
.bar .brand svg[data-idle] > g > * { animation-duration: .9s; animation-timing-function: cubic-bezier(.2, .7, .3, 1); animation-fill-mode: both; }
.bar .brand svg[data-idle="pop"] > g > * { animation-name: mk-pop; }
.bar .brand svg[data-idle="spin"] > g > * { animation-name: mk-spin; }
.bar .brand svg[data-idle="bounce"] > g > * { animation-name: mk-bounce; }
.bar .brand svg[data-idle="wiggle"] > g > * { animation-name: mk-wiggle; }
.bar .brand svg[data-idle="flip"] > g > * { animation-name: mk-flip; }
@keyframes mk-spin { 0% { transform: rotate(0); } 80% { transform: rotate(372deg); } 100% { transform: rotate(360deg); } }
@keyframes mk-bounce { 0%, 100% { transform: translateY(0); } 30% { transform: translateY(-18%); } 55% { transform: translateY(0) scale(1.06, .94); } 75% { transform: translateY(-6%); } }
@keyframes mk-wiggle { 0%, 100% { transform: rotate(0); } 20% { transform: rotate(-12deg); } 40% { transform: rotate(10deg); } 60% { transform: rotate(-6deg); } 80% { transform: rotate(3deg); } }
@keyframes mk-flip { 0% { transform: perspective(80px) rotateY(0); } 100% { transform: perspective(80px) rotateY(360deg); } }

/* Main menu: the hovered or open item is the brand accent with white text. In high-contrast dark the
   accent is a light orange, so its text turns dark to keep 7:1. The section-you-are-in mark keeps
   its own colour until hovered. The menu row is nudged 4px down so its baseline meets the wordmark's
   (menu 36px vs wordmark 40px, measured at 1280px); padding, not transform, so dropdowns follow. */
:root { --on-accent: #fff; }
[data-theme="contrast-dark"] { --on-accent: #000; }
.nav-top:hover, .nav-group > summary:hover, .nav-group[open] > summary,
.nav-group[data-current] > summary:hover, .nav-group[data-current][open] > summary { color: var(--on-accent); background: var(--accent); }
.nav-group > summary:hover svg, .nav-group[open] > summary svg { color: inherit; }
.nav-links { padding-block-start: 8px; }
@media (max-width: 1200px) { .nav-links { padding-block-start: 0; } }

/* Static sub menu: the current page is the flat accent (no gradient); every pill is 5px 15px with
   0.3px tracking. */
.subnav li > a:not([class]) { padding: 5px 15px; letter-spacing: .3px; }
.subnav li > a:not([class])[aria-current="page"] { background: var(--accent); color: var(--on-accent); }

.crumbs { font-size: 11px; }

/* ALTERNATIVE shelf style, opt-in: add class "shelf-alt" to a .foot-family section. Everything down to
   the end of this block is scoped to it; the default shelf above is the original. Shelf tiles, "mark is the hero" (Maciej, 2026-09-24; tile-lab #3 without the drawings, so the same
   tile works on every ecosystem site whatever its illustration kit). Visuals only: the scrolling row,
   snap, arrows and markup above are untouched. A white card holds a tinted window (::after, the
   brand hue at 10%); the mark sits in it, large and frameless, with its faint twin (.shelf-bg) filling
   the window behind it; name, line and domain pill follow below. The window is 188px tall; the
   background mark is the window's own size and clipped to its corners, its paths scaled up inside
   the SVG's viewport (transform-box: view-box keeps a multi-part mark in one piece). */
.foot-family.shelf-alt a {
  isolation: isolate; grid-template-rows: 188px auto auto 1fr; justify-items: start; text-align: start;
  padding: 12px 12px 20px; background: var(--paper); border-color: var(--rule);
  transition: transform .35s cubic-bezier(.3, 1.45, .5, 1), box-shadow .35s var(--ease);
}
.foot-family.shelf-alt a:hover, .foot-family.shelf-alt a:focus-visible { background: var(--paper); border-color: var(--rule); box-shadow: 0 26px 50px -24px rgba(11, 12, 16, .45); }
.foot-family.shelf-alt a::after { content: ""; position: absolute; inset: 12px 12px auto; height: 188px; border-radius: calc(var(--radius) * 1.1); background: color-mix(in srgb, var(--hue, var(--accent)) 10%, var(--paper-2)); z-index: -1; pointer-events: none; }
.foot-family.shelf-alt a::before { z-index: 2; }
.foot-family.shelf-alt a > svg:not(.shelf-bg) {
  grid-row: 1; align-self: center; justify-self: center; width: 96px; margin: 0; z-index: 1;
  transition: transform .5s cubic-bezier(.3, 1.7, .5, 1);
}
.foot-family.shelf-alt a:hover > svg:not(.shelf-bg), .foot-family.shelf-alt a:focus-visible > svg:not(.shelf-bg) { transform: translateY(-10px) rotate(-6deg) scale(1.08); }
.foot-family.shelf-alt .shelf-bg {
  grid-area: auto; inset: 12px auto auto 12px; width: calc(100% - 24px); height: 188px; aspect-ratio: auto; overflow: hidden;
  border-radius: calc(var(--radius) * 1.1); opacity: .1; transform: none; z-index: 0;
}
.foot-family.shelf-alt .shelf-bg > * { transform-box: view-box; transform-origin: 50% 50%; transform: translate(26%, 20%) scale(1.9) rotate(-14deg); transition: transform .6s var(--ease); }
.foot-family.shelf-alt a:hover .shelf-bg, .foot-family.shelf-alt a:focus-visible .shelf-bg { opacity: .16; transform: none; }
.foot-family.shelf-alt a:hover .shelf-bg > *, .foot-family.shelf-alt a:focus-visible .shelf-bg > * { transform: translate(22%, 16%) scale(2) rotate(-6deg); }
.foot-family.shelf-alt b { grid-row: 2; justify-self: start; margin: 16px 6px 0; font-size: 20px; font-weight: 700; letter-spacing: -.02em; text-transform: none; }
.foot-family.shelf-alt span { grid-row: 3; margin: 4px 6px 0; font-size: var(--t0); font-weight: 400; line-height: 1.5; letter-spacing: 0; color: var(--ink-2); }
.foot-family.shelf-alt small { grid-row: 4; align-self: end; justify-self: start; margin: 16px 6px 0; }
@media (max-width: 600px) { .foot-family.shelf-alt a { min-height: 0; grid-template-rows: 160px auto auto 1fr; } .foot-family.shelf-alt a::after, .foot-family.shelf-alt .shelf-bg { height: 160px !important; } }
@media (prefers-reduced-motion: reduce) { .foot-family.shelf-alt a > svg:not(.shelf-bg), .foot-family.shelf-alt .shelf-bg > * { transition: none; } }
/* Round 2 (Maciej, 2026-09-24): white card, centred text and pill, 28px foot padding, and the mark
   pops OUT of the tile on hover. The scrolling row clips vertically too (overflow-x forces it), so
   the row gets 64px of headroom above the cards, handed back with a negative margin so the layout
   does not move. The card itself stops clipping; the faint mark clips itself (overflow: hidden). */
:root { --tile-bg: #fff; }
:is([data-theme="dark"], [data-theme="contrast-dark"]) { --tile-bg: var(--paper-2); }
@media (prefers-color-scheme: dark) { :root:not([data-theme]) { --tile-bg: var(--paper-2); } }
.foot-family.shelf-alt ul { padding-top: 76px; margin-top: -64px; }
.foot-family.shelf-alt a, .foot-family.shelf-alt a:hover, .foot-family.shelf-alt a:focus-visible { background: var(--tile-bg); }
.foot-family.shelf-alt a { overflow: visible; justify-items: center; text-align: center; }
/* The class-less-link reset (§0.11) zeroes padding-block-end at 0,2,1; this beats it. */
.foot-family.shelf-alt li > a:not([class]) { padding-block-end: 28px; }
.foot-family.shelf-alt a::before { border-radius: calc(var(--radius) * 1.5) calc(var(--radius) * 1.5) 0 0; }
.foot-family.shelf-alt b, .foot-family.shelf-alt small { justify-self: center; }
.foot-family.shelf-alt span { justify-self: center; text-wrap: balance; }
.foot-family.shelf-alt a:hover > svg:not(.shelf-bg), .foot-family.shelf-alt a:focus-visible > svg:not(.shelf-bg) {
  transform: translateY(-96px) scale(1.18) rotate(-6deg); filter: drop-shadow(0 14px 16px rgba(11, 12, 16, .22));
}
/* Round 3 (Maciej, 2026-09-24): the window rests grey (and the faint mark greyscale) and takes the
   brand colour on hover; the popped mark grows to three times its size and paints above the accent
   line (z-index 3 over the ::before's 2). Three times the 96px mark is 288px, so the row's headroom
   grows to 112px to keep the popped mark whole. */
.foot-family.shelf-alt ul { padding-top: 124px; margin-top: -112px; }
.foot-family.shelf-alt a::after { background: color-mix(in srgb, var(--ink) 5%, var(--tile-bg)); transition: background-color .35s var(--ease); }
.foot-family.shelf-alt a:hover::after, .foot-family.shelf-alt a:focus-visible::after { background: color-mix(in srgb, var(--hue, var(--accent)) 12%, var(--tile-bg)); }
.foot-family.shelf-alt .shelf-bg { filter: grayscale(1); transition: filter .35s var(--ease), opacity var(--dur) var(--ease); }
.foot-family.shelf-alt a:hover .shelf-bg, .foot-family.shelf-alt a:focus-visible .shelf-bg { filter: none; }
.foot-family.shelf-alt a:hover > svg:not(.shelf-bg), .foot-family.shelf-alt a:focus-visible > svg:not(.shelf-bg) {
  z-index: 3; transform: translateY(-44px) scale(3) rotate(-6deg); filter: drop-shadow(0 6px 6px rgba(11, 12, 16, .18));
}
/* The accent line stops 10px short of each side (Maciej, 2026-09-24). */
.foot-family.shelf-alt a::before { left: 10px; right: 10px; }
/* Hover shadow: smaller, and the row gets 28px more room below (handed back by a negative margin) so
   the scroller, which clips vertically, never cuts it off. */
.foot-family.shelf-alt ul { padding-bottom: 56px; margin-bottom: -28px; }
.foot-family.shelf-alt a:hover, .foot-family.shelf-alt a:focus-visible { box-shadow: 0 16px 28px -18px rgba(11, 12, 16, .4); }

/* Original shelf: the class-less-link reset (§0.11, 0,2,1) zeroed the tile's padding-block-end, gluing
   the domain pill to the bottom edge. This restores the tile's own padding (Maciej, 2026-09-24). */
.foot-family li > a:not([class]) { padding-block-end: clamp(24px, 3vw, 34px); }

/* ==== Site header: pill + bubble (header lab #13, adopted 2026-09-24; ECOSYSTEM-GUIDELINES §0.26) ====
   One header on every page. Resting: a full-width bar, logo left, bold menu on the true centre, tools
   and the "Start now!" CTA right; the sub menu and the breadcrumbs centred, with no strip of their
   own. Scrolled (desktop): the bar gathers into a floating pill, the sub menu into a second pill.
   Submenus open in one shared speech bubble that glides between sections, an Andy scene leaping in.
   Behaviour lives in assets/header.js; the no-JS page keeps the plain bar and <details> menus. */

/* §0.22 CTA: accent pill, one moving icon, attention pulse on load + every ~10 s. */
.bar-cta.btn{background:var(--accent);color:var(--on-accent,#fff);border-color:transparent;box-shadow:0 6px 18px -6px color-mix(in srgb,var(--accent) 70%,transparent);transition:transform .25s cubic-bezier(.34,1.56,.64,1),box-shadow .25s}
.bar-cta.btn:hover,.bar-cta.btn:focus-visible{transform:translateY(-2px);box-shadow:0 10px 24px -8px color-mix(in srgb,var(--accent) 80%,transparent)}
.bar-cta.btn.cta-ping{animation:cta-ping 1.1s cubic-bezier(.2,.7,.3,1)}
.bar-cta.btn.cta-ping svg{animation:hdr-nudge 1.1s ease-in-out}
/* A signed-in visitor's button carries their name (assets/account-hint.js). It is whatever they
   typed at sign-up, so it is clamped: an unbroken 40-character name must not widen the bar at
   desktop or push the drawer's button past a 375px screen. The icon beside it keeps its size. */
.bar-cta.is-signed-in .cta-name,.drawer-cta.is-signed-in .cta-name{min-width:0;max-width:16ch;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
@keyframes cta-ping{0%{box-shadow:0 0 0 0 color-mix(in srgb,var(--accent) 55%,transparent)}70%{box-shadow:0 0 0 12px transparent}100%{box-shadow:0 6px 18px -6px color-mix(in srgb,var(--accent) 70%,transparent)}}
@keyframes hdr-nudge{0%,100%{transform:none}30%{transform:translateX(4px)}55%{transform:translateX(0)}75%{transform:translateX(3px)}}

/* #11: flags, rocket CTA, prompter, floating controls (back-to-top left, chat right, tools bottom-centre when no room) */
.flag{width:20px;height:14px;border-radius:2px;flex:none;box-shadow:0 0 0 1px var(--rule)}
.lang-btn{display:inline-flex;align-items:center;gap:6px}
.lang-menu a{display:flex!important;align-items:center;gap:10px}
.bar-end{gap:8px}
.bar-end .lang{order:9}.bar-end .bar-cta{order:10}
.bar-tools{display:inline-flex;align-items:center;gap:2px;border:1px solid var(--rule);border-radius:99px;padding:2px}
.bar-tools>*{border:0!important}
body.tools-dock .bar-tools{position:fixed;left:50%;transform:translateX(-50%);bottom:max(16px,env(safe-area-inset-bottom));z-index:70;background:var(--paper);box-shadow:0 10px 30px -12px rgba(0,0,0,.35)}
body.tools-dock .bar-tools .pop{position:fixed!important;left:50%!important;right:auto!important;top:auto!important;bottom:calc(max(16px,env(safe-area-inset-bottom)) + 56px)!important;transform:translateX(-50%);max-height:70vh;overflow:auto;width:min(360px,calc(100vw - 32px))}
.totop{inset-inline-end:auto!important;inset-inline-start:20px!important}
.bar-cta .rkt{width:18px;height:18px;margin-inline-start:8px;transition:transform .5s cubic-bezier(.34,1.56,.64,1)}
.bar-cta:hover .rkt,.bar-cta:focus-visible .rkt,.bar-cta.cta-ping .rkt{animation:rkt-fly 1.1s cubic-bezier(.3,.7,.3,1)}
.bar-cta.cta-ping svg:not(.rkt){animation:none}
@keyframes rkt-fly{0%{transform:none;opacity:1}40%{transform:translate(14px,-14px) rotate(8deg);opacity:0}41%{transform:translate(-12px,12px);opacity:0}100%{transform:none;opacity:1}}
.prompter[hidden]{display:none}
.prompter{position:fixed;inset:0;z-index:100;display:grid;place-items:start center;padding:10vh 16px 16px;background:color-mix(in srgb,var(--ink) 35%,transparent)}
.prompter-box{position:relative;width:min(680px,100%);max-height:80vh;overflow:auto;background:var(--paper);color:var(--ink);border-radius:22px;padding:24px;box-shadow:0 30px 80px -30px rgba(0,0,0,.5)}
.prompter-box h2{margin:0 0 6px;font-size:24px}.prompter-box p{margin:0 0 14px;color:var(--ink-2)}
.prompter textarea{width:100%;min-height:96px;border:1px solid var(--rule);border-radius:14px;padding:12px;font:inherit;background:var(--paper-2);color:var(--ink);resize:vertical}
.prompter-row{display:flex;gap:8px;justify-content:space-between;align-items:center;margin-top:10px;flex-wrap:wrap}
.prompter ol{list-style:none;margin:16px 0 0;padding:0;display:grid;gap:8px}
.prompter li a{display:grid;gap:2px;padding:12px 14px;border-radius:14px;background:var(--pill-bg);color:var(--ink)}
.prompter li b{font-weight:700}.prompter li span{font-size:14px;color:var(--ink-2)}
.prompter .prompter-x{position:absolute;right:14px;top:14px}
/* The name stays beside the mark on a phone (it used to hide under 420 px, leaving a lone icon): the
   pair just tightens, and a very narrow screen (under 350 px) sets the name one step smaller still. */
@media(max-width:420px){.bar .brand{gap:7px;font-size:18px}.bar .brand svg{width:28px;height:28px}}
@media(max-width:350px){.bar .brand{gap:5px;font-size:16px}.bar .brand svg{width:24px;height:24px}}
.prompter-note{font-size:12px!important;color:var(--ink-3)!important;margin-top:12px!important}

/* ── #12 morphing pill ─────────────────────────────────────────────────────────────────────────
   The bar keeps its size forever; a background layer is clipped to insets that grow with scroll,
   so it gathers from a full-width bar into a floating pill hugging logo, menu and tools. The logo
   and menu slide right and the tools slide left by the same amount (transform only), so nothing
   below ever re-lays out. */
@media (min-width: 1201px) {
  .bar { background: transparent; border-bottom-color: transparent; }
  .bar-in { position: relative; z-index: 2; }
  .pill-glow { position: absolute; inset: 0; z-index: 0; pointer-events: none;
    filter: drop-shadow(0 12px 24px rgb(11 12 16 / calc(var(--p, 0) * .18))); }
  /* See-through at rest, 99% --paper-2 as it gathers into the pill; no rule under it at any point. */
  .pill { position: absolute; inset: 0; background: color-mix(in srgb, var(--paper-2) calc(var(--p, 0) * 99%), transparent);
    clip-path: inset(var(--iy, 0px) var(--ixr, 0px) var(--iy, 0px) var(--ixl, 0px) round calc(var(--p, 0) * 999px)); }
  .bar-in > .brand, .bar-in .nav-links { transform: translateX(var(--shift-l, 0px)); }
  .bar-in .bar-end { transform: translateX(calc(-1 * var(--shift-r, 0px))); }
  .bar[data-layout="centre"] .bar-in .nav-links { transform: none; }

  /* ── #12 bubble submenu ──────────────────────────────────────────────────────────────────────
     Each section's own <details> panel stays where it is (links, focus order, no-JS all intact)
     but loses its box; one shared bubble behind them glides and resizes to whichever is open, and
     its tail slides to point at the open item. Switching sections morphs instead of blinking. */
  .nav-menu { background: transparent; border-color: transparent; box-shadow: none; animation: none;
    top: calc(100% + 40px); --menu-w: 50rem; padding: 16px 16px 16px 196px; min-height: 236px; }
  .nav-menu[data-cols="3"] { --menu-w: 66rem; }
  .nav-menu::before { top: -42px; height: 42px; }
  .bubble { position: absolute; left: 0; top: 0; z-index: 1; width: var(--w, 0px); height: var(--h, 0px);
    transform: translate(var(--x, 0px), var(--y, 0px)) scale(.94); transform-origin: var(--tail, 50%) 0;
    background: var(--paper-2); border: var(--rule-w) solid var(--rule); border-radius: 26px;
    box-shadow: 0 30px 60px -30px rgb(11 12 16 / .45), 0 2px 6px rgb(11 12 16 / .06);
    opacity: 0; pointer-events: none;
    transition: transform .42s cubic-bezier(.3, 1.3, .5, 1), width .42s cubic-bezier(.3, 1.3, .5, 1),
                height .42s cubic-bezier(.3, 1.3, .5, 1), opacity .18s ease; }
  .bubble.on { opacity: 1; transform: translate(var(--x, 0px), var(--y, 0px)); }
  .bubble.instant, .bubble.instant::before { transition: none; }
  /* The tail: a rotated square sharing the bubble's fill, so the pair reads as one speech bubble. */
  .bubble::before { content: ""; position: absolute; top: -12px; left: calc(var(--tail, 50%) - 12px); width: 24px; height: 24px;
    background: inherit; border-left: var(--rule-w) solid var(--rule); border-top: var(--rule-w) solid var(--rule);
    border-top-left-radius: 5px; transform: rotate(45deg); transition: left .42s cubic-bezier(.3, 1.3, .5, 1); }
  /* The character's stage: a tinted well on the bubble's left, the Andy scene leaping in. */
  .bubble::after { content: ""; position: absolute; left: 12px; top: 12px; bottom: 12px; width: 172px; border-radius: 18px;
    background: color-mix(in srgb, var(--accent) 9%, var(--paper-2)); }
  .nav-art { position: absolute; left: 18px; bottom: 18px; width: 160px; pointer-events: none; }
  /* Air (2026-09-24): every row gets real padding and the groups real separation. The .bar prefix
     outranks the §0.11 link reset (0,2,1), which would zero padding-block-end. */
  /* Selectors carry .bar .nav-links (0,3,x) on purpose: the §0.11 reset
     `:is(…, .lg-post h3, …) a:not([class])` weighs 0,2,2 and zeroes padding-block-end on plain links,
     and the site's bar-anchored menus (`.nav-group[data-anchor="bar"] > .nav-menu`, 0,3,0) set their
     own top. */
  .bar .nav-links .nav-group > .nav-menu { top: calc(100% + 30px); padding: 22px 22px 22px 216px; gap: 6px 14px; }
  /* The hover bridge covers only the gap between the bar row and the bubble: never the other menu
     items, so moving sideways to the next section always reaches it at once. */
  .bar .nav-links .nav-group > .nav-menu::before { top: -30px; height: 30px; }
  .bar .nav-links .nav-menu > a { padding: 12px 14px; column-gap: 14px; row-gap: 3px; border-radius: 14px; }
  .bar .nav-links .nav-menu > .nav-lead { margin-block-end: 10px; padding: 12px 14px; border-radius: 14px; position: relative; border-bottom: 0; }
  .bar .nav-links .nav-menu > .nav-lead::after { content: ""; position: absolute; left: 14px; right: 14px; bottom: -6px; border-bottom: var(--rule-w) solid var(--rule); }
  .bar .nav-links .nav-menu > .nav-more { margin-top: 10px; padding: 14px; border-radius: 14px; border-top: 0; position: relative; }
  .bar .nav-links .nav-menu > .nav-more::after { content: ""; position: absolute; left: 14px; right: 14px; top: -6px; border-top: var(--rule-w) solid var(--rule); }
  /* Every submenu icon moves on hover/focus (§0.2): the tile pops and wiggles, the glyph redraws
     itself (pathLength=1 is set by v.js, so one dash length fits every shape). */
  .bar .nav-links .nav-menu > a:is(:hover, :focus-visible) .nav-ico { animation: hdr-ico .6s cubic-bezier(.3, 1.6, .5, 1); }
  .bar .nav-links .nav-menu > a:is(:hover, :focus-visible) .nav-ico svg :is(path, circle, rect, line, polyline) { stroke-dasharray: 1; animation: hdr-draw .7s cubic-bezier(.4, 0, .2, 1); }
  .nav-menu[data-cols="3"] { --menu-w: 70rem; }
  .bubble::after { left: 14px; top: 14px; bottom: 14px; width: 184px; }
  .nav-art { left: 22px; bottom: 22px; width: 168px; }
  .nav-group[open] .nav-art { animation: hdr-leap .7s cubic-bezier(.3, 1.2, .5, 1) .05s both; }
  .nav-group[open] .nav-art::part(prop) { animation: hdr-pop .55s cubic-bezier(.3, 1.6, .5, 1) calc(.4s + var(--k, 0) * 90ms) both; }
  .nav-group[open] .nav-menu > a { animation: hdr-in .34s cubic-bezier(.2, .7, .3, 1) both; }
  .nav-menu[data-from="left"] > a { --dx: -26px; }
  .nav-menu[data-from="right"] > a { --dx: 26px; }
  .nav-group[open] .nav-menu > a:nth-of-type(2) { animation-delay: .03s; }
  .nav-group[open] .nav-menu > a:nth-of-type(3) { animation-delay: .06s; }
  .nav-group[open] .nav-menu > a:nth-of-type(4) { animation-delay: .09s; }
  .nav-group[open] .nav-menu > a:nth-of-type(n+5) { animation-delay: .12s; }
}
@keyframes hdr-in { from { opacity: 0; transform: translate(var(--dx, 0), 8px); } }
@keyframes hdr-leap {
  0% { opacity: 0; transform: translate(18%, 70%) rotate(14deg) scale(.7); }
  55% { opacity: 1; transform: translate(0, -10%) rotate(-5deg) scale(1.04); }
  78% { transform: translate(0, 2%) scale(1.03, .96); }
  100% { transform: none; }
}
@keyframes hdr-ico { 30% { transform: scale(1.18) rotate(-10deg); } 60% { transform: scale(.96) rotate(6deg); } }
@keyframes hdr-draw { from { stroke-dashoffset: 1; } }
@keyframes hdr-pop { from { opacity: 0; transform: scale(.2) rotate(-25deg); } }
@media (min-width: 1201px) and (prefers-reduced-motion: reduce) {
  .bubble, .bubble::before { transition: opacity .18s ease; }
  .nav-group[open] .nav-art, .nav-group[open] .nav-art::part(prop), .nav-group[open] .nav-menu > a { animation: none; }
  .bar .nav-links .nav-menu > a .nav-ico, .bar .nav-links .nav-menu > a .nav-ico svg * { animation: none !important; }
}
[data-motion="reduce"] .bar .nav-links .nav-menu > a :is(.nav-ico, .nav-ico svg *) { animation: none !important; }
[data-motion="reduce"] .bubble, [data-motion="reduce"] .bubble::before { transition: opacity .18s ease; }
[data-motion="reduce"] .nav-group[open] :is(.nav-art, .nav-menu > a), [data-motion="reduce"] .nav-group[open] .nav-art::part(prop) { animation: none; }
@media (max-width: 1200px) { .nav-art, .bubble, .pill-glow { display: none; } }

@media (min-width: 1201px) {
  /* Raw tool icons, as tall as the CTA (44px), no frame: only a soft round wash on hover. */
  body:not(.tools-dock) .bar .bar-tools { border: 0; padding: 0; gap: 2px; background: none; }
  body:not(.tools-dock) .bar .bar-tools .player-label { display: none; }
  body:not(.tools-dock) .bar .bar-tools :is(.player-btn, .icon-btn) {
    width: 37px; height: 37px; padding: 0; justify-content: center; border: 0 !important; border-radius: 50%;
    background: transparent; color: var(--ink); transition: background-color .2s, transform .35s cubic-bezier(.34, 1.56, .64, 1); }
  body:not(.tools-dock) .bar .bar-tools :is(.player-btn, .icon-btn):is(:hover, :focus-visible, [aria-expanded="true"]) { background: var(--field); transform: scale(1.08); }
  body:not(.tools-dock) .bar .bar-tools .player-btn svg { width: 21px; height: 21px; }
  body:not(.tools-dock) .bar .bar-tools .icon-btn svg { width: 24px; height: 24px; stroke-width: 1.9; }
  body:not(.tools-dock) .bar .bar-tools .player.is-active .player-btn { background: var(--accent); color: var(--on-fill); }

  /* Sub menu pill: same technique as the header pill. The strip keeps its size; a background layer
     clipped to insets gathers around the pills, and the pill row slides to the centre (transform). */
  .subnav { background: transparent; border-bottom-color: transparent; }
  .subnav > .wrap { position: relative; z-index: 1; }
  .subnav ul { transform: translateX(var(--sshift, 0px)); }
  .sub-glow { position: absolute; inset: 0; pointer-events: none; filter: drop-shadow(0 10px 20px rgb(11 12 16 / calc(var(--p, 0) * .16))); }
  .sub-pill { position: absolute; inset: 0; background: var(--paper-2);
    border-bottom: var(--rule-w) solid color-mix(in srgb, var(--rule) calc((1 - var(--p, 0)) * 100%), transparent);
    clip-path: inset(var(--siy, 0px) var(--sixr, 0px) var(--siy, 0px) var(--sixl, 0px) round calc(var(--p, 0) * 999px)); }
}
@media (min-width: 1201px) {
  /* In the pill every item shares one centre line (32px): the site's 8px top pad (§0.21 baseline
     alignment with the wordmark) left the menu 4px below the logo, tools and CTA. */
  .bar .nav-links { padding-top: 0; }
  .bar .bar-tools > * { display: inline-flex; align-items: center; }
}
@media (min-width: 1201px) {
  /* Three columns: logo left, menu centred on the page, tools + CTA right. Equal 1fr sides keep
     the menu on the true centre whatever the two sides weigh. The resting bar is full width. The
     40px gap (GAP_X in header.js) leaves room for a tool's hover lift and focus ring beside the menu. */
  .bar .bar-in.wrap { width: calc(100% - clamp(32px, 4vw, 72px)); max-width: none;
    display: grid; grid-template-columns: minmax(max-content, 1fr) auto minmax(max-content, 1fr); column-gap: 40px; }
  .bar .bar-in > .brand { grid-column: 1; justify-self: start; }
  .bar .bar-in .nav-links { grid-column: 2; grid-row: 1; margin: 0; }
  .bar .bar-in > .bar-end { grid-column: 3; grid-row: 1; justify-self: end; }
  .bar .nav-links :is(.nav-top, .nav-group > summary) { font-weight: 700; }

  /* One family for play, accessibility and language: same 37px ghost shape, same ink, same wash
     and lift on hover, circle-based 22px icons at one stroke weight, the flag a circle too. */
  body:not(.tools-dock) .bar :is(.bar-tools .player-btn, .bar-tools .icon-btn, .lang-btn) {
    height: 37px; border: 0 !important; border-radius: 999px; background: transparent; color: var(--ink);
    transition: background-color .2s, transform .35s cubic-bezier(.34, 1.56, .64, 1); }
  body:not(.tools-dock) .bar :is(.bar-tools .player-btn, .bar-tools .icon-btn, .lang-btn):is(:hover, :focus-visible, [aria-expanded="true"]) {
    background: var(--field); transform: scale(1.08); }
  body:not(.tools-dock) .bar .bar-tools :is(.player-btn, .icon-btn) svg { width: 22px; height: 22px; fill: none; stroke: currentColor; stroke-width: 1.9; stroke-linecap: round; stroke-linejoin: round; }
  body:not(.tools-dock) .bar .bar-tools .icon-btn svg circle[fill] { fill: currentColor; }
  body:not(.tools-dock) .bar .lang-btn { padding: 0 10px 0 8px; gap: 6px; font-size: 14px; font-weight: 700; text-transform: uppercase; letter-spacing: .02em; }
  body:not(.tools-dock) .bar .lang-btn .flag { width: 17px; height: 17px; margin: 0 2px; border-radius: 50%; box-shadow: 0 0 0 1.9px currentColor; }
  body:not(.tools-dock) .bar .lang-btn > svg:last-child { width: 10px; height: 6px; }
  body:not(.tools-dock) .bar .bar-end { gap: 4px; }
}
@media (min-width: 1201px) {
  /* The sub menu rests with no strip of its own (no white/dark band, no rule): just the centred
     pills on the page. Its background exists only as the scrolled pill, fading in as it gathers. */
  .sub-pill { opacity: var(--p, 0); border-bottom: 0; }
}
/* Centred sub menu at every width. Auto margins on the end pills, not justify-content: centre,
   so an overflowing row still scrolls to its first pill instead of cutting it off on the left. */
.subnav ul > li:first-child { margin-inline-start: auto; }
.subnav ul > li:last-child { margin-inline-end: auto; }
.crumbs-bar ol { justify-content: center; }
@media (max-width: 1200px) { .sub-glow { display: none; } }

/* Breadcrumbs: a faint home icon leads the trail (a soft left inset more than a signal), and the
   trail gets the same air below as above. */
.crumbs-bar { padding-block: 18px 22px; }
.crumbs-bar li:first-child a { display: inline-flex; align-items: center; gap: 6px; }
.crumb-home { width: 13px; height: 13px; flex: none; fill: none; stroke: currentColor; stroke-width: 2; stroke-linecap: round; stroke-linejoin: round; opacity: .3; }

/* Sub menu arrows: shown only while the row overflows and only on the side that has more to show.
   The row fades out under each visible arrow so a cut-off pill reads as "more this way". */
.subnav > .wrap { position: relative; }
.sub-arrow { position: absolute; top: 50%; z-index: 2; display: inline-grid; place-items: center; width: 32px; height: 32px;
  margin-top: -16px; padding: 0; border: 0; border-radius: 50%; background: var(--paper); color: var(--ink);
  box-shadow: 0 4px 12px -4px rgb(11 12 16 / .35), 0 0 0 1px var(--rule); cursor: pointer; transition: transform .3s cubic-bezier(.34, 1.56, .64, 1); }
.sub-arrow[hidden] { display: none; }
.sub-arrow.prev { left: calc(var(--ul-l, 0px) + 4px); }
.sub-arrow.next { right: calc(var(--ul-r, 0px) + 4px); }
.sub-arrow svg { width: 16px; height: 16px; fill: none; stroke: currentColor; stroke-width: 2.4; stroke-linecap: round; stroke-linejoin: round; transition: transform .3s cubic-bezier(.34, 1.56, .64, 1); }
.sub-arrow:hover, .sub-arrow:focus-visible { transform: scale(1.1); }
.sub-arrow.prev:hover svg, .sub-arrow.prev:focus-visible svg { transform: translateX(-2px); }
.sub-arrow.next:hover svg, .sub-arrow.next:focus-visible svg { transform: translateX(2px); }
.subnav[data-more~="prev"] ul { --fade-l: 48px; }
.subnav[data-more~="next"] ul { --fade-r: 48px; }
.subnav[data-more] ul { scroll-behavior: smooth;
  mask-image: linear-gradient(to right, transparent 0, #000 var(--fade-l, 0px), #000 calc(100% - var(--fade-r, 0px)), transparent 100%); }
@media (prefers-reduced-motion: reduce) { .subnav[data-more] ul { scroll-behavior: auto; } }
[data-motion="reduce"] .subnav[data-more] ul { scroll-behavior: auto; }
/* The bottom dock (phones, or no room in the bar): tools 20% bigger than the site's 40px. */
body.tools-dock .bar-tools :is(.player-btn, .icon-btn) { height: 48px; min-width: 48px; }
body.tools-dock .bar-tools .player-btn svg { width: 19px; height: 19px; }
body.tools-dock .bar-tools .icon-btn svg { width: 24px; height: 24px; }

.player-btn svg { fill: none; stroke: currentColor; stroke-width: 1.9; stroke-linecap: round; stroke-linejoin: round; }
body.tools-dock .bar-tools .player-btn svg { width: 24px; height: 24px; }
.bar-cta .rkt { fill: none; stroke: currentColor; stroke-width: 2; stroke-linecap: round; stroke-linejoin: round; }
.prompter-row .rkt { width: 18px; height: 18px; margin-inline-start: 8px; fill: none; stroke: currentColor; stroke-width: 2; stroke-linecap: round; stroke-linejoin: round; transition: transform .3s cubic-bezier(.34, 1.56, .64, 1); }
.prompter-row .btn:is(:hover, :focus-visible) .rkt { transform: translateX(4px); }
.crumb-home { width: 13px; height: 13px; flex: none; fill: none; stroke: currentColor; stroke-width: 2; stroke-linecap: round; stroke-linejoin: round; opacity: .3; }

/* Phones and tablets (the drawer layout, <= 1200px): no CTA in the bar — the drawer carries its own
   call to action — and the bar slides away while the reader scrolls down, leaving only the sub menu
   (when the page has one) pinned at the top; scrolling up brings the bar back (header.js). */
@media (max-width: 1200px) {
  .bar-end .bar-cta { display: none; }
  .bar { transition: transform .32s cubic-bezier(.2, .7, .2, 1), background-color .25s ease; }
  .subnav { transition: top .32s cubic-bezier(.2, .7, .2, 1); }
  html.bar-away .bar { transform: translateY(-100%); }
  html.bar-away .subnav { top: 0; }
}
@media (max-width: 1200px) and (prefers-reduced-motion: reduce) { .bar, .subnav { transition: none; } }
[data-motion="reduce"] :is(.bar, .subnav) { transition: none; }
.prompter-box h2 { padding-inline-end: 48px; }

/* Phones and tablets: the tools stay in the bar (no bottom dock, so nothing jumps after load) and
   look exactly as on desktop: raw round icons, one family with the language button (§0.26). The
   sides of the desktop grid never shrink below their content, so the tools never collide with the
   centred menu either: at the tightest width the menu merely sits a little off centre. */
@media (max-width: 1200px) {
  .bar .bar-tools { border: 0; padding: 0; gap: 0; background: none; }
  .bar .bar-tools .player-label { display: none; }
  .bar .bar-tools :is(.player-btn, .icon-btn), .bar .lang-btn {
    height: 40px; border: 0 !important; border-radius: 999px; background: transparent; color: var(--ink); }
  .bar .bar-tools :is(.player-btn, .icon-btn) { width: 40px; padding: 0; justify-content: center; }
  .bar .bar-tools :is(.player-btn, .icon-btn):is(:hover, :focus-visible, [aria-expanded="true"]), .bar .lang-btn:is(:hover, :focus-visible, [aria-expanded="true"]) { background: var(--field); }
  .bar .bar-tools :is(.player-btn, .icon-btn) svg { width: 24px; height: 24px; fill: none; stroke: currentColor; stroke-width: 1.9; }
  .bar .bar-tools .icon-btn svg circle[fill] { fill: currentColor; }
  .bar .bar-tools .player.is-active .player-btn { background: var(--accent); color: var(--on-fill); }
  .bar .lang-btn { padding: 0 8px; gap: 6px; font-size: 14px; font-weight: 700; text-transform: uppercase; }
  .bar .lang-btn .flag { width: 18px; height: 18px; border-radius: 50%; box-shadow: 0 0 0 1.9px currentColor; }
  .bar .bar-end { gap: 0; }
}

/* Back to top matches the chat button (2026-09-24): both are --castomo-size, both 20px off the
   bottom. The chat defines the variable on its own element; the root copy is the same 56px, so the
   pair stays equal even before the chat script has loaded. A ring draws around it with the scroll
   and is closed at the bottom of the page (site.js sets --totop-p). */
:root { --castomo-size: 56px; }
.totop { width: var(--castomo-size); height: var(--castomo-size); inset-block-end: 20px; }
.totop svg:not(.totop-ring) { width: 22px; height: 22px; }
.totop-ring { position: absolute; inset: 0; width: 100% !important; height: 100% !important; transform: rotate(-90deg); pointer-events: none; }
.totop-ring circle { fill: none; stroke: var(--accent); stroke-width: 3; stroke-linecap: round;
  stroke-dasharray: 1; stroke-dashoffset: calc(1 - var(--totop-p, 0)); transition: stroke-dashoffset .12s linear; }
[data-theme^="contrast"] .totop-ring circle { stroke: var(--paper); }

/* The ecosystem and tools shelves: every tile snaps to the centre of the row, and the row opens on
   the first tile centred (site.js), on phones and desktops alike. */
.foot-family ul { scroll-padding-inline: 0; }
.foot-family li { scroll-snap-align: center; }

/* The narrowest phones: the footer's link lists stack in one column. */
@media (max-width: 370px) { .foot-grid { grid-template-columns: 1fr; } }
