/* =====================================================================
   stica.css — the one stylesheet for stica.co.uk
   Built 2026-08-06 from phase-zero/GRID-CONSOLIDATED.md.

   This file replaces nine drifted copies of the same component set:
   the old stica.css, grid-motion.css, mobile.css, scenes.css,
   craft/assets/style.css and the page-local <style> blocks in
   index, start, craft, craft/pricing, apply, member, coaching,
   pricing and science.

   A page links exactly two files and nothing else:
     <link rel="stylesheet" href="/assets/stica.css">
     <script src="/assets/stica.js" defer></script>

   NO page-local component CSS. If a component is missing, it is missing
   on purpose or by mistake, and the answer is to ask, not to invent.

   Fonts are five external .woff2 files under /assets/fonts/: Public Sans
   (variable), IBM Plex Mono 400 and 500, Kalam 400 and 700. They are NOT
   embedded; nothing renders offline. (The old header claimed otherwise.)

   The site moved to direction A on 2026-08-31: Public Sans for display and
   text, IBM Plex Mono for data and provenance only, Kalam for the hand.
   Geist and Geist Mono are gone and their files are deleted.
   ===================================================================== */


/* =====================================================================
   1. FONTS
   Public Sans is a variable font, so one @font-face with a weight range.
   IBM Plex Mono and Kalam ship one file per weight.
   Preload the two that render above the fold, from the page <head>:
     <link rel="preload" href="/assets/fonts/public-sans-wght-normal.woff2"
           as="font" type="font/woff2" crossorigin>
     <link rel="preload" href="/assets/fonts/ibm-plex-mono-400-normal.woff2"
           as="font" type="font/woff2" crossorigin>
   Kalam is below the fold everywhere; swap is fine, do not preload it.
   ===================================================================== */

@font-face{font-family:'Public Sans';font-style:normal;font-weight:100 900;font-display:swap;
  src:url(/assets/fonts/public-sans-wght-normal.woff2) format("woff2");}
@font-face{font-family:'IBM Plex Mono';font-style:normal;font-weight:400;font-display:swap;
  src:url(/assets/fonts/ibm-plex-mono-400-normal.woff2) format("woff2");}
@font-face{font-family:'IBM Plex Mono';font-style:normal;font-weight:500;font-display:swap;
  src:url(/assets/fonts/ibm-plex-mono-500-normal.woff2) format("woff2");}
@font-face{font-family:'Kalam';font-style:normal;font-weight:400;font-display:swap;
  src:url(/assets/fonts/kalam-400-normal.woff2) format("woff2");}
@font-face{font-family:'Kalam';font-style:normal;font-weight:700;font-display:swap;
  src:url(/assets/fonts/kalam-700-normal.woff2) format("woff2");}


/* =====================================================================
   2. TOKENS

   Palette values are declared ONCE, as --l-* (light) and --d-* (dark).
   The live tokens (--bg, --ink, …) are a mapping onto those.

   Dark is LIVE (switched on 2026-08-06). Every page carries
   data-theme="auto" on <html>, so a visitor whose device is set to dark
   gets the dark site and everyone else gets light. "dark" forces it and
   "light" pins it; two pages pin light on purpose, because they are
   artefacts rather than pages a visitor browses: assets/og-src.html
   renders the social card, and downloads/stica-example-plan.html is a
   printable A4 document. Every new page must set data-theme, or the site
   changes appearance as someone walks through it.

   Working-state colours (green / amber / cobalt / oxide) are FACTS, not
   branding. They never change with the line accent and are never
   repurposed as decoration.
   ===================================================================== */

:root{
  /* ---- families ---- */
  --sans:'Public Sans','Public Sans Variable',system-ui,-apple-system,sans-serif;
  --mono:'IBM Plex Mono','SFMono-Regular',Consolas,monospace;
  --hand:'Kalam',cursive;

  /* ---- light palette (values live here, once) ---- */
  /* Ian, 2026-08-29 (ans-20260829-182938-e22f), verbatim: "I want the page to
     be white on the day mode, and black on night mode. With the graph paper
     being a slight beige on day mode, and charcoal on night mode."
     --paper is the sheet the page lies on; --bg is the graph-paper ground
     under it. They are separate tokens because --surface (cards, the footer,
     pinned scraps) must NOT follow the sheet: at night the sheet goes darker
     than the ground, which is the opposite of what a card does.
     The ground was #FAFAF6 until that answer, one step off white, so a white
     sheet on it read as nothing (1.046:1, measured). #F0ECDF puts the sheet
     at 1.182:1, which is the same step the black sheet gets on the charcoal
     ground at night, so the page reads the same amount in both palettes.
     --l-grid moved with it to hold the ruling at the step it had (1.129:1
     against 1.133:1 before). Muted text on the new ground is 4.555:1. */
  --l-bg:#F0ECDF;      --l-surface:#FFFFFF;  --l-rule:#F3F0E8;
  --l-paper:#FFFFFF;
  --l-line:#E2E2DC;    --l-line2:#CFCFC8;    --l-line-ui:#8D8D82;
  --l-ink:#111110;     --l-ink2:#33332B;     --l-mut:#6B6B61;
  /* The sheet's edge on a textured ground. Measured against the cloth blotter
     with sheet-edge-probe.mjs (3:1, non-text): the weave's own turbulence
     noise runs from near-black to near-white inside any few pixels next to
     the sheet, so no flat hairline colour can clear 3:1 against the worst
     single thread-crossing — checked directly, black, white and mid greys
     all fail the same way. --l-line-edge is the crisp line at the true
     edge; --l-shadow-sheet (below, with --l-shadow) is what actually earns
     the contrast, a close, near-opaque contact ring that reads as the
     paper's own cast shadow rather than a drawn box, wide enough to cover
     every pixel the probe samples. Worst case measured 4.48:1 at 1280,
     4.6:1 at 390 (2026-08-31). */
  --l-line-edge:#111110;
  --l-green:#0F7A3D;   --l-green-bg:#E4F6EA;
  --l-amber:#8A5806;   --l-amber-bg:#FBEFD6;
  --l-cobalt:#27349C;  --l-cobalt-bg:#E6EAFE;
  --l-oxide:#7A2520;   --l-oxide-bg:#F3E4E2;
  /* The pen. One ink for a human mark on the page, on every product line,
     because a mark somebody made by hand is not part of the brand palette:
     if it took the line accent it would read as design rather than as a
     note. Deliberately NOT --oxide, which is the fault colour: the hand
     rule says a note is only ever positive or clarifying, never a warning,
     so the two must not share a value. Measured on every ground it can
     land on: white paper 5.90:1, the beige ground 4.99:1, and the three
     sticky-note colours 5.01 / 4.61 / 4.66:1. */
  --l-pen:#B8351A;
  /* Sticky notes. Objects, so they keep these values in both palettes,
     the same rule the pinned scrap follows. Text on one is always --ink;
     never the pen, never muted, neither of which clears 4.5:1 on the
     pink or the blue. */
  --l-note-y:#FBEE8C;  --l-note-p:#FBC8D4;  --l-note-b:#A8E0F2;  --l-note-g:#C7EFA8;
  --l-shadow:0 1px 2px rgba(20,20,19,.05), 0 6px 18px rgba(20,20,19,.05);
  /* The contact ring: 0-blur so it is at full strength all the way through
     the 3-11px band the probe samples (blur only softens the transition
     PAST its own spread, never before it), then a second, wider, blurred
     layer to fade it into the desk beyond that band so it reads as a soft
     lift, not a ruled box. --l-shadow layered in underneath keeps the
     existing paper depth. */
  --l-shadow-sheet:0 0 0 12px rgba(130,130,118,.95), 0 0 22px 14px rgba(130,130,118,.25), var(--l-shadow);
  --l-hl-mix:20%;
  --l-scrim:rgba(10,10,9,.5);

  /* ---- dark palette (values live here, once) ---- */
  --d-bg:#17181A;      --d-surface:#1F2123;  --d-rule:#191919;
  --d-paper:#000000;
  --d-line:#303234;    --d-line2:#3C3E41;    --d-line-ui:#6E7175;
  --d-ink:#ECECE7;     --d-ink2:#C4C5BE;     --d-mut:#8F908A;
  /* At night the cloth is close to black end to end (measured range 0 to
     ~25 in the same band), so a border this much lighter than the ground
     already clears 3:1 on its own — worst case 3.62:1 at both widths — and
     needs no extra ring. Same strength as --d-line-ui, kept separate so the
     two can move independently later. */
  --d-line-edge:#6E7175;
  --d-shadow-sheet:var(--d-shadow);
  --d-green:#4FD68A;   --d-green-bg:#16302233;
  --d-amber:#FBB13C;   --d-amber-bg:#3A2C0F40;
  --d-cobalt:#8FA0FF;  --d-cobalt-bg:#1B215080;
  --d-oxide:#E0625A;   --d-oxide-bg:#3A1E1B66;
  --d-pen:#FF8C63;     /* 9.18:1 on the black sheet, 7.77:1 on a light page's dark slab, 6.47:1 on a dark page's raised slab */
  --d-shadow:0 1px 2px rgba(0,0,0,.4), 0 8px 22px rgba(0,0,0,.35);
  --d-hl-mix:32%;
  --d-scrim:rgba(0,0,0,.62);

  /* ---- live tokens: light by default ---- */
  --bg:var(--l-bg);            --surface:var(--l-surface);   --rule:var(--l-rule);
  --paper:var(--l-paper);
  --line:var(--l-line);        --line2:var(--l-line2);       --line-ui:var(--l-line-ui);
  --ink:var(--l-ink);          --ink2:var(--l-ink2);         --mut:var(--l-mut);
  --line-edge:var(--l-line-edge); --shadow-sheet:var(--l-shadow-sheet);
  --green:var(--l-green);      --green-bg:var(--l-green-bg);
  --amber:var(--l-amber);      --amber-bg:var(--l-amber-bg);
  --cobalt:var(--l-cobalt);    --cobalt-bg:var(--l-cobalt-bg);
  --oxide:var(--l-oxide);      --oxide-bg:var(--l-oxide-bg);
  --pen:var(--l-pen);
  --note-y:var(--l-note-y);    --note-p:var(--l-note-p);
  --note-b:var(--l-note-b);    --note-g:var(--l-note-g);
  --shadow:var(--l-shadow);    --hl-mix:var(--l-hl-mix);     --scrim:var(--l-scrim);

  /* ---- spacing: 4px-rooted, twelve steps, nothing between ---- */
  --s1:4px;  --s2:8px;  --s3:12px; --s4:16px; --s5:24px;
  --s6:32px; --s7:44px; --s8:64px; --s9:80px; --s10:96px;
  /* Round 3: two steps added for a page that opts into a roomier rhythm
     (.st-section--roomy, .st-hero-split--roomy) than the 80px default.
     Nothing existing reads these; no page regresses by their addition. */
  --s11:128px; --s12:176px;

  /* ---- page ---- */
  --w-page:1080px;   /* everything */
  --w-wide:1220px;   /* one case only: a full-bleed media row. Never text, never a footer. */
  --gutter:44px;     /* set by the container, never per section */
  --sec-y:var(--s9); /* section vertical padding */
  --sec-y-roomy:var(--s11); /* the opt-in wider rhythm; overridden at both breakpoints below like --sec-y is */

  /* ---- radii: five values, nothing else ---- */
  --r-sm:4px;    /* inline marks, wordmark highlight, small inner blocks */
  --r-md:6px;    /* form inputs, chips, small controls */
  --r-lg:8px;    /* tiles, cards, buttons, panels — the default */
  --r-xl:12px;   /* the one hero-scale panel per page */
  --r-full:999px;/* header nav pills ONLY */
  --r-dot:50%;   /* brand dot, chip dots, step rings, pins */

  /* ---- shadows: objecthood, never hierarchy ---- */
  --shadow-paper:drop-shadow(0 1px 2px rgba(20,20,19,.32))
                 drop-shadow(0 12px 18px rgba(20,20,19,.20))
                 drop-shadow(0 26px 36px rgba(20,20,19,.14));
  --shadow-stuck:0 1px 1px rgba(28,24,14,.05), 1px 2px 4px rgba(28,24,14,.06),
                 3px 6px 11px rgba(28,24,14,.07), 6px 13px 22px rgba(28,24,14,.08),
                 9px 22px 38px rgba(28,24,14,.06);

  /* ---- motion ---- */
  --ease:cubic-bezier(0.22,1,0.36,1);
  --dur-1:150ms;  /* colour and border on hover */
  --dur-2:220ms;  /* transform, small state change */
  --dur-3:320ms;  /* stuck-note lift, panel open */
  --dur-4:380ms;  /* scroll reveal */
  --dur-5:520ms;  /* highlighter sweep */
  --dur-6:1600ms; /* a drawn line */

  /* ---- type steps: every size on the site is one of these ---- */
  --fs-display:clamp(36px,6.4vw,64px);
  --fs-h1:clamp(30px,5vw,44px);
  --fs-h2:clamp(26px,4vw,38px);
  /* One addition, round 2: the price card's own number needed to outrank a
     section heading and never tie an h1. Sits between --fs-h1 and
     --fs-display on purpose. Still a token, still clamped, still used
     nowhere by hand. */
  --fs-price:clamp(40px,4.6vw,52px);
  --fs-h3:20px;
  --fs-h3-lg:24px;
  --fs-h4:16px;
  --fs-lede:clamp(16.5px,2vw,19px);
  --fs-body:16px;
  --fs-body-sm:14px;
  --fs-eyebrow:11px;
  --fs-label:10.5px;
  --fs-chip:10px;
  --fs-small:11px;
  --fs-note:30px;
  --fs-note-sm:21px;
  --fs-note-xs:17px;
}

/* ---- dark mapping. Two blocks, one set of values. ---- */
:root[data-theme="dark"]{
  --bg:var(--d-bg);            --surface:var(--d-surface);   --rule:var(--d-rule);
    --paper:var(--d-paper);
  --line:var(--d-line);        --line2:var(--d-line2);       --line-ui:var(--d-line-ui);
  --ink:var(--d-ink);          --ink2:var(--d-ink2);         --mut:var(--d-mut);
  --line-edge:var(--d-line-edge); --shadow-sheet:var(--d-shadow-sheet);
  --green:var(--d-green);      --green-bg:var(--d-green-bg);
  --amber:var(--d-amber);      --amber-bg:var(--d-amber-bg);
  --cobalt:var(--d-cobalt);    --cobalt-bg:var(--d-cobalt-bg);
  --oxide:var(--d-oxide);      --oxide-bg:var(--d-oxide-bg);
  --pen:var(--d-pen);
  --shadow:var(--d-shadow);    --hl-mix:var(--d-hl-mix);     --scrim:var(--d-scrim);
  color-scheme:dark;
}
@media (prefers-color-scheme: dark){
  :root[data-theme="auto"]{
    --bg:var(--d-bg);            --surface:var(--d-surface);   --rule:var(--d-rule);
    --paper:var(--d-paper);
    --line:var(--d-line);        --line2:var(--d-line2);       --line-ui:var(--d-line-ui);
    --ink:var(--d-ink);          --ink2:var(--d-ink2);         --mut:var(--d-mut);
    --line-edge:var(--d-line-edge); --shadow-sheet:var(--d-shadow-sheet);
    --green:var(--d-green);      --green-bg:var(--d-green-bg);
    --amber:var(--d-amber);      --amber-bg:var(--d-amber-bg);
    --cobalt:var(--d-cobalt);    --cobalt-bg:var(--d-cobalt-bg);
    --oxide:var(--d-oxide);      --oxide-bg:var(--d-oxide-bg);
    --pen:var(--d-pen);
    --shadow:var(--d-shadow);    --hl-mix:var(--d-hl-mix);     --scrim:var(--d-scrim);
    color-scheme:dark;
  }
}
:root[data-theme="light"]{ color-scheme:light; }


/* =====================================================================
   3. THE ACCENT: one brand constant, one accent per product line

   Teal is the brand constant. It carries the shell on EVERY page without
   exception: wordmark, nav, footer, focus ring, skip link. It is also the
   page accent for everything that is not a product line — the homepage,
   pricing, consulting, the method, the legal pages, status, 404.

   A product line overrides ONLY the four accent tokens, by putting its
   class on <html>. Pages within a line share their line's accent. There
   is no such thing as a per-page accent.

     (none)        Stica teal      brand constant + all shared pages
     .line-stica   cobalt          Stica: /learn, /teams, /start
     .line-coach   violet          Stica Coach: /coaches

   Each accent ships FOUR tokens with three different jobs, because one
   value doing focus ring, button fill and accent text is exactly the
   fault that produced six live accessibility failures:

     --acc        decorative ONLY. The pin, the wordmark wash, a low-alpha
                  fill, a spine stroke where a number already carries the
                  meaning. NEVER text. NEVER a sole boundary.
     --acc-line   any accent line that must be perceivable: focus ring,
                  step ring, active-tab underline, left-accent border.
                  >= 3:1 on both grounds.
     --acc-fill   solid accent blocks carrying white text. White on it
                  is >= 4.5:1.
     --acc-text   accent-coloured text at any size. >= 4.5:1 on both
                  grounds.

   Measured ratios are in FOUNDATION-REPORT.md. Do not substitute a value.
   ===================================================================== */

:root{
  /* Every accent value on the property is declared here, once. Nothing
     below this block, and no page anywhere, may write one of these hexes
     by hand. Measured ratios are in FOUNDATION-REPORT.md. */
  --a-brand:#12A6B5;        --a-brand-line:#0F909D;   --a-brand-fill:#0D7B87;
  --a-brand-line-d:#12A6B5; --a-brand-text-d:#12A6B5;

  --a-stica:#3D5AFF;        --a-stica-line:#5F77FF;   --a-stica-fill:#405CFF;
  --a-stica-line-d:#4C67FF; --a-stica-text-d:#7086FF;

  --a-coach:#7C5CFF;        --a-coach-line:#876AFF;   --a-coach-fill:#704DFF;
  --a-coach-line-d:#7959FF; --a-coach-text-d:#957BFF;

  /* live accent tokens: brand teal by default, so a page that forgets its
     line class renders the brand, never something wrong */
  --acc:var(--a-brand);
  --acc-line:var(--a-brand-line);
  --acc-fill:var(--a-brand-fill);
  --acc-text:var(--a-brand-fill);
}
/* .line-stica / .line-coach normally sit on <html> (one line owns the whole
   page). pricing/index.html is the one page where two lines share a page,
   so the same classes are ALSO plain (non-:root) rules here: put one on a
   <section> and only that section's descendants get the line's tokens.
   Same tokens, same bars, just a narrower scope. */
:root.line-stica, .line-stica{
  --acc:var(--a-stica); --acc-line:var(--a-stica-line);
  --acc-fill:var(--a-stica-fill); --acc-text:var(--a-stica-fill);
}
:root.line-coach, .line-coach{
  --acc:var(--a-coach); --acc-line:var(--a-coach-line);
  --acc-fill:var(--a-coach-fill); --acc-text:var(--a-coach-fill);
}

/* On the dark board the bright value becomes the safe one. Fill is
   mode-invariant: white on it passes in both modes. */
:root[data-theme="dark"]{             --acc-line:var(--a-brand-line-d); --acc-text:var(--a-brand-text-d); }
:root[data-theme="dark"].line-stica, [data-theme="dark"] .line-stica{  --acc-line:var(--a-stica-line-d); --acc-text:var(--a-stica-text-d); }
:root[data-theme="dark"].line-coach, [data-theme="dark"] .line-coach{  --acc-line:var(--a-coach-line-d); --acc-text:var(--a-coach-text-d); }
@media (prefers-color-scheme: dark){
  :root[data-theme="auto"]{            --acc-line:var(--a-brand-line-d); --acc-text:var(--a-brand-text-d); }
  :root[data-theme="auto"].line-stica, [data-theme="auto"] .line-stica{ --acc-line:var(--a-stica-line-d); --acc-text:var(--a-stica-text-d); }
  :root[data-theme="auto"].line-coach, [data-theme="auto"] .line-coach{ --acc-line:var(--a-coach-line-d); --acc-text:var(--a-coach-text-d); }
}

/* Teal carries the shell on EVERY page without exception, even inside a
   product line: wordmark, nav, footer, focus ring. A line class overrides
   --acc* at :root for the content it wraps, but that inheritance must not
   leak into the header or footer, or the brand mark changes colour from
   page to page. Re-pinning the four tokens on the shell subtree wins the
   cascade for everything inside it, machine content untouched. */
.st-head,.st-footer,.st-skip{
  --acc:var(--a-brand); --acc-line:var(--a-brand-line);
  --acc-fill:var(--a-brand-fill); --acc-text:var(--a-brand-fill);
}
:root[data-theme="dark"] .st-head,:root[data-theme="dark"] .st-footer,:root[data-theme="dark"] .st-skip{
  --acc-line:var(--a-brand-line-d); --acc-text:var(--a-brand-text-d);
}
@media (prefers-color-scheme: dark){
  :root[data-theme="auto"] .st-head,:root[data-theme="auto"] .st-footer,:root[data-theme="auto"] .st-skip{
    --acc-line:var(--a-brand-line-d); --acc-text:var(--a-brand-text-d);
  }
}


/* =====================================================================
   4. BASE
   ===================================================================== */

*,*::before,*::after{box-sizing:border-box;}
html{-webkit-text-size-adjust:100%;}
body{
  margin:0;
  /* The ground is the plain desk (brand spec section 4): beige by day,
     charcoal by night, no lines or texture on it anywhere, ever. The cloth
     weave added 2026-08-31 was never in the approved spec and is removed,
     2026-09-01, not converted or lightened. */
  background-color:var(--bg);
  color:var(--ink);
  font-family:var(--sans);
  font-size:var(--fs-body);
  line-height:1.6;
  -webkit-font-smoothing:antialiased;
  text-rendering:optimizeLegibility;
}
img,svg,video{max-width:100%;height:auto;}
/* Coach-violet as text measures 4.24:1 on the beige desk, which fails the
   body-text bar. Accent text is allowed on paper or a surface, never
   directly on the desk ground. Enforced here rather than trusted per page:
   the desk default is ink with an underline, and only a paper/surface
   context restores accent-coloured text. */
a{color:var(--ink);text-decoration:underline;text-underline-offset:3px;}
.st-sheet a:not(.st-btn):not(.st-btn--sec):not(.st-btn--pill):not(.st-btn--ghost):not(.st-btn--outline),
.st-tile a:not(.st-btn):not(.st-btn--sec):not(.st-btn--pill):not(.st-btn--ghost):not(.st-btn--outline),
.st-panel a:not(.st-btn):not(.st-btn--sec):not(.st-btn--pill):not(.st-btn--ghost):not(.st-btn--outline),
.st-postit a:not(.st-btn):not(.st-btn--sec):not(.st-btn--pill):not(.st-btn--ghost):not(.st-btn--outline),
.st-stuck a:not(.st-btn):not(.st-btn--sec):not(.st-btn--pill):not(.st-btn--ghost):not(.st-btn--outline),
.st-surface a:not(.st-btn):not(.st-btn--sec):not(.st-btn--pill):not(.st-btn--ghost):not(.st-btn--outline),
.st-rowlist a:not(.st-btn):not(.st-btn--sec):not(.st-btn--pill):not(.st-btn--ghost):not(.st-btn--outline),
.st-drop a:not(.st-btn):not(.st-btn--sec):not(.st-btn--pill):not(.st-btn--ghost):not(.st-btn--outline),
.st-filelist a:not(.st-btn):not(.st-btn--sec):not(.st-btn--pill):not(.st-btn--ghost):not(.st-btn--outline),
.st-callout a:not(.st-btn):not(.st-btn--sec):not(.st-btn--pill):not(.st-btn--ghost):not(.st-btn--outline),
.st-evidence a:not(.st-btn):not(.st-btn--sec):not(.st-btn--pill):not(.st-btn--ghost):not(.st-btn--outline){
  color:var(--acc-text);text-decoration:none;
}
/* Every marketing page's <main> carries .st-sheet, so the .st-sheet a rule
   above already wins for every link on the page, including ones inside a
   nested .st-callout--honest. That variant deliberately repaints itself
   with background:var(--bg), the plain desk (see .st-callout--honest
   below), so a link inside it is NOT on paper even though it sits inside
   a .st-sheet ancestor. The exclusion has to live here, scoped tighter
   than .st-sheet a, or it is silently overruled by the ancestor rule that
   matches first. Measured: "consulting" on teams/index.html at 4.24:1,
   the desk-accent fault this file's opening comment describes. */
.st-callout--honest a:not(.st-btn):not(.st-btn--sec):not(.st-btn--pill):not(.st-btn--ghost):not(.st-btn--outline){
  color:var(--ink);text-decoration:underline;text-underline-offset:3px;
}
/* Same fault, three more self-painting variants. .st-tile--flat repaints
   itself var(--bg) ("on a banded section", see the class above); a link
   inside one measured 4.04:1 on consulting/index.html's waitlist card.
   .st-section--tint is color-mix(acc 5%, bg), close enough to the desk
   that accent text on it measured 4.04:1 on consulting and services.
   .st-drop is the file drop zone, also background:var(--bg); its "choose
   them" link measured 4.23:1 on assets/components.html once that page's
   <main> got .st-sheet (2026-08-31). All three are descendants of the
   page's .st-sheet <main>, so all three need the same scoped-tighter
   treatment as .st-callout--honest above. */
.st-tile--flat a:not(.st-btn):not(.st-btn--sec):not(.st-btn--pill):not(.st-btn--ghost):not(.st-btn--outline),
.st-section--tint a:not(.st-btn):not(.st-btn--sec):not(.st-btn--pill):not(.st-btn--ghost):not(.st-btn--outline),
.st-drop a:not(.st-btn):not(.st-btn--sec):not(.st-btn--pill):not(.st-btn--ghost):not(.st-btn--outline){
  color:var(--ink);text-decoration:underline;text-underline-offset:3px;
}
hr{border:0;border-top:1px solid var(--line);margin:var(--s6) 0;}

/* One focus treatment, everywhere. 3px at 2px offset in --acc-line,
   which clears the 3:1 non-text bar in every line and both modes. */
:where(a,button,input,textarea,select,summary,[tabindex]):focus-visible{
  outline:3px solid var(--acc-line);
  outline-offset:2px;
  border-radius:var(--r-sm);
}
:where(a,button,input,textarea,select,summary,[tabindex]):focus:not(:focus-visible){outline:none;}


/* =====================================================================
   5. PAGE, CONTAINER, GRAPH GROUND, SECTION
   ===================================================================== */

/* Direction A: the two-axis graph ground is retired. The desk (--bg) is
   plain, day and night, everywhere. No .st-grid class, no lines on it. */

/* One width. Gutters are applied by the container, never per section. */
.st-wrap{
  max-width:var(--w-page);
  margin-inline:auto;
  padding-inline:var(--gutter);
}
.st-wrap--wide{max-width:var(--w-wide);}  /* full-bleed media rows only */

/* THE SHEET. The page's main column is a sheet of paper lying on the graph
   ground, not painted onto it: white by day, black at night, with the ruled
   beige (day) or charcoal (night) running out to the edges around it, under
   the header and the footer too.
   This is NOT the white-paper rule in §16. That rule is for a small pinned
   scrap, a note or an artefact, and it deliberately keeps its LIGHT palette
   when the board goes dark, because a scrap of paper pinned to a dark board
   is still white. The sheet is the page itself, so it flips with the palette
   the way the page does: at night it is the darkest thing on screen, and the
   ground around it is lighter. The two differ because one is an object ON the
   page and the other IS the page. (Ian, 2026-08-29.) */
.st-sheet{
  background-color:var(--paper);
  /* One-axis ruling, feint (target <=1.15:1 against the paper), clipped to
     the sheet's own rounded border. Ruling is texture, never a boundary. */
  background-image:linear-gradient(var(--rule) 1px,transparent 1px);
  background-size:100% 32px;
  background-position:0 12px;
  /* --line2, not --line: the sheet's edge is drawn against the GROUND, and
     --line on the beige measures 1.101:1, which is no edge at all. See the
     check 'an edge drawn on the graph ground can be seen from both sides'
     in test/brand-is-one-brand.smoke.mjs. (2026-08-29.)
     Cloth (2026-08-31): --line2 was tuned for the flat graph ground and
     measures as low as 1:1 against the cloth blotter's own dark threads.
     --line-edge and --shadow-sheet replace it here, on the sheet only —
     --line2 keeps its old value everywhere else it is used. See the tokens
     block above for why the two need different jobs by day and by night. */
  border:1px solid var(--line-edge);
  border-radius:var(--r-lg);
  padding-block:var(--s7);
  margin-block:var(--s6);
  box-shadow:var(--shadow-sheet);
}
@media (max-width:640px){ .st-sheet{ margin-inline:12px; } }

/* A BLOCK: a titled section lying on the sheet. Not an object. The sheet's
   ruling runs through it, which is the point: it is part of the page, not a
   thing on the page. Divided from its neighbour by a rule and by air, the
   way a section of a document is. */
.st-block{
  background:none; border:0; border-radius:0; box-shadow:none;
  border-top:1px solid var(--line);
  padding:var(--s5) 0 0;
  margin:var(--s5) 0 0;
}
.st-block:first-child{border-top:0;padding-top:0;margin-top:0;}
.st-block > :last-child{margin-bottom:0;}

/* A CARD: an object laid on the sheet. NEVER --paper, NEVER ruled, NEVER
   shadowed. --surface plus an edge. --line2, not --line: measured on the
   sheet, --line is 1.30:1 and --line2 is 1.57:1, and an edge that cannot be
   seen is not an edge. */
.st-card{
  background-color:var(--surface);
  border:1px solid var(--line2);
  border-radius:var(--r-lg);
  padding:var(--s5);
  box-shadow:none;
}
.st-card > :last-child{margin-bottom:0;}

/* A card whose whole box is the click target. Its boundary is now a control
   boundary, so it has to clear the 3:1 non-text bar: --line-ui measures
   3.35:1 on the light sheet and 4.28:1 on the black one. */
.st-card--act{border-color:var(--line-ui);}

/* A card that floats above the page (modal, popover, drawer). The one place
   --shadow survives, because this one really is lifted off. */
.st-card--float{box-shadow:var(--shadow);}

/* THE FIRST-RUN EMPTY STATE. One class, both apps. See §7. */
.st-firstrun{
  display:flex;flex-direction:column;align-items:flex-start;
  gap:var(--s3);
  max-width:46ch;
  padding-block:var(--s8);
}
.st-firstrun h2{margin:0;}
.st-firstrun p{color:var(--ink2);margin:0;}
.st-firstrun .st-stuck{align-self:flex-start;margin-top:var(--s5);}
/* When it replaces the contents of a grid, it spans the grid. */
.st-firstrun--span{grid-column:1/-1;align-items:center;text-align:center;}

/* Two-axis graph exists in exactly one place: an actual writing surface.
   Declared here so nobody reinvents the wallpaper; not used on these three
   screens. */
.st-writing-surface{
  background-color:var(--paper);
  background-image:
    linear-gradient(var(--rule) 1px,transparent 1px),
    linear-gradient(90deg,var(--rule) 1px,transparent 1px);
  background-size:48px 48px;
}

/* One idea per section. Sections divide with a hairline, never a colour block. */
.st-section{
  padding-block:var(--sec-y);
  border-top:1px solid var(--line);
  /* So an in-page anchor jump (a #id target inside a section) lands below
     the sticky header instead of under it. Matches the header's own
     rendered height with a little air. */
  scroll-margin-top:84px;
}
.st-section--first{border-top:0;}
/* Round 3: opt-in roomier rhythm (128px desktop, stepping down at both
   breakpoints below same as --sec-y does) for a page that wants more air
   than the 80px default. Nothing existing carries this class. */
.st-section--roomy{padding-block:var(--sec-y-roomy);}
/* Round 3 (teams): the opposite opt-in — a denser seam where a page wants
   two adjacent sections to read as one cluster rather than two equal
   80px-apart blocks. --s8 rather than a bespoke value, so the step still
   sits on the twelve-step scale. */
.st-section--tight{padding-block:var(--s8);}
/* A banded section separates one major block. At most twice per page. */
.st-section--band{background:var(--surface);}
/* A tinted section separates the page's one most important block with a real
   ground, in the line's own accent, at a low mix so text contrast is
   untouched. At most once per page.
   Round 3: radius added so the tint reads as a designed ground change
   confined to .st-wrap, not a stray square div. Purely additive (a rounded
   corner where there was a sharp one); nothing that depends on the sharp
   corner exists. */
.st-section--tint{background:color-mix(in srgb,var(--acc) 5%,var(--bg));border-radius:var(--r-xl);}
/* Round 4 (index): a tinted panel that closes the page sits straight against
   the footer's own top hairline, so its rounded bottom corners have nothing
   to sit in. Give it the page's own section rhythm as clearance. Only fires
   when the panel really is the last thing in main. */
main > .st-section--tint:last-child{margin-bottom:var(--sec-y);}

/* Round 5 (index): the same clearance the tinted panel got in round 4, for
   the bold block that replaced it. A .st-section--dark closing the page sits
   straight against the footer's own top hairline, so its rounded bottom
   corners have nothing to sit in and the slab reads as if it has been cut off
   rather than as if it ends. Give it the page's own section rhythm as
   clearance, and only when it really is the last thing in main: the "What you
   get" slab further up wants the ordinary section rhythm and must not pick
   this up. (Morris, 2026-08-29.) */
main > .st-section--dark:last-child{margin-bottom:var(--sec-y);}
/* Round 3 (index): an inverted dark band, for the one moment a page wants a
   surface contrast a light page cannot otherwise reach (a closing CTA).
   Same technique as the white-paper-on-dark-board rule in §16, run in
   reverse: re-pin the live tokens to the already-fully-defined dark
   palette on this subtree only, so every descendant (.st-btn, .st-lede,
   a focus ring) just works without a single hardcoded hex. AT MOST TWICE
   per page, and the second one only ever the closing ask. This said "at most
   once" until 2026-08-29. It changed because the front page needs a bold
   ground under its sign-up as well as under its proof, and because Ian had
   already ruled out the alternative, a pale tint. What the old limit really
   guarded against is a page of alternating stripes, so this is the limit that
   actually guards it: three is a stripe, two, on the proof and the ask with
   plain sections between them, is not. Full width by default — wrap the max-width content inside in
   .st-wrap, same as .st-rail and the final .st-cta already do. */
.st-section--dark{
  background:var(--d-bg); color:var(--d-ink);
  --bg:var(--d-bg);         --surface:var(--d-surface);   --rule:var(--d-rule);
  --paper:var(--d-paper);
  --line:var(--d-line);     --line2:var(--d-line2);       --line-ui:var(--d-line-ui);
  --ink:var(--d-ink);       --ink2:var(--d-ink2);         --mut:var(--d-mut);
  --green:var(--d-green);   --green-bg:var(--d-green-bg);
  --amber:var(--d-amber);   --amber-bg:var(--d-amber-bg);
  --cobalt:var(--d-cobalt); --cobalt-bg:var(--d-cobalt-bg);
  --oxide:var(--d-oxide);   --oxide-bg:var(--d-oxide-bg);
  --shadow:var(--d-shadow);
  --acc-line:var(--a-brand-line-d); --acc-text:var(--a-brand-text-d);
  /* NEW 1: the pen never lands on a solid dark block at the light-pen value
     (the live 3.01:1 defect). Any hand mark inside a solid dark slab takes
     --d-pen automatically: 7.77:1 on this slab's #17181A, 6.47:1 on the
     raised slab a dark page gets (#26282B). The old failure can no longer
     be authored. */
  --pen:var(--d-pen);
}
/* 2026-08-28 (Ian: "if we want to differentiate sections I want bold blocks").
   The rule above was written for a full-bleed band and had never been used by
   any page. Used the way index.html now uses it, inside <main class="st-wrap">,
   it needs three things the tint could do without because a 5% wash hides them:
   an inset, so text does not run to the edge of a solid ground; the tint's own
   radius, so the block reads as a designed slab rather than a stray rectangle;
   and no .st-section border-top, which would otherwise draw a straight line
   across the top of a rounded box. Only applies inside a wrap, so a genuine
   full-bleed use (none exists yet) is untouched. */
.st-wrap > .st-section--dark{
  border-radius:var(--r-xl);
  border-top:0;
  padding-inline:var(--s6);
}
@media (max-width:640px){
  .st-wrap > .st-section--dark{ padding-inline:var(--s4); }
}
/* .st-callout--honest deliberately sits FLUSH on the page (background:var(--bg))
   so the plain half of a pair reads as plain. Inside a bold block that stops
   meaning flush and starts meaning invisible: measured rgb(23,24,26) on a
   rgb(23,24,26) ground, against its --caution partner sitting one rung up. Two
   callouts side by side then differ for no reason a reader could name. On this
   ground it takes the same surface as every other card. */
.st-wrap > .st-section--dark .st-callout--honest{ background:var(--surface); }
.st-section--dark.line-stica, .line-stica .st-section--dark{ --acc-line:var(--a-stica-line-d); --acc-text:var(--a-stica-text-d); }
.st-section--dark.line-coach, .line-coach .st-section--dark{ --acc-line:var(--a-coach-line-d); --acc-text:var(--a-coach-text-d); }
/* …and on a page that is ALREADY dark, the rule above painted #17181A onto
   #17181A: measured 1.00:1 against the page, so the homepage's closing band
   disappeared completely the moment dark was switched on. Inversion has to
   run the other way here. The band becomes a RAISED slab: every live token
   steps one rung up the dark ramp it already owns, so the block reads as its
   own ground (1.20:1 against the page, better separation than the light
   theme's own .st-section--band gets at 1.05:1) with no new colour invented
   and no hex written by hand. Deliberately stops at --d-grid rather than
   going lighter: one rung further and the accent text inside drops under
   4.5:1. Whether the closing band should be LOUDER than this in dark is a
   design call, not a repair — a light band on a dark page is the literal
   inversion, and it is also a glare bomb at the foot of a night-time page. */
:root[data-theme="dark"] .st-section--dark{
  /* #26282B is the incumbent raised-slab step, kept literal on purpose: the
     ruling rename moves --d-rule to a much darker feint value (#191919),
     which is not this step. See NEW 2 in the tokens block. */
  background:#26282B; color:var(--d-ink);
  --bg:#26282B;              --surface:var(--d-line);      --rule:var(--d-line);
  --paper:var(--d-paper);
  --line:var(--d-line2);    --line2:var(--d-line-ui);
  --pen:var(--d-pen);
}
@media (prefers-color-scheme: dark){
  :root[data-theme="auto"] .st-section--dark{
    background:#26282B; color:var(--d-ink);
    --bg:#26282B;              --surface:var(--d-line);      --rule:var(--d-line);
  --paper:var(--d-paper);
    --line:var(--d-line2);    --line2:var(--d-line-ui);
    --pen:var(--d-pen);
  }
}

/* Column grids. These four, and nothing else. */
.st-cols-2{display:grid;grid-template-columns:1fr 1fr;gap:var(--s4);}
/* Round 3: opt-in top alignment. Default stretch is right when both
   columns are the same kind of card; it is wrong the moment one side is
   short text and the other is a tall card, because the short side then
   stretches into a bordered box with nothing in the lower half of it —
   a visibly empty container. Opt in per instance rather than flipping the
   shared default, since existing two-column grids elsewhere rely on stretch. */
.st-cols-2--top{align-items:start;}
.st-cols-3{display:grid;grid-template-columns:repeat(3,1fr);gap:var(--s4);}
.st-cols-4{display:grid;grid-template-columns:repeat(4,1fr);gap:var(--s4);}
.st-split{display:grid;grid-template-columns:1.15fr .85fr;gap:var(--s8);align-items:center;}
/* A grid item's default min-width is its content's min-content size, so an
   intrinsically wide image (a phone screenshot shot at real resolution)
   forces its track wider than the column and the page overflows sideways.
   min-width:0 lets the track (and the image inside it, which is already
   width:100%) shrink to the column instead. */
.st-split > *,.st-cols-2 > *,.st-cols-3 > *,.st-cols-4 > *,.st-hero-split > *,.st-spread > *{min-width:0;}

/* Two-voice reading spread: 7/5 machine column plus a dashed pen rail.
   This is the whole of what survived scenes.css. */
.st-spread{display:grid;grid-template-columns:7fr 5fr;gap:0 var(--s7);align-items:start;}
.st-spread__body{max-width:68ch;min-width:0;}
.st-spread__rail{
  position:relative;min-height:60px;padding-top:var(--s1);
  border-left:1px dashed var(--line2);padding-left:var(--s5);
  font-size:var(--fs-body-sm);color:var(--mut);
}
/* Decorative grid flourish in the rail. Hairline only, never carries meaning. */
.st-spread__mark{display:block;}
.st-spread__mark svg{overflow:visible;}
.st-spread__mark path,.st-spread__mark line,.st-spread__mark circle{
  fill:none;stroke:var(--line2);stroke-width:1;vector-effect:non-scaling-stroke;
}


/* =====================================================================
   6. TYPE
   One h1 per page. No skipped heading levels. Visual size comes from the
   class, semantics from the tag: if an h3 must look big, use
   <h3 class="st-h3-lg">, never <h2>.
   Measure caps are enforced, not advisory.
   Minimum size anywhere is 10px, and 10px is reserved for chip text.
   ===================================================================== */

/* Round 3: text-wrap:balance on the three headline-scale classes. No
   `text-wrap` existed anywhere in the sheet, so a display headline at 64px
   in a narrow hero column ragged badly (a short first line, an orphaned
   word closing line two). Supported browsers balance the rag; browsers
   without it just keep the old wrap, so this is a pure improvement with
   no fallback needed. Never added to .st-h3/.st-h4 — balance below display
   scale fights short, deliberately-terse card headings more than it helps. */
.st-display,.st-h1,.st-h2{text-wrap:balance;}
.st-display{font-size:var(--fs-display);font-weight:800;line-height:.94;letter-spacing:-.05em;color:var(--ink);margin:0 0 var(--s4);}
/* Round 3: an opt-in larger step for the one page that wants to lead on
   scale, without moving --fs-display itself under six live pages that
   have not been re-checked against it. Floor sits clear of --fs-h2's own
   38px ceiling at every width; ceiling clears the 84px bar a flagship
   hero needs on a 1440 canvas. */
.st-display--xl{font-size:clamp(44px,6.4vw,88px);}
.st-h1     {font-size:var(--fs-h1);font-weight:800;line-height:1;letter-spacing:-.045em;color:var(--ink);margin:0 0 var(--s4);}
.st-h2     {font-size:var(--fs-h2);font-weight:700;line-height:1.1;letter-spacing:-.042em;color:var(--ink);margin:0 0 var(--s3);}
.st-h3     {font-size:var(--fs-h3);font-weight:700;line-height:1.25;letter-spacing:-.5px;color:var(--ink);margin:0 0 var(--s2);}
.st-h3-lg  {font-size:var(--fs-h3-lg);font-weight:700;line-height:1.2;letter-spacing:-.7px;color:var(--ink);margin:0 0 var(--s2);}
.st-h4     {font-size:var(--fs-h4);font-weight:600;line-height:1.35;letter-spacing:-.3px;color:var(--ink);margin:0 0 var(--s2);}

.st-lede   {font-size:var(--fs-lede);font-weight:400;line-height:1.6;color:var(--ink2);max-width:52ch;margin:0 0 var(--s5);}
.st-body   {font-size:var(--fs-body);font-weight:400;line-height:1.6;color:var(--ink2);max-width:64ch;margin:0 0 var(--s4);}
.st-body-sm{font-size:var(--fs-body-sm);font-weight:400;line-height:1.55;color:var(--ink2);max-width:48ch;margin:0 0 var(--s3);}

/* The mono metadata rule: anything ABOUT the content is mono, small,
   uppercase, tracked out. Content itself is never mono. */
.st-eyebrow{font-family:var(--sans);font-size:var(--fs-eyebrow);font-weight:600;line-height:1.4;letter-spacing:.2px;text-transform:none;color:var(--mut);margin:0 0 var(--s3);display:flex;align-items:center;}
.st-eyebrow::before{content:"";display:inline-block;width:16px;height:3px;border-radius:2px;background:var(--pen);margin-right:var(--s2);flex:none;}
.st-eyebrow b{color:var(--acc-text);font-weight:600;}  /* ONE accent word, not a whole accent line */
.st-label  {font-family:var(--sans);font-size:var(--fs-label);font-weight:600;line-height:1.4;letter-spacing:.2px;text-transform:none;color:var(--mut);margin:0 0 var(--s2);}
/* Two measured contrast faults, both --mut read against a ground it was
   never tuned for: a card on the raised dark surface (4:1) and the eyebrow
   on the tinted section (4.3:1). --ink2 clears both without a new token. */
.st-section--dark .st-tile .st-label{color:var(--ink2);}
.st-section--tint .st-eyebrow{color:var(--ink2);}
.st-small  {font-family:var(--sans);font-size:var(--fs-small);font-weight:400;line-height:1.6;color:var(--mut);margin:0;}

/* The hand rule: Kalam is margins and captions. Never body copy, never a
   button, never an interface label, never a heading. Always tilted.
   Only ever a positive or clarifying note, never a warning. */
.st-note   {font-family:var(--hand);font-weight:700;font-size:var(--fs-note);line-height:1.05;color:var(--ink);display:inline-block;margin:0 0 var(--s4);}
.st-note-sm{font-family:var(--hand);font-weight:700;font-size:var(--fs-note-sm);line-height:1.15;color:var(--ink);display:inline-block;margin:0 0 var(--s3);}
.st-note-xs{font-family:var(--hand);font-weight:700;font-size:var(--fs-note-xs);line-height:1.2;color:var(--ink);display:inline-block;margin:0 0 var(--s2);}
.st-note--acc,.st-note-sm--acc,.st-note-xs--acc{color:var(--acc-text);}
/* A pen note sitting directly under a display heading. Two things eat the
   16px the heading leaves and nothing accounted for either. `.st-display`
   has line-height .94, so the glyph metrics of its last line sit about
   .11em BELOW the heading's own border box (6.8px at 64px). And a note
   carrying `data-tilt` grows its bounding box UPWARD by w*sin(tilt)/2, so
   the wider the note the higher it reaches (11.9px on the 681px note on
   thanks.html). Together they cost about 19px against a 16px gap, which is
   how a note landed 3px inside a heading's last line. Measured 2026-09-01,
   caught by tools/mark-overlap-check.mjs. The extra --s3 restores a real
   gap and holds for a tilted note up to roughly 1370px wide, well beyond
   anything the sheet can carry. */
.st-display + .st-note,.st-display + .st-note-sm,.st-display + .st-note-xs{margin-top:var(--s3);}
[data-tilt="-3"]{transform:rotate(-3deg);}
[data-tilt="-2"]{transform:rotate(-2deg);}
[data-tilt="-1"]{transform:rotate(-1deg);}
[data-tilt="1"] {transform:rotate(1deg);}
[data-tilt="2"] {transform:rotate(2deg);}
[data-tilt="3"] {transform:rotate(3deg);}

.st-mono{font-family:var(--mono);}
.st-rule{display:block;height:4px;width:96px;background:var(--acc-line);margin:var(--s3) 0 var(--s5);border-radius:2px;}

/* Section heading block: eyebrow + h2 + lede. Sizes from the scale.
   No inline overrides, on any page, ever. */
.st-sechead{margin:0 0 var(--s6);}
.st-sechead--centre{text-align:center;}
.st-sechead--centre .st-lede{margin-inline:auto;}


/* =====================================================================
   7. SHELL: skip link, header, nav, mobile nav, brand, footer
   ===================================================================== */

/* 7.1 Skip link — required on every page, first element in <body>. */
.st-skip{
  position:absolute;left:var(--s2);top:-100px;z-index:100;
  background:var(--surface);color:var(--ink);
  border:1px solid var(--line-ui);border-radius:var(--r-md);
  padding:var(--s3) var(--s4);font-size:var(--fs-body-sm);font-weight:600;
  text-decoration:none;transition:top var(--dur-2) var(--ease);
}
.st-skip:focus{top:var(--s2);}

/* 7.2 Header. One definition. One nav item list. */
.st-head{
  position:sticky;top:0;z-index:40;
  /* On the ground, so --line2. See .st-sheet above. */
  border-bottom:1px solid var(--line2);
  background:color-mix(in srgb,var(--bg) 88%,transparent);
  backdrop-filter:blur(8px);
  -webkit-backdrop-filter:blur(8px);
}
.st-head__inner{
  max-width:var(--w-page);margin-inline:auto;padding:var(--s3) var(--gutter);
  display:flex;align-items:center;justify-content:space-between;gap:var(--s5);
}
.st-nav{display:flex;align-items:center;gap:var(--s5);}
.st-nav a{
  font-size:var(--fs-body-sm);color:var(--ink2);text-decoration:none;
  transition:color var(--dur-1) var(--ease);
}
.st-nav a:hover{color:var(--ink);}
/* Current page. Every page must mark its own nav item with aria-current="page". */
.st-nav a[aria-current="page"]{
  color:var(--ink);font-weight:600;
  box-shadow:inset 0 -2px 0 0 var(--acc-line);
}

/* 7.6 Wordmark. The one thing already built as a single component. */
.st-brand{
  /* Tracking is -1% at every size the wordmark is used (brand spec section
     1): -0.01em, not a px value pinned to one font-size. Fixed 2026-09-01
     after the header's -1px measured as -3.33% at 30px. */
  font-weight:700;font-size:30px;letter-spacing:-0.01em;color:var(--ink);
  text-decoration:none;line-height:1;white-space:nowrap;flex:none;
  --logo-hl:color-mix(in srgb,var(--acc) var(--hl-mix),transparent);
  background-image:linear-gradient(var(--logo-hl),var(--logo-hl));
  /* Position and size are in em, tied to the glyph box itself, never to the
     element's own box height — so it cannot detach when the header around
     it grows (a wrapped nav, a taller row). Fixed 2026-08-06, round 2. */
  background-repeat:no-repeat;background-position:left .78em;background-size:100% .52em;
  border-radius:var(--r-sm);padding:0 var(--s1) 1px;
}
.st-brand .dot{
  /* Drawn circle, diameter 0.16em, centred on the baseline, clearance
     around the mark of half the diameter (brand spec section 1). All three
     numbers are in em against the dot's own inherited font-size, so they
     hold at 30px in the header and 26px on narrow screens without a
     second, hand-copied override. vertical-align:-0.08em (half the 0.16em
     box, negative so the box sits lower) puts the circle's centre, not its
     edge, on the baseline. The "." text stays in the markup for
     accessibility and is hidden with colour, not font-size:0, because a
     zeroed font-size would zero the em math below it too. Fixed
     2026-09-01: the header dot landed on spec (0.167em) by coincidence
     only, and the narrow-screen 4px override was under spec at 0.133em. */
  display:inline-block;width:0.16em;height:0.16em;border-radius:var(--r-dot);
  background:var(--acc);margin-left:0.08em;vertical-align:-0.08em;
  overflow:hidden;color:transparent;
}

/* 7.3 Mobile nav. Hidden above 640px; the toggle is the only nav below it. */
.st-navtoggle{
  display:none;
  align-items:center;justify-content:center;gap:var(--s2);
  min-width:44px;min-height:44px;padding:0 var(--s3);
  font-family:var(--sans);font-size:var(--fs-eyebrow);font-weight:600;text-transform:none;
  color:var(--ink);background:transparent;
  border:1px solid var(--line2);border-radius:var(--r-md);cursor:pointer;
}
.st-navtoggle__bars{display:block;width:16px;height:10px;position:relative;}
.st-navtoggle__bars::before,.st-navtoggle__bars::after{
  content:"";position:absolute;left:0;width:100%;height:1.5px;background:currentColor;
  transition:transform var(--dur-2) var(--ease),opacity var(--dur-2) var(--ease);
}
.st-navtoggle__bars::before{top:0;}
.st-navtoggle__bars::after{bottom:0;}
.st-navtoggle[aria-expanded="true"] .st-navtoggle__bars::before{transform:translateY(4px) rotate(45deg);}
.st-navtoggle[aria-expanded="true"] .st-navtoggle__bars::after{transform:translateY(-4px) rotate(-45deg);}

.st-navpanel{
  display:none;
  border-top:1px solid var(--line);
  background:var(--surface);
}
.st-navpanel[data-open="true"]{display:block;}
.st-navpanel ul{list-style:none;margin:0;padding:var(--s2) var(--gutter) var(--s4);}
.st-navpanel li + li{border-top:1px solid var(--line);}
.st-navpanel a{
  display:flex;align-items:center;min-height:44px;
  font-size:var(--fs-body);color:var(--ink2);text-decoration:none;
}
.st-navpanel a[aria-current="page"]{color:var(--ink);font-weight:600;}
.st-navpanel .st-btn,.st-navpanel .st-btn--sec{width:100%;justify-content:center;margin-top:var(--s3);}

/* 7.4 Footer. One definition, one link list, same width as the content. */
/* The footer stands on the ground, not on a slab of its own. It carried
   --surface until 2026-08-29, which was white by day: with the sheet above it
   also white and full-bleed, the page read as two white blocks rather than as
   one sheet lying on ruled paper. Its own top hairline still separates it. */
/* Transparent, so this line is drawn straight onto the ground: --line2. */
.st-footer{border-top:1px solid var(--line2);background:transparent;}
.st-footer__inner{
  max-width:var(--w-page);margin-inline:auto;padding:var(--s6) var(--gutter);
  display:flex;align-items:center;justify-content:space-between;gap:var(--s5);flex-wrap:wrap;
}
.st-footer__nav{display:flex;gap:var(--s5);flex-wrap:wrap;}
.st-footer__nav a{font-size:13px;color:var(--ink2);text-decoration:none;}
.st-footer__nav a:hover{color:var(--ink);text-decoration:underline;text-underline-offset:3px;}
.st-footer__tag{font-family:var(--sans);font-size:var(--fs-small);color:var(--mut);margin:0;}


/* =====================================================================
   8. BUTTONS
   Four, and four states each. Minimum target 44x44 everywhere.
   Buttons carry NO shadow at rest: shadows signal objecthood, not
   hierarchy. If you are reaching for a shadow to make a button look
   important, use the accent or the hierarchy instead.
   ===================================================================== */

.st-btn,.st-btn--sec,.st-btn--ghost,.st-btn--pill{
  display:inline-flex;align-items:center;justify-content:center;gap:var(--s2);
  font-family:var(--sans);text-decoration:none;cursor:pointer;
  border:1px solid transparent;
  /* Round 2: a button never mirrors its container. Cards and tiles use
     --r-lg; a button sitting inside one now reads as its own object
     rather than a nested panel. */
  border-radius:var(--r-md);
  min-height:44px;
  transition:background-color var(--dur-1) var(--ease),
             border-color var(--dur-1) var(--ease),
             color var(--dur-1) var(--ease);
}

/* Primary — the ONE action in view */
.st-btn{
  background:var(--acc-fill);color:#fff;
  font-size:15px;font-weight:600;padding:13px 22px;
}
.st-btn:hover{background:color-mix(in srgb,var(--acc-fill) 88%,#000);}
.st-btn:active{background:color-mix(in srgb,var(--acc-fill) 78%,#000);}

/* Secondary — beside a primary. A filled tint, not a hairline box, so it
   never disappears against a --surface card that has its own hairline
   border in the same neutral family. */
.st-btn--sec{
  background:color-mix(in srgb,var(--ink) 5%,transparent);
  color:var(--ink);border-color:var(--line-ui);
  font-size:15px;font-weight:600;padding:13px 20px;
}
.st-btn--sec:hover{border-color:var(--acc-line);background:color-mix(in srgb,var(--ink) 9%,transparent);}
.st-btn--sec.is-picked,.st-btn--sec[aria-pressed="true"]{
  border-color:var(--acc-line);color:var(--acc-text);
  background:color-mix(in srgb,var(--acc) 8%,transparent);
}

/* Outline — an accent-bordered action that must read as live, not muted.
   Round 3: for a free/starter tier sitting beside a paid one, where --sec's
   neutral tint reads as switched off. Accent border and text, transparent
   fill, so it never competes with the page's one primary (.st-btn). */
.st-btn--outline{
  background:transparent;color:var(--acc-text);border-color:var(--acc-line);
  font-size:15px;font-weight:600;padding:13px 20px;
}
.st-btn--outline:hover{background:color-mix(in srgb,var(--acc) 8%,transparent);}

/* Tertiary — an in-flow link that acts as an action */
.st-btn--ghost{
  background:transparent;color:var(--ink);padding:var(--s3) 0;min-height:44px;
  font-size:15px;font-weight:600;
  text-decoration:underline;text-underline-offset:3px;text-decoration-thickness:1px;
}
.st-btn--ghost:hover{color:var(--acc-text);}

/* Pill — the header nav's Sign in / Start here pair. NOWHERE ELSE. */
.st-btn--pill{
  border-radius:var(--r-full);font-size:13px;font-weight:600;
  padding:9px 16px;min-height:40px;
  background:var(--acc-fill);color:#fff;
}
.st-btn--pill.st-btn--pill-outline{
  background:transparent;color:var(--ink);border-color:var(--line-ui);padding:8px 15px;
}
.st-btn--pill:hover{background:color-mix(in srgb,var(--acc-fill) 88%,#000);}
.st-btn--pill.st-btn--pill-outline:hover{background:var(--surface);border-color:var(--line-ui);}

/* The shell repaints its own buttons, so say the label colour again here.
   `.st-nav a` and `.st-navpanel a` (§7) are class-plus-type, so they outrank
   the plain `.st-btn` / `.st-btn--pill` class rules above and were painting a
   FILLED button's white label in --ink2. Measured on the header's own "Start
   free": 2.55:1 in light and 2.88:1 in dark, i.e. the site's most-clicked
   button failed in both modes, and it only surfaced when dark was switched on.
   Re-stated at the specificity the shell uses, for the filled variants only.
   The outline pill keeps --ink, which is what a transparent fill wants. */
.st-nav a.st-btn,.st-nav a.st-btn--pill,
.st-navpanel a.st-btn,.st-navpanel a.st-btn--pill{color:#fff;}
.st-nav a.st-btn--pill-outline,.st-navpanel a.st-btn--pill-outline,
.st-navpanel a.st-btn--sec,.st-navpanel a.st-btn--ghost{color:var(--ink);}
.st-navpanel a.st-btn--outline{color:var(--acc-text);}

/* Disabled. Always set aria-disabled too; a disabled <a> is not a thing. */
.st-btn[disabled],.st-btn--sec[disabled],.st-btn--ghost[disabled],.st-btn--pill[disabled],
.st-btn[aria-disabled="true"],.st-btn--sec[aria-disabled="true"],
.st-btn--ghost[aria-disabled="true"],.st-btn--pill[aria-disabled="true"]{
  opacity:.55;cursor:not-allowed;pointer-events:none;
}

/* Pending. The label swaps, the width does NOT, so the layout cannot jump.
   Markup: <button aria-busy="true" style="min-width:<measured>px"> or wrap
   the label in .st-btn__label and let the ellipsis replace it. */
[aria-busy="true"] .st-btn__label{visibility:hidden;}
[aria-busy="true"]{position:relative;}
[aria-busy="true"]::after{
  content:"…";position:absolute;inset:0;
  display:flex;align-items:center;justify-content:center;
  font-size:20px;line-height:1;
}

.st-btnrow{display:flex;gap:var(--s3);flex-wrap:wrap;align-items:center;}


/* =====================================================================
   9. TILE / CARD  — one component, four variants
   Replaces seventeen classes describing one thing.
   No shadow at rest. Ever.
   ===================================================================== */

.st-tile{
  background:var(--surface);
  border:1px solid var(--line);
  border-radius:var(--r-lg);
  padding:var(--s5);
  display:flex;flex-direction:column;gap:var(--s3);
}
.st-tile > :last-child{margin-bottom:0;}

/* Whole card is an anchor */
.st-tile--link{
  text-decoration:none;color:inherit;
  transition:border-color var(--dur-1) var(--ease),transform var(--dur-2) var(--ease);
}
.st-tile--link:hover{border-color:var(--acc-line);transform:translateY(-2px);}
.st-tile:focus-within{border-color:var(--acc-line);}

.st-tile--source{border-left:3px solid var(--green);}   /* verified against source */
.st-tile--accent{border-left:3px solid var(--acc-line);}
.st-tile--flat{background:var(--bg);}                   /* on a banded section */
/* Provisional or optional block. Two per page maximum. */
.st-tile--draft{border-style:dashed;border-color:var(--line2);}
/* Round 3 (teams): a persistent door mark. A hover-only lift is invisible
   on a touch screen and, at rest, a link tile and a static tile read as
   the same object. The arrow is always on; only its position animates. */
.st-tile--arrow{position:relative;padding-right:calc(var(--s5) + 22px);}
.st-tile--arrow::after{
  content:"\2192";position:absolute;right:var(--s5);bottom:var(--s5);
  font-size:16px;color:var(--acc-text);
  transition:transform var(--dur-2) var(--ease);
}
.st-tile--arrow:hover::after{transform:translateX(3px);}

/* 9b. SHOT — a real screen framed as a small object: a delivered artefact,
   not decorative photography (see §16's placeholder-imagery rule, which
   this does not fall under; the image behind it is genuine product output).
   Whole-image link; the border and shadow read as "this is a page", the
   same shadow language as .st-panel uses for the one hero-scale surface. */
.st-shot{
  display:block;border:1px solid var(--line);border-radius:var(--r-lg);
  overflow:hidden;box-shadow:var(--shadow);background:var(--bg);
  transition:border-color var(--dur-1) var(--ease);
}
.st-shot:hover{border-color:var(--acc-line);}
.st-shot img{display:block;width:100%;height:auto;}

/* 7.23 Audience door: a tile with a mono who-line, a body line, one action.
   On the homepage the door's left border is the ONLY place a line accent
   appears outside its own pages. */
.st-door{}
.st-door--stica{border-left:3px solid var(--a-stica-line);}
.st-door--coach{border-left:3px solid var(--a-coach-line);}
.st-door--teal {border-left:3px solid var(--acc-line);}
/* Round 2: each door hovers in its own line accent, not whatever accent
   the current page happens to carry — the generic .st-tile--link:hover
   rule above was turning the cobalt and violet cards teal on hover. */
.st-door--stica:hover{border-color:var(--a-stica-line);}
.st-door--coach:hover{border-color:var(--a-coach-line);}
.st-door--teal:hover {border-color:var(--acc-line);}
/* Round 3: sans, not mono. The page already carries a section eyebrow in
   mono; a second tracked-mono voice on the same screen (this line, three
   times over) read as the same grey noise repeated, not two roles. This
   kicker keeps the small-caps, letter-spaced treatment but in the sans
   face, so it reads as a distinct "card kicker" register. */
.st-door__who{font-family:var(--sans);font-size:13px;font-weight:600;letter-spacing:.3px;text-transform:uppercase;color:var(--mut);margin:0;}
/* Round 2: the price is the answer to the page's one required question.
   Never .st-small for it — that is a caption size reserved for metadata,
   not the decision a cold visitor came to make.
   Round 3: pinned to the bottom of the card. Door cards carry a variable
   amount of copy above the price, and the row they sit in is equal-height
   by CSS Grid's default stretch — so without this, the price line landed
   at a different y-position on every card in the same row. */
.st-door__price{font-size:15px;font-weight:500;color:var(--ink2);margin:0;margin-top:auto;}
.st-door__price b{font-size:20px;font-weight:700;color:var(--ink);letter-spacing:-.02em;}

/* 7.22 Metric strip: mono label then one claim, in a bordered surface row. */
.st-strip{
  display:flex;align-items:baseline;gap:var(--s4);flex-wrap:wrap;
  background:var(--surface);border:1px solid var(--line);border-radius:var(--r-lg);
  padding:var(--s4) var(--s5);
}
.st-strip__claim{font-size:17.5px;font-weight:600;letter-spacing:-.3px;line-height:1.35;color:var(--ink);margin:0;}

/* 7.22b Glance list. Round 3: a compact set of anchor rows for a real
   price/fact summary in a hero panel, built for pricing/index.html — never
   a chip pretending to be navigation. One line per fact, the value pinned
   right, each row a full 44px tap target. */
.st-glance{list-style:none;margin:0;padding:0;display:flex;flex-direction:column;}
.st-glance li + li{border-top:1px solid var(--line);}
.st-glance a{
  display:flex;align-items:center;justify-content:space-between;gap:var(--s3);
  min-height:44px;padding:var(--s2) 0;
  text-decoration:none;color:var(--ink);
  font-size:var(--fs-body-sm);font-weight:600;
  transition:color var(--dur-1) var(--ease);
}
.st-glance a:hover,.st-glance a:hover .st-glance__val{color:var(--acc-text);}
.st-glance__val{font-family:var(--mono);font-size:var(--fs-small);font-weight:500;color:var(--mut);transition:color var(--dur-1) var(--ease);}


/* =====================================================================
   10. CHIP
   10px mono uppercase with a 6px leading dot. A chip NEVER carries meaning
   by colour alone — the word is always there.
   ===================================================================== */

.st-chip{
  display:inline-flex;align-items:center;gap:6px;
  font-family:var(--sans);font-size:var(--fs-small);font-weight:600;
  line-height:1.2;letter-spacing:0;text-transform:none;
  padding:4px 9px;border-radius:var(--r-md);
  background:var(--surface);color:var(--mut);border:1px solid var(--line);
}
.st-chip::before{content:"";width:6px;height:6px;border-radius:var(--r-dot);background:currentColor;flex:none;}
/* A chip used as a real, clickable jump link (not a status tag) needs the
   44px tap target every interactive thing on the site gets. Text stays
   chip-size; the hit area grows around it. */
.st-chip--link{
  min-height:44px;padding:0 var(--s3);text-decoration:none;
  transition:border-color var(--dur-1) var(--ease),color var(--dur-1) var(--ease);
}
.st-chip--link:hover{border-color:var(--acc-line);color:var(--acc-text);}
.st-chip--verified{background:var(--green-bg);color:var(--green);border-color:transparent;}
.st-chip--review  {background:var(--amber-bg);color:var(--amber);border-color:transparent;}
.st-chip--source  {background:var(--cobalt-bg);color:var(--cobalt);border-color:transparent;}
.st-chip--rejected{background:var(--oxide-bg);color:var(--oxide);border-color:transparent;}
.st-chip--mute    {background:var(--bg);color:var(--mut);}
/* Large chip: for a grading system carrying the primary meaning of a page
   (e.g. an evidence grade), not a secondary tag. Steps up to the next
   type step on the scale (--fs-small) rather than inventing a new size. */
.st-chip--lg{font-size:var(--fs-small);padding:5px 11px;gap:7px;}
.st-chip--lg::before{width:7px;height:7px;}


/* =====================================================================
   11. NUMBERED STEP, and the timeline that is the same shape with state
   Replaces seven implementations of one motif.
   ===================================================================== */

.st-steps{list-style:none;margin:0;padding:0;display:flex;flex-direction:column;gap:var(--s5);}
.st-step{--step-badge:36px;display:grid;grid-template-columns:auto 1fr;gap:var(--s4);align-items:start;}
.st-step__num{
  width:var(--step-badge);height:var(--step-badge);flex:none;
  display:inline-flex;align-items:center;justify-content:center;
  border-radius:var(--r-dot);background:var(--bg);
  border:2px solid var(--acc-line);
  /* Round 3 (teams): 14px mono in --acc-text read as an empty ring at
     mobile widths — the numeral is the one piece of information the shape
     exists to carry, so it gets the readable weight; the ring alone still
     carries the accent identity. */
  font-family:var(--mono);font-weight:700;font-size:16px;color:var(--ink);
}
.st-step__body > :last-child{margin-bottom:0;}

/* 7.19 Timeline: the step list with three states. */
.st-timeline .st-step__num{border-color:var(--line2);color:var(--mut);}
.st-timeline .is-done    .st-step__num{border-color:var(--green);color:var(--green);}
/* .st-step__num's own background is var(--bg), the desk, by design (the
   badge floats free of whatever it sits on). The base rule above already
   colours the numeral --ink for exactly that reason ("the ring alone still
   carries the accent identity"); this state was overriding it back to
   --acc-text, which measured 4.23:1 on the desk on consulting/index.html.
   Kept: the accent border, which only needs the 3:1 non-text bar. */
.st-timeline .is-current .st-step__num{border-color:var(--acc-line);}
.st-timeline .is-upcoming .st-step__body{opacity:.7;}

/* Round 3: opt-in connector. A hairline down the numeral column, so a
   step list reads as one continuous line rather than loose, disconnected
   circles — the difference between a numbered list and an actual
   timeline. Off by default because most .st-steps uses (three cards in a
   row elsewhere) are not meant to read as a single connected line. */
.st-steps--connected .st-step{position:relative;}
/* The line was drawn on the BADGE at a fixed height of --s5 + 2px, which is
   the gap between step BOXES only. Measured on the live page, badge bottom to
   next badge top is 86 to 108px, because the line has to clear the whole step
   body, heading plus paragraph, not just the gap. So 26px of a 86-108px run
   was painted and the rest was empty: the broken stubs Ian saw (2026-09-01).
   Drawn on the step instead, it spans whatever that step turns out to be
   tall. --step-badge is read here and by .st-step__num above, so the two
   cannot drift. */
.st-steps--connected .st-step:not(:last-child)::after{
  content:"";position:absolute;
  top:calc(var(--step-badge) + var(--s2));
  bottom:calc(-1 * (var(--s5) - var(--s2)));
  left:calc(var(--step-badge) / 2);width:1px;
  background:var(--line2);transform:translateX(-50%);
}
.st-steps--connected .st-step__num{position:relative;}


/* =====================================================================
   12. PRICE BLOCK
   One component. The featured variant carries the word "Recommended" as
   text, never colour alone.
   ===================================================================== */

/* Round 2: fixed, consistent card width instead of 1fr-stretch, so a
   two-tier row and a three-tier row read as the same module rather than
   two different widths one scroll apart. Centred so a short row (one or
   two cards) doesn't sprawl edge to edge. auto-fit + a capped max keeps
   the 640px and 920px breakpoint overrides below in full control. */
.st-prices{
  display:grid;gap:var(--s4);
  grid-template-columns:repeat(auto-fit,minmax(240px,290px));
  justify-content:center;
  align-items:stretch;
}
.st-price{
  position:relative;
  background:var(--surface);border:1px solid var(--line);border-radius:var(--r-lg);
  /* Top padding reserves the badge slot on EVERY card, badged or not, so
     __name and __amount sit on one baseline across a row regardless of
     which cards carry a badge. */
  padding:40px var(--s5) var(--s5);
  display:flex;flex-direction:column;gap:var(--s3);
  transition:border-color var(--dur-1) var(--ease),transform var(--dur-2) var(--ease);
}
.st-price:hover{border-color:var(--acc-line);transform:translateY(-2px);}
/* Round 3: a shared row-track grid, opted into only where subgrid is
   supported (every evergreen browser; the file already relies on :has()
   at the same bar). Every card in a row now shares six row tracks — name,
   amount, what, list, note, action — so badge/name/price/what-line/list-
   start land on one baseline across the row regardless of how much text
   any one card carries, and the action lands on one shared bottom line
   without needing margin-top:auto to fake it. Flex remains the fallback
   for anything older. */
/* Final pass, 2026-08-06: the six-row block below is now scoped to
   .st-prices > .st-price. It used to match EVERY .st-price, which broke a
   card standing on its own outside a price row: on /teams the hero price
   card sits in the .st-hero-split grid, inherited `grid-row:span 6` from
   that grid instead, and opened a dead half-screen gap between the name
   and the £295. Shared row tracks are a property of a ROW of cards, so
   they belong to the row, and a lone card now falls back to the flex
   layout above, which is what it wants. */
@supports (grid-template-rows:subgrid){
  .st-prices{grid-template-rows:repeat(6,auto);}
  .st-prices > .st-price{display:grid;grid-template-rows:subgrid;grid-row:span 6;gap:var(--s3);}
  .st-prices > .st-price > .st-price__name{grid-row:1;align-self:start;}
  .st-prices > .st-price > .st-price__amount{grid-row:2;align-self:start;}
  .st-prices > .st-price > .st-price__what{grid-row:3;align-self:start;}
  .st-prices > .st-price > .st-price__list{grid-row:4;align-self:start;}
  .st-prices > .st-price > .st-price__note{grid-row:5;align-self:start;}
  .st-prices > .st-price > .st-btn,.st-prices > .st-price > .st-btn--sec,
  .st-prices > .st-price > .st-btn--ghost,.st-prices > .st-price > .st-btn--outline{grid-row:6;align-self:end;}
}
/* Recommended: real weight, never a shadow (rule: no shadow at rest). A
   tinted surface and the accent border carry it.
   Round 3: was border-width:2px plus +6px padding on every side to
   compensate — that shrank the featured card's content box relative to
   its neighbours by 2x the extra border, which is exactly what pushed
   "/ month" onto its own line on the one card carrying the badge. An
   inset box-shadow reads as the same thicker ring without touching the
   box model, so all three cards keep an identical content width. */
.st-price--featured{
  border-color:var(--acc-line);
  box-shadow:inset 0 0 0 1px var(--acc-line);
  background:color-mix(in srgb,var(--acc) 5%,var(--surface));
}
/* The badge sits astride the card's top edge, out of flow, so it never
   pushes __name/__amount down — the fix for the staircase. */
.st-price__badge{
  position:absolute;top:0;left:24px;transform:translateY(-50%);
  font-family:var(--sans);font-size:var(--fs-chip);font-weight:600;text-transform:none;
  border-radius:var(--r-md);padding:4px 9px;white-space:nowrap;
  background:var(--acc-fill);color:#fff;
}
/* Recommendation gets the fill (above). Scarcity/availability gets an
   outline in --mut — two different facts never wear the same pill. */
.st-price__badge--outline{background:var(--surface);color:var(--mut);border:1px solid var(--mut);padding:3px 8px;}
.st-price__name{font-family:var(--sans);font-size:var(--fs-label);font-weight:600;text-transform:none;color:var(--mut);margin:0;}
.st-price__amount{
  font-size:var(--fs-price);font-weight:800;letter-spacing:-.03em;line-height:1;color:var(--ink);margin:0;
  font-variant-numeric:tabular-nums;
}
.st-price__unit{
  font-family:var(--sans);font-size:var(--fs-body-sm);font-weight:500;color:var(--mut);
  margin-left:4px;vertical-align:6px;
  /* Round 3: the single highest-priority figure on the page must never
     wrap onto its own orphan line under a narrowed content box. */
  white-space:nowrap;
}
.st-price__what{font-size:var(--fs-body-sm);line-height:1.55;color:var(--ink2);margin:0;}
.st-price__list{list-style:none;margin:0;padding:0;display:flex;flex-direction:column;gap:var(--s2);}
.st-price__list li{font-size:var(--fs-body-sm);line-height:1.5;color:var(--ink2);padding-left:var(--s5);position:relative;}
.st-price__list li::before{content:"";position:absolute;left:var(--s2);top:.62em;width:6px;height:6px;border-radius:var(--r-dot);background:var(--acc-line);}
/* The annual note is a conversion lever, not a version stamp: body face,
   the figure in --ink, straight under __unit — never floating above the
   button as its own orphan line. */
.st-price__note{font-family:var(--sans);font-size:var(--fs-body-sm);color:var(--mut);margin:0;}
.st-price__note strong{color:var(--ink);font-weight:700;}
/* The action is always the LAST child of a price card, so every button in a
   row lines up on one baseline no matter how uneven the cards above are.
   Put __note before the button, never after it. Removing flex:1 from
   __list (above) means any leftover height collects here, above the
   button, as one clean gap — not stretched mid-list. This is the flex
   fallback; the @supports block above overrides it with a shared grid row
   wherever subgrid is available. */
.st-price > .st-btn,.st-price > .st-btn--sec,.st-price > .st-btn--ghost,.st-price > .st-btn--outline{margin-top:auto;}
@media (max-width:640px){
  .st-price{padding:36px var(--s4) var(--s4);}
  .st-price--featured{padding:36px var(--s4) var(--s4);}
  .st-price__badge{left:16px;}
}

/* Round 3: a row that should own its measure edge-to-edge and left-align
   under a left-aligned heading, rather than centre a short row in the
   middle of the page — built for pricing/index.html, where two, three and
   one-card rows all sat at different widths and different alignments.
   Default .st-prices (centred, capped at 290px) is untouched, so every
   existing page keeps its current layout. Wider min/max so three cards
   land near 315-320px in the 992px content column, and a lone card still
   reads as a real object rather than a shrunk-down list item. */
.st-prices--wide{
  grid-template-columns:repeat(auto-fit,minmax(280px,360px));
  justify-content:start;
}

/* Round 3: monthly/annual billing toggle. Any input[name="billing"] (see
   stica.js §6) flips this attribute on <body>; the CSS below only ever
   swaps which [data-period] span shows, so it is a content change, not a
   motion effect, and needs no entry in the motion section. Two or more
   segmented controls sharing name="billing" (e.g. one per subscription
   section) stay in sync for free, because a radio group is scoped by name
   across the whole document regardless of where each input sits in the DOM. */
[data-period="annual"]{display:none;}
body[data-billing="annual"] [data-period="monthly"]{display:none;}
body[data-billing="annual"] [data-period="annual"]{display:inline;}


/* =====================================================================
   13. HERO
   Two shapes. Every hero on the site is one of these two.
   ===================================================================== */

.st-hero{padding-block:var(--s10) var(--s9);}
.st-hero__cta{margin-top:var(--s5);}
.st-hero-split{
  padding-block:var(--s10) var(--s9);
  display:grid;grid-template-columns:1.15fr .85fr;gap:var(--s8);align-items:center;
}
/* Round 3: opt-in roomier hero (176/128 instead of 96/80). The existing
   ≤920px rule below (padding-block:var(--s8) var(--s7)) also targets
   .st-hero-split and wins there on source order at equal specificity, so
   this collapses to the same mobile padding every other hero uses —
   nothing extra to maintain per breakpoint. */
.st-hero-split--roomy{padding-block:var(--s12) var(--sec-y-roomy);}
/* Round 3: opt-in top alignment for a hero whose panel is a real content
   object (a timeline, a multi-line block) rather than a short note — the
   default centring floats a short panel in the middle of a taller column
   and reads as an orphaned widget. */
.st-hero-split--top{align-items:start;}
/* The one hero-scale panel per page. It, and the sticky header, are the
   only things on a page that carry --shadow. */
.st-panel{
  /* --line2: this card lies on the graph ground, where --line is 1.101:1
     and therefore no edge. See .st-sheet in stica.css. (2026-08-29.) */
  background:var(--surface);border:1px solid var(--line2);border-radius:var(--r-xl);
  padding:var(--s6);box-shadow:var(--shadow);
}
/* Round 3 (index): a light window-chrome bar for a panel that is standing
   in for a real product screen. Three dots, nothing else — no fabricated
   photograph, no invented metric, just the frame that tells a viewer
   "this is app content" rather than "this is a card of text". */
.st-panel__chrome{display:flex;gap:6px;margin-bottom:var(--s5);}
.st-panel__chrome span{width:10px;height:10px;border-radius:var(--r-dot);background:var(--line2);}


/* =====================================================================
   14. FORM
   Native controls only: a real <a>, a real <button>, a real <details>.
   No <div> with a click handler, anywhere, ever.
   Input boundaries are --line-ui (>=3:1), never --line2 (1.50:1).
   ===================================================================== */

.st-field{display:flex;flex-direction:column;gap:var(--s2);margin-bottom:var(--s5);}
.st-field__label{
  font-family:var(--sans);font-size:var(--fs-label);text-transform:none;
  color:var(--mut);font-weight:600;
}
/* Required: the mark carries a text equivalent, not a bare asterisk. */
.st-field__req{color:var(--oxide);font-weight:700;}
.st-field__req::after{content:" required";font-size:var(--fs-label);letter-spacing:1px;}

.st-input,.st-select,.st-textarea{
  font-family:var(--sans);font-size:var(--fs-body);line-height:1.5;color:var(--ink);
  background:var(--surface);
  border:1px solid var(--line-ui);
  border-radius:var(--r-md);
  padding:12px 14px;min-height:44px;width:100%;
  transition:border-color var(--dur-1) var(--ease);
}
.st-textarea{min-height:120px;resize:vertical;}
/* A textarea standing in for what used to be a one-line input: it starts at
   the same height as an .st-input and grows with what is typed or loaded,
   never a fixed multi-line box. Sized by JS (autosize()), never by resize. */
.st-textarea--line{min-height:44px;resize:none;overflow:hidden;}
.st-input:hover,.st-select:hover,.st-textarea:hover{border-color:var(--ink2);}
.st-input[disabled],.st-select[disabled],.st-textarea[disabled]{opacity:.55;cursor:not-allowed;background:var(--bg);}
.st-input::placeholder,.st-textarea::placeholder{color:var(--mut);}

/* Error. Per-field, wired with aria-invalid and aria-describedby.
   One server error line at the bottom of a form is not feedback. */
.st-input[aria-invalid="true"],.st-select[aria-invalid="true"],.st-textarea[aria-invalid="true"]{
  border-color:var(--oxide);border-width:2px;
}
.st-help {font-family:var(--sans);font-size:var(--fs-small);color:var(--mut);margin:0;}
.st-error{font-family:var(--sans);font-size:var(--fs-small);color:var(--oxide);margin:0;display:flex;gap:6px;align-items:baseline;}
.st-error::before{content:"×";font-size:14px;line-height:1;}
.st-error:empty{display:none;}

.st-check{display:flex;align-items:flex-start;gap:var(--s3);cursor:pointer;}
.st-check input{width:20px;height:20px;margin:2px 0 0;accent-color:var(--acc-fill);flex:none;}
.st-check span{font-size:var(--fs-body-sm);line-height:1.5;color:var(--ink2);}

/* Disclosure — native <details>. */
.st-disclosure{border:1px solid var(--line);border-radius:var(--r-lg);background:var(--surface);}
.st-disclosure > summary{
  cursor:pointer;list-style:none;padding:var(--s3) var(--s4);min-height:44px;
  display:flex;align-items:center;gap:var(--s2);
  font-size:var(--fs-body-sm);font-weight:600;color:var(--ink);
}
.st-disclosure > summary::-webkit-details-marker{display:none;}
.st-disclosure > summary::after{content:"+";margin-left:auto;font-family:var(--mono);color:var(--mut);}
.st-disclosure[open] > summary::after{content:"–";}
.st-disclosure__body{padding:0 var(--s4) var(--s4);}

/* Segmented control — a real radio group. role="tablist" is the only
   alternative. Two buttons with an .on class and no ARIA is not one. */
.st-segment{display:inline-flex;border:1px solid var(--line-ui);border-radius:var(--r-md);overflow:hidden;}
.st-segment label{
  display:inline-flex;align-items:center;justify-content:center;
  min-height:44px;padding:0 var(--s4);cursor:pointer;
  font-size:var(--fs-body-sm);font-weight:500;color:var(--ink2);background:var(--surface);
  transition:background-color var(--dur-1) var(--ease),color var(--dur-1) var(--ease);
}
.st-segment label + label{border-left:1px solid var(--line-ui);}
.st-segment input{position:absolute;opacity:0;width:1px;height:1px;}
.st-segment input:checked + span,.st-segment label:has(input:checked){background:var(--acc-fill);color:#fff;}
.st-segment label:has(input:focus-visible){outline:3px solid var(--acc-line);outline-offset:-3px;}

/* File drop zone and list */
.st-drop{
  border:1px dashed var(--line-ui);border-radius:var(--r-lg);background:var(--bg);
  padding:var(--s6);text-align:center;
  transition:border-color var(--dur-1) var(--ease),background-color var(--dur-1) var(--ease);
}
.st-drop.is-over{border-color:var(--acc-line);background:color-mix(in srgb,var(--acc) 6%,transparent);}
.st-filelist{list-style:none;margin:var(--s3) 0 0;padding:0;display:flex;flex-direction:column;gap:var(--s2);}
.st-filelist li{
  display:flex;align-items:center;gap:var(--s3);
  border:1px solid var(--line);border-radius:var(--r-md);padding:var(--s2) var(--s3);
  font-family:var(--mono);font-size:var(--fs-small);color:var(--ink2);background:var(--surface);
}
.st-filelist button{margin-left:auto;min-width:44px;min-height:44px;background:transparent;border:0;color:var(--mut);cursor:pointer;font-size:16px;}
.st-filelist button:hover{color:var(--oxide);}


/* =====================================================================
   15. CALLOUT, EVIDENCE, RAIL, CTA, TABLE
   ===================================================================== */

/* 7.16 + 7.21 Honesty callout, banner and alert are one component.
   The --honest variant is a signature of the positioning: it says plainly
   what is not proven. */
.st-callout{
  /* --line2 because the --honest variant below fills itself with the ground
     colour, so its edge has to hold against the ground as well as the sheet. */
  border:1px solid var(--line2);border-left:3px solid var(--acc-line);
  border-radius:var(--r-lg);background:var(--surface);
  padding:var(--s4) var(--s5);
}
.st-callout > :last-child{margin-bottom:0;}
.st-callout--info   {border-left-color:var(--cobalt);}
.st-callout--caution{border-left-color:var(--amber);}
.st-callout--honest {border-left-color:var(--line-ui);background:var(--bg);}
.st-callout__label{font-family:var(--sans);font-size:var(--fs-label);font-weight:600;text-transform:none;color:var(--mut);margin:0 0 var(--s2);}
/* Round 3: a callout sitting directly above a price row needs real
   clearance, or a badge astride the top of the first card touches the
   callout's own edge. Scoped to the adjacent-sibling case only. */
.st-callout + .st-prices{margin-top:var(--s7);}

/* 7.15 Evidence card: a claim, its grade, its source. Available on every
   page, not just the method page. Left border is green by default (the
   common case, a pooled meta-analysis) with two modifiers so the rail
   always agrees with the grade chip inside it, never contradicts it. */
.st-evidence{background:var(--surface);border:1px solid var(--line);border-left:3px solid var(--green);border-radius:var(--r-lg);padding:var(--s5);}
.st-evidence--review{border-left-color:var(--amber);}
.st-evidence--rejected{border-left-color:var(--oxide);}
.st-evidence__head{display:flex;align-items:center;justify-content:space-between;gap:var(--s3);margin-bottom:var(--s3);flex-wrap:wrap;}
/* One measure for the whole card, so the claim and the copy under it don't
   step in and out at different widths. Round 3: bumped 16->17px, the
   title (.st-h3-lg in markup) to 24px and the claim to 500/17px, so the
   card has a real two-step hierarchy instead of a 4px gap at one weight. */
.st-evidence__claim{font-size:17px;font-weight:500;line-height:1.4;letter-spacing:-.3px;color:var(--ink);max-width:62ch;margin:0 0 var(--s3);}
.st-evidence .st-body-sm{max-width:62ch;}
/* Round 3: citations were 11px mono, a legibility failure for running
   text (contrast measured fine; size and face did not). Sans, 12.5px. */
.st-evidence__prov{font-family:var(--sans);font-size:12.5px;color:var(--mut);line-height:1.7;margin:0;}
/* Round 3: the "In Stica, this means" line was a third identical
   .st-body-sm paragraph, distinguished only by a bold lead-in. Give it a
   hairline and a tinted inset so it reads as the card's own conclusion,
   not another caveat. One card (habit timelines) was missing it; every
   evidence card on the page now carries one. */
.st-evidence__stica{
  margin-top:var(--s3);padding-top:var(--s3);border-top:1px solid var(--line);
}
.st-evidence__stica-label{
  font-family:var(--sans);font-size:var(--fs-label);font-weight:600;
  text-transform:none;color:var(--acc-text);margin:0 0 4px;
}
.st-evidence__stica p{font-size:var(--fs-body-sm);line-height:1.55;color:var(--ink2);max-width:62ch;margin:0;}

/* 7.15b Effect-size bar: the numeric encoding an evidence card needs
   beside its claim. A fixed track, a filled portion sized by inline style
   (the one sanctioned case of a one-off data-driven position — see
   BUILD-BRIEF §1), and an optional tick marking a reference point. The
   fill colour matches the card's grade, the same three-state logic as the
   chip and the left border, so all three signals on a card always agree.
   Only ever used for measures on one shared 0-1 scale (g, d) — a card
   whose number is a different kind of statistic (r, R², a day count)
   does not get this mark; see .st-range below for a range instead. */
.st-effect{display:flex;align-items:center;gap:var(--s3);margin:var(--s3) 0 var(--s4);}
.st-effect__track{
  position:relative;display:block;flex:1;max-width:200px;height:8px;
  background:var(--line);border-radius:var(--r-full);
}
.st-effect__fill{position:absolute;top:0;left:0;bottom:0;border-radius:var(--r-full);background:var(--green);}
.st-effect--review .st-effect__fill{background:var(--amber);}
.st-effect--rejected .st-effect__fill{background:var(--oxide);}
.st-effect__tick{position:absolute;top:-3px;bottom:-3px;width:2px;background:var(--line-ui);}
.st-effect__val{font-family:var(--mono);font-size:var(--fs-small);color:var(--ink2);white-space:nowrap;flex:none;}

/* 7.15c Range mark: two labelled endpoints and an average tick, for a
   measure that is a spread (e.g. 18-254 days) rather than a 0-1 effect
   size. Visually distinct from .st-effect on purpose — two dots, not a
   fill-from-zero bar — so it can never be misread as the same scale. */
.st-range{display:flex;align-items:center;gap:var(--s3);margin:var(--s3) 0 var(--s4);}
.st-range__track{
  position:relative;display:block;flex:1;max-width:200px;height:8px;
  background:var(--line);border-radius:var(--r-full);
}
.st-range__bar{position:absolute;top:0;bottom:0;border-radius:var(--r-full);background:var(--amber);opacity:.45;}
.st-range__dot{
  position:absolute;top:50%;width:9px;height:9px;border-radius:var(--r-dot);
  background:var(--amber);transform:translate(-50%,-50%);
}
.st-range__tick{position:absolute;top:-3px;bottom:-3px;width:2px;background:var(--line-ui);}
.st-range__val{font-family:var(--mono);font-size:var(--fs-small);color:var(--ink2);white-space:nowrap;flex:none;}

/* 7.25 Cross-sell rail: the "part of the line" strip. */
.st-rail{border-top:1px solid var(--line);background:var(--surface);}
.st-rail__inner{
  max-width:var(--w-page);margin-inline:auto;padding:var(--s8) var(--gutter);
  display:flex;align-items:center;gap:var(--s5);flex-wrap:wrap;
}
.st-rail__links{display:flex;gap:var(--s3);flex-wrap:wrap;}
.st-rail__links a{
  font-size:13.5px;font-weight:600;text-decoration:none;color:var(--ink);
  border:1px solid var(--line);border-radius:var(--r-lg);padding:var(--s3) var(--s4);
  background:var(--bg);min-height:44px;display:inline-flex;align-items:center;
  transition:border-color var(--dur-1) var(--ease);
}
.st-rail__links a:hover{border-color:var(--acc-line);}

/* 7.26 Final CTA block. */
.st-cta{text-align:center;padding-block:var(--s10);}
/* Round 3 (teams): the one page-closing CTA that is meant to close a sale
   gets the roomy step instead of the shared 96px default. The ≤920px and
   ≤640px overrides below (both plain .st-cta, equal specificity, later in
   the cascade) still win at those widths, so this only ever adds air on
   a desktop canvas that has it to spare. */
.st-cta--roomy{padding-block:var(--sec-y-roomy);}
/* Round 3: four live pages (index, method, coaches, and now learn) were
   each setting this inline per instance to centre the lede's own box
   under a centred heading. Same fix as .st-sechead--centre already gets;
   promoted so no page has to say it by hand. */
.st-cta .st-lede{margin-inline:auto;}
.st-cta__flourish{width:260px;height:auto;margin:0 auto var(--s2);display:block;}
/* Round 4 (index): the closing CTA nested inside the section above it, so the
   pitch and the action share one ground instead of sitting either side of a
   176px stack of two sections' padding. It keeps its own centring and its
   flourish; it drops the page-closing padding, which is the outer section's
   job now, and takes a hairline instead so the two halves still read as two.
   Only meaningful as a child of a .st-section. */
.st-cta--inset{
  padding-block:0;
  margin-top:var(--s7);
  padding-top:var(--s7);
  border-top:1px solid var(--line);
}

/* Round 5 (index): the closing block on the page's own left edge, as the
   whole content of its own section, after Ian on 2026-08-29: "Look at the
   alignment of the final sections, that is shockingly bad."

   What he was looking at, measured at 1280. "The deal" started its eyebrow,
   its heading, its lede and its small print at x=144, which is where the
   hero, both other section headings and all four numbered steps start. The
   closing ask sitting underneath it was centred, so its lede started at
   x=312 and its buttons at x=522, and nothing between the two blocks marked
   the change. The page dropped its left edge at the exact moment it asks
   somebody to sign up, and that is what reads as broken: not either block on
   its own, but the join.

   Centring "the deal" was the other way to give the foot one edge. It is
   worse. It centres a 68ch paragraph of small print, which is a bad shape to
   read, and it leaves the closing block the only centred thing on a page
   that is left-aligned from the first line of the hero down.

   Why the modifier also zeroes the block's own vertical padding. Coming left
   fixes the alignment and leaves the rest of the complaint standing: a
   four-line left-aligned block arriving after 1,109px of continuous
   left-aligned prose is still just more prose, so the close now takes a
   ground of its own (.st-section--dark, the bold block this page already
   uses once at the top). The section around it therefore owns the page
   rhythm, --sec-y, and .st-cta's own 96px on top of that would put 176px of
   dark ground above the heading. This is not .st-cta--inset with the border
   taken off: that class was written the day before for the opposite
   arrangement, a CTA nested under a sibling inside one shared section, and
   it draws a hairline to separate the two halves. Here the slab's own edge
   is that line, so there is nothing left for a hairline to do.

   Works anywhere, not only here: any page whose closing ask should sit on
   the page's own left edge instead of floating in the middle. Nothing else
   in the sheet changes, and .st-cta unmodified is still centred, which is
   what every other page's close still is. (Morris, 2026-08-29.) */
.st-cta--spine{padding-block:0;text-align:left;}
.st-cta--spine .st-lede{margin-inline:0;}
.st-cta--spine .st-cta__flourish{margin-inline:0;}
/* Visible by default (rule 15: with JS off, everything is visible). The
   path carries pathLength="1" so dasharray/dashoffset are both in the
   0..1 unit space regardless of the SVG's own drawn length. Only under
   .st-motion-ready does it start hidden and draw in on reveal. */
.st-cta__flourish path{
  fill:none;stroke:var(--acc-line);stroke-width:2;stroke-linecap:round;
  stroke-dasharray:1;stroke-dashoffset:0;
}
.st-motion-ready .st-cta__flourish path{stroke-dashoffset:1;}
.st-motion-ready .st-cta__flourish.st-drawn path{
  animation:st-draw var(--dur-6) var(--ease) forwards;
}
@keyframes st-draw{to{stroke-dashoffset:0;}}

/* 7.20 Table. A wide table scrolls inside itself and never scrolls the page.
   A row may open with <th scope="row"> instead of <td> for accessible row
   headers; it reads as body copy, not as a column-header label, so it stays
   legible at the sizes people actually read. */
.st-tablewrap{overflow-x:auto;-webkit-overflow-scrolling:touch;}
/* min-width is what makes overflow-x:auto actually engage on a plain table
   (no th[scope=row]) at narrow widths, rather than squeezing every column
   until it wraps onto three lines. width:100% still governs the wide case. */
.st-table{width:100%;min-width:640px;border-collapse:collapse;font-size:var(--fs-body-sm);}
.st-table th{
  font-family:var(--sans);font-size:var(--fs-label);font-weight:600;
  letter-spacing:.2px;text-transform:none;color:var(--mut);
  text-align:left;padding:var(--s3) var(--s4) var(--s3) 0;border-bottom:1px solid var(--line);
}
.st-table tbody th{
  font-family:var(--sans);font-size:var(--fs-body-sm);font-weight:500;
  letter-spacing:normal;text-transform:none;color:var(--ink2);
}
.st-table td{padding:var(--s3) var(--s4) var(--s3) 0;border-bottom:1px solid var(--line);color:var(--ink2);vertical-align:top;}
.st-table tr:last-child td{border-bottom:0;}
/* Round 3: the row-header cell (<th scope="row">) carries its own
   border-bottom from the base .st-table th rule above and was never
   cleared on the last row, so the closing hairline stopped dead at column
   two instead of running the full row width. */
.st-table tr:last-child th{border-bottom:0;}
/* Below 640px a table that uses accessible row headers restacks as paired
   rows: each cell shows its own column label, so nothing is read by
   position alone. Opt in with <th scope="row"> on the first cell of every
   <tr> and data-label="<column header>" on every <td> after it. Scoped to
   :has(th[scope="row"]) on purpose, so a plain data table with no row
   headers (e.g. the evidence table on /method) keeps its own header row
   and simple horizontal scroll instead of losing its column context. */
@media (max-width:640px){
  /* Round 3: the base .st-table rule sets min-width:640px so overflow-x
     scrolling engages on an ordinary table. This variant restacks instead
     of scrolling, but that min-width was never reset, so the table stayed
     640px wide inside a 390px viewport, every value's flex-based
     space-between threw it off to the right edge at x≈640, and the whole
     section rendered as row headings with nothing beside them. */
  .st-table:has(th[scope="row"]){min-width:0;}
  .st-tablewrap:has(.st-table:has(th[scope="row"])){overflow-x:visible;}
  .st-table:has(th[scope="row"]) thead{
    position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;
    clip:rect(0 0 0 0);white-space:nowrap;border:0;
  }
  .st-table:has(th[scope="row"]),
  .st-table:has(th[scope="row"]) tbody,
  .st-table:has(th[scope="row"]) tr,
  .st-table:has(th[scope="row"]) th,
  .st-table:has(th[scope="row"]) td{display:block;width:100%;}
  .st-table:has(th[scope="row"]) tr{padding:var(--s3) 0;border-bottom:1px solid var(--line);}
  .st-table:has(th[scope="row"]) tr:last-child{border-bottom:0;}
  .st-table:has(th[scope="row"]) tbody th{
    font-weight:700;color:var(--ink);padding:0 0 var(--s2);border-bottom:0;
  }
  .st-table:has(th[scope="row"]) td{
    border-bottom:0;padding:2px 0;
    display:flex;align-items:baseline;justify-content:space-between;gap:var(--s3);
  }
  .st-table:has(th[scope="row"]) td::before{
    content:attr(data-label);flex:none;
    font-family:var(--sans);font-size:var(--fs-label);font-weight:600;
    text-transform:none;color:var(--mut);
  }
}

/* Breadcrumb — for any page two levels deep. */
.st-crumb{font-family:var(--sans);font-size:var(--fs-small);font-weight:600;color:var(--mut);margin:0 0 var(--s4);}
.st-crumb a{color:var(--mut);text-decoration:none;}
.st-crumb a:hover{color:var(--ink2);text-decoration:underline;text-underline-offset:3px;}
.st-crumb span{margin:0 var(--s2);}


/* =====================================================================
   16. PAPER OBJECTS
   Shadows signal objecthood. Paper gets --shadow-stuck or --shadow-paper;
   tiles, cards, chips and buttons get nothing at rest.
   ONE .st-stuck per page, maximum, on a standalone note or artefact,
   never on a grid tile or a repeated list.
   The white paper rule: in dark mode the board darkens, pinned paper does
   not. .st-stuck and .st-paper carry their own light palette.
   ===================================================================== */

.st-stuck{
  /* Round 3: sizing promoted from a per-instance inline style (the only
     page that used the component was setting max-width and padding by
     hand) to a sensible default. A page with a real reason to differ can
     still override width via a wrapping element with a max-width utility;
     this removes the need to do it inline in the common case. */
  /* position is not an author knob on paper objects: an inline
     position:absolute is exactly what put a sticky over a paragraph on the
     homepage (Ian, 2026-08-31). A sticky or taped card is an object IN the
     flow of its lane. */
  position:relative !important;background:var(--surface);color:var(--ink);
  max-width:340px;padding:var(--s5);
  border:1px solid color-mix(in srgb,var(--ink) 6%,transparent);
  border-radius:var(--r-sm);
  transform:rotate(var(--tilt,-1.4deg));
  background-image:
    linear-gradient(180deg,rgba(255,255,255,.6),rgba(255,255,255,0) 9px),
    linear-gradient(0deg,rgba(40,34,20,.035),rgba(40,34,20,0) 14px);
  box-shadow:var(--shadow-stuck);
  transition:transform var(--dur-3) var(--ease),box-shadow var(--dur-3) var(--ease);
}
/* Same convention as .st-tile and .st-step__body: the last line inside
   never carries its own trailing margin, so the padding box closes clean
   without an inline margin:0 per instance. */
.st-stuck > :last-child{margin-bottom:0;}
.st-stuck:hover{
  transform:rotate(calc(var(--tilt,-1.4deg) * .35)) translateY(-3px) scale(1.014);
  box-shadow:0 2px 3px rgba(28,24,14,.06),2px 5px 9px rgba(28,24,14,.08),
             5px 12px 22px rgba(28,24,14,.10),9px 24px 40px rgba(28,24,14,.11),
             14px 36px 60px rgba(28,24,14,.07);
}
.st-stuck::before{
  content:"";position:absolute;top:-9px;left:calc(50% + var(--tapex,0px));
  transform:translateX(-50%) rotate(var(--tape,-1.5deg));
  width:98px;height:28px;z-index:2;
  background:linear-gradient(180deg,rgba(255,255,255,.34),rgba(255,255,255,.05) 48%,rgba(70,58,28,.06)),
             repeating-linear-gradient(90deg,rgba(70,58,28,.05) 0 1px,transparent 1px 4px),
             rgba(214,201,158,.5);
  box-shadow:0 2px 5px rgba(28,24,14,.14);
  -webkit-mask-image:linear-gradient(90deg,transparent,#000 11%,#000 89%,transparent);
  mask-image:linear-gradient(90deg,transparent,#000 11%,#000 89%,transparent);
}
.st-stuck--pin::before{
  width:18px;height:18px;border-radius:var(--r-dot);top:-7px;z-index:3;
  background:radial-gradient(circle at 36% 30%,#ffb3b3 0 10%,#ff7a7a 32%,var(--acc) 62%,
             color-mix(in srgb,var(--acc) 62%,#000) 100%);
  box-shadow:0 3px 6px rgba(28,24,14,.4),inset -2px -2px 4px rgba(0,0,0,.35),
             inset 2px 2px 3px rgba(255,255,255,.6);
  -webkit-mask-image:none;mask-image:none;
}
/* The white paper rule, both dark entry points. */
:root[data-theme="dark"] .st-stuck{
  --surface:#FCFBF7; --ink:#111110; --ink2:#33332B; --mut:#6B6B61; --line:#E2E2DC;
  --pen:var(--l-pen);
  background:#FCFBF7;border-color:rgba(0,0,0,.14);
  box-shadow:0 1px 2px rgba(0,0,0,.5),4px 10px 22px rgba(0,0,0,.55),10px 26px 50px rgba(0,0,0,.42);
}
@media (prefers-color-scheme: dark){
  :root[data-theme="auto"] .st-stuck{
    --surface:#FCFBF7; --ink:#111110; --ink2:#33332B; --mut:#6B6B61; --line:#E2E2DC;
    --pen:var(--l-pen);
    background:#FCFBF7;border-color:rgba(0,0,0,.14);
    box-shadow:0 1px 2px rgba(0,0,0,.5),4px 10px 22px rgba(0,0,0,.55),10px 26px 50px rgba(0,0,0,.42);
  }
}

/* Torn-paper machinery. Kept, but do NOT build a page that depends on it
   until real photography exists. Placeholder imagery must not ship. */
.st-photo{position:relative;display:inline-block;}
.st-paper{background:#fff;padding:10px 10px 4px;filter:var(--shadow-paper);}
.st-photo .win{display:block;line-height:0;overflow:hidden;}
.st-photo .win img{width:100%;height:100%;object-fit:cover;display:block;}
.st-photo .cap{font-family:var(--hand);font-weight:700;color:#111110;padding:9px 4px 2px;line-height:1.05;background:#fff;}
.st-photo .cap small{font-family:var(--mono);font-weight:400;font-size:var(--fs-chip);letter-spacing:1px;color:#6B6B61;display:block;margin-top:4px;text-transform:uppercase;}
.st-paper.tear-left{clip-path:polygon(6% 0%,100% 0%,100% 100%,4% 100%,3.59% 100%,0.12% 98.95%,1.03% 95.72%,0.25% 92.63%,3.79% 89.87%,3.95% 85.9%,0.14% 83.26%,0.16% 80.25%,3.52% 76.05%,0.21% 71.75%,3.63% 66.64%,2.64% 61.49%,0.4% 58.78%,2.36% 54.38%,0.09% 49.67%,1.18% 46.84%,1.09% 43.45%,2.58% 40.6%,0.46% 35.94%,0.48% 31.7%,3.58% 27.77%,2.61% 24.69%,1.19% 20.57%,0.33% 16.65%,0.79% 12.83%,0.77% 7.83%,1.01% 3.78%,0.56% 0.0%);padding-left:16px;}
.st-paper.tear-right{clip-path:polygon(0% 0%,96.07% 0.0%,99.71% 3.46%,99.69% 6.83%,97.48% 9.62%,97.39% 12.45%,99.33% 16.57%,96.15% 19.8%,97.32% 23.96%,97.48% 27.53%,99.56% 31.31%,99.84% 36.4%,98.93% 40.26%,99.21% 44.35%,97.74% 49.2%,99.98% 52.28%,99.16% 55.28%,99.97% 60.24%,99.42% 64.97%,99.62% 68.22%,99.91% 71.31%,99.12% 75.83%,99.12% 80.88%,99.05% 83.99%,99.58% 89.06%,98.92% 93.95%,99.5% 98.12%,98.96% 100%,0% 100%);padding-right:16px;}
.st-paper.tear-top{clip-path:polygon(0.0% 3.42%,4.22% 3.83%,7.82% 0.79%,11.93% 1.07%,16.12% 2.98%,18.93% 0.97%,23.19% 1.06%,28.37% 2.45%,33.26% 0.6%,37.75% 1.02%,41.36% 2.5%,45.87% 2.59%,49.98% 0.5%,53.73% 3.71%,58.51% 1.18%,61.33% 3.77%,65.88% 0.05%,68.55% 3.62%,72.72% 3.02%,76.57% 0.03%,79.77% 2.28%,84.18% 0.76%,88.07% 0.76%,92.27% 0.97%,97.27% 3.06%,100% 3.05%,100% 100%,0% 100%);padding-top:16px;}
.st-paper.tear-bottom{clip-path:polygon(0% 0%,100% 0%,100% 99.4%,99.01% 96.84%,95.21% 98.84%,90.15% 99.75%,85.48% 98.99%,80.6% 99.63%,76.52% 98.99%,72.94% 99.23%,67.97% 96.49%,63.74% 97.4%,59.97% 98.87%,57.03% 98.99%,52.55% 99.38%,47.52% 97.53%,44.7% 99.79%,39.74% 99.92%,35.08% 96.34%,31.46% 99.34%,26.89% 99.9%,22.7% 98.97%,18.74% 99.62%,14.33% 99.36%,10.83% 98.82%,7.52% 96.18%,4.46% 99.02%,0.0% 99.75%);padding-bottom:14px;}
.st-tape{position:absolute;width:90px;height:23px;background:rgba(231,219,176,.74);border-left:1px dashed rgba(0,0,0,.07);border-right:1px dashed rgba(0,0,0,.07);}
.st-pin{position:absolute;width:15px;height:15px;border-radius:var(--r-dot);background:var(--acc);box-shadow:0 2px 4px rgba(0,0,0,.35),inset -2px -2px 3px rgba(0,0,0,.25);}
.st-pin::after{content:"";position:absolute;top:3px;left:3px;width:5px;height:5px;border-radius:var(--r-dot);background:rgba(255,255,255,.6);}


/* =====================================================================
   16A. THE HAND — the annotation kit
   Ian, 2026-08-29: "the coloured hand written font ... the human
   annotation of the content, with arrows and sketching", plus taped-on
   cards at an angle and sticky notes.

   The idea is one idea. Everything in this section is a mark a PERSON
   made on top of finished work: a note in the margin, an arrow pointing
   at the thing being talked about, a ring round a detail, a sticky note
   somebody left. It is never chrome, never a control, and never how a
   page says something is wrong.

   Four rules bind the whole section.

   1. The pen is --pen, one ink on every product line. It does not take
      the line accent, because an accent-coloured mark reads as design
      rather than as somebody's hand. It is not --oxide either: oxide
      means a fault, and §6's hand rule says a note is only ever positive
      or clarifying.
   2. A mark is decoration and must never be the only carrier of meaning.
      Whatever the arrow points at says it in words too. Marks are
      aria-hidden and have no text of their own.
   3. Marks and sticky notes are OBJECTS on the page, so like the pinned
      scrap in §16 they keep their own light colours when the board goes
      dark. The pen is the exception: it has a dark value, because it is
      ink on whatever it is written on, and at night it is written on the
      black sheet.
   4. Sparingly, per section. A section (one .st-section, or one card in
      an app) may carry at most one sticky note (.st-postit) - pen notes
      are not counted against this cap - and at most one mark (arrow,
      ring, underline, brace, tick or star). At most three stickies on a
      deliberate .st-board, and still at most one taped card per page. A
      long page may therefore carry several marks, and should: a
      marked-up page is the brand. But two marks in one section read as
      decoration, and a page covered in marks is a page nobody believes
      anyone marked.
   ===================================================================== */

/* 16A.1 The hand, in pen. §6 declares .st-note / -sm / -xs and the --acc
   variant; this is the third colour, and the one that means a person
   wrote it rather than the brand did. Always tilted, same as the others. */
.st-note--pen,.st-note-sm--pen,.st-note-xs--pen{color:var(--pen);}

/* 16A.2 Marks. Each is one hand-drawn SVG carried as a mask, so the
   shape is declared once here and takes its colour from CSS: --pen by
   default, --ink or the line accent by variant. Nothing animates: §17
   allows opacity and transform, so a mark that should arrive with its
   section takes .st-reveal like anything else. Never a drawn-on effect
   built from a property that is not on §17's list.

   Geometry: --mark-w / --mark-h come from the mark itself. --mark-s
   scales it (0.6 for a caption-sized arrow, 1.6 for one across a hero).
   --turn rotates it, and rotation is how you get the other seven
   directions rather than seven more mask images. */
.st-mark{
  display:inline-block;vertical-align:middle;flex:none;
  width:calc(var(--mark-w) * var(--mark-s,1));
  height:calc(var(--mark-h) * var(--mark-s,1));
  background-color:var(--pen);
  -webkit-mask-repeat:no-repeat;mask-repeat:no-repeat;
  -webkit-mask-size:100% 100%;mask-size:100% 100%;
  -webkit-mask-position:center;mask-position:center;
  transform:rotate(var(--turn,0deg));
}
.st-mark--ink{background-color:var(--ink);}
.st-mark--acc{background-color:var(--acc-text);}
.st-mark--mut{background-color:var(--mut);}

.st-mark--arrow{--mark-w:120px;--mark-h:60px;-webkit-mask-image:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 120 60' fill='none' stroke='%23000' stroke-width='5.5' stroke-linecap='round' stroke-linejoin='round' preserveAspectRatio='none'%3E%3Cpath d='M6 47C28 47 50 43 68 33 84 24 98 18 112 15'/%3E%3Cpath d='M112 15 96 8'/%3E%3Cpath d='M112 15 100 27'/%3E%3C/svg%3E");mask-image:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 120 60' fill='none' stroke='%23000' stroke-width='5.5' stroke-linecap='round' stroke-linejoin='round' preserveAspectRatio='none'%3E%3Cpath d='M6 47C28 47 50 43 68 33 84 24 98 18 112 15'/%3E%3Cpath d='M112 15 96 8'/%3E%3Cpath d='M112 15 100 27'/%3E%3C/svg%3E");}
.st-mark--arrow-sm{--mark-w:64px;--mark-h:40px;-webkit-mask-image:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 64 40' fill='none' stroke='%23000' stroke-width='6' stroke-linecap='round' stroke-linejoin='round' preserveAspectRatio='none'%3E%3Cpath d='M5 32C18 32 32 27 44 18 50 13 55 10 59 9'/%3E%3Cpath d='M59 9 47 5'/%3E%3Cpath d='M59 9 51 18'/%3E%3C/svg%3E");mask-image:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 64 40' fill='none' stroke='%23000' stroke-width='6' stroke-linecap='round' stroke-linejoin='round' preserveAspectRatio='none'%3E%3Cpath d='M5 32C18 32 32 27 44 18 50 13 55 10 59 9'/%3E%3Cpath d='M59 9 47 5'/%3E%3Cpath d='M59 9 51 18'/%3E%3C/svg%3E");}
.st-mark--ring{--mark-w:140px;--mark-h:74px;-webkit-mask-image:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 140 74' fill='none' stroke='%23000' stroke-width='5' stroke-linecap='round' stroke-linejoin='round' preserveAspectRatio='none'%3E%3Cpath d='M106 12C64 0 16 11 10 33 4 55 46 68 84 64 118 60 136 45 128 27 122 14 100 8 78 10'/%3E%3C/svg%3E");mask-image:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 140 74' fill='none' stroke='%23000' stroke-width='5' stroke-linecap='round' stroke-linejoin='round' preserveAspectRatio='none'%3E%3Cpath d='M106 12C64 0 16 11 10 33 4 55 46 68 84 64 118 60 136 45 128 27 122 14 100 8 78 10'/%3E%3C/svg%3E");}
.st-mark--underline{--mark-w:160px;--mark-h:20px;-webkit-mask-image:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 160 20' fill='none' stroke='%23000' stroke-width='4.5' stroke-linecap='round' stroke-linejoin='round' preserveAspectRatio='none'%3E%3Cpath d='M5 8C42 3 96 5 155 7'/%3E%3Cpath d='M12 16C50 12 104 13 146 12'/%3E%3C/svg%3E");mask-image:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 160 20' fill='none' stroke='%23000' stroke-width='4.5' stroke-linecap='round' stroke-linejoin='round' preserveAspectRatio='none'%3E%3Cpath d='M5 8C42 3 96 5 155 7'/%3E%3Cpath d='M12 16C50 12 104 13 146 12'/%3E%3C/svg%3E");}
.st-mark--brace{--mark-w:30px;--mark-h:140px;-webkit-mask-image:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 30 140' fill='none' stroke='%23000' stroke-width='4.5' stroke-linecap='round' stroke-linejoin='round' preserveAspectRatio='none'%3E%3Cpath d='M23 5C13 8 15 27 13 47 12 62 7 66 4 70 7 74 12 78 13 93 15 113 13 132 23 135'/%3E%3C/svg%3E");mask-image:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 30 140' fill='none' stroke='%23000' stroke-width='4.5' stroke-linecap='round' stroke-linejoin='round' preserveAspectRatio='none'%3E%3Cpath d='M23 5C13 8 15 27 13 47 12 62 7 66 4 70 7 74 12 78 13 93 15 113 13 132 23 135'/%3E%3C/svg%3E");}
.st-mark--tick{--mark-w:44px;--mark-h:37px;-webkit-mask-image:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 60 50' fill='none' stroke='%23000' stroke-width='6.5' stroke-linecap='round' stroke-linejoin='round' preserveAspectRatio='none'%3E%3Cpath d='M5 26C12 32 17 38 23 45 31 30 43 12 56 4'/%3E%3C/svg%3E");mask-image:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 60 50' fill='none' stroke='%23000' stroke-width='6.5' stroke-linecap='round' stroke-linejoin='round' preserveAspectRatio='none'%3E%3Cpath d='M5 26C12 32 17 38 23 45 31 30 43 12 56 4'/%3E%3C/svg%3E");}
.st-mark--star{--mark-w:30px;--mark-h:30px;-webkit-mask-image:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 40 40' fill='none' stroke='%23000' stroke-width='5' stroke-linecap='round' stroke-linejoin='round' preserveAspectRatio='none'%3E%3Cpath d='M20 4 20 36'/%3E%3Cpath d='M7 11 33 29'/%3E%3Cpath d='M33 11 7 29'/%3E%3C/svg%3E");mask-image:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 40 40' fill='none' stroke='%23000' stroke-width='5' stroke-linecap='round' stroke-linejoin='round' preserveAspectRatio='none'%3E%3Cpath d='M20 4 20 36'/%3E%3Cpath d='M7 11 33 29'/%3E%3Cpath d='M33 11 7 29'/%3E%3C/svg%3E");}

/* 16A.3 Placing a mark over something. Wrap the thing in .st-annot and
   the mark takes --x / --y as its top-left, in whatever units suit.
   The wrapper is the positioning context and nothing else: it adds no
   padding, no border and no shadow, so the object inside is unchanged.

   THE HALO, and why it is automatic here and nowhere else. A pen mark
   laid over something disappears the moment it crosses a line its own
   colour: another mark, a red button in a screenshot, the drawn frame in
   16A.4. So anything PLACED over something gets a thin halo in the
   paper's own colour, which is what a real mark has against real paper
   at the edge of the ink. A mark sitting inline beside text never gets
   one, because there is nothing under it to be lost against and a halo
   in open space reads as a sticker.

   --halo is the colour, --paper by default. Override it per instance
   over a picture whose own ground is nothing like the page's: a mark on
   a dark screenshot in day mode wants a dark halo, not a white one. */
.st-annot{position:relative;display:inline-block;max-width:100%;}
.st-annot > .st-mark,.st-annot > .st-note,.st-annot > .st-note-sm,.st-annot > .st-note-xs{
  position:absolute;left:var(--x,0);top:var(--y,0);margin:0;z-index:2;pointer-events:none;
}
.st-annot > .st-mark{
  filter:drop-shadow(0 0 2px var(--halo,var(--paper)))
         drop-shadow(0 0 2px var(--halo,var(--paper)))
         drop-shadow(0 0 4px var(--halo,var(--paper)));
}
.st-annot > .st-note,.st-annot > .st-note-sm,.st-annot > .st-note-xs{
  text-shadow:0 0 3px var(--halo,var(--paper)),0 0 3px var(--halo,var(--paper)),
              0 0 7px var(--halo,var(--paper));
}
/* The same halo on demand, for a mark that is over something without
   .st-annot doing the placing (inside a figure, say). */
.st-mark--over{
  filter:drop-shadow(0 0 2px var(--halo,var(--paper)))
         drop-shadow(0 0 2px var(--halo,var(--paper)))
         drop-shadow(0 0 4px var(--halo,var(--paper)));
}

/* 16A.4 The drawn frame. One wobbly rectangle round a region of a
   screenshot. It stretches to whatever it wraps, so keep it within about
   3:1 either way or the stroke reads uneven. */
.st-sketch{position:relative;display:inline-block;}
.st-sketch::after{
  content:"";position:absolute;inset:-10px;pointer-events:none;
  background-color:var(--pen);
  filter:drop-shadow(0 0 2px var(--halo,var(--paper)))
         drop-shadow(0 0 2px var(--halo,var(--paper)));
  -webkit-mask-repeat:no-repeat;mask-repeat:no-repeat;
  -webkit-mask-size:100% 100%;mask-size:100% 100%;
  -webkit-mask-image:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 200 120' fill='none' stroke='%23000' stroke-width='4' stroke-linecap='round' stroke-linejoin='round' preserveAspectRatio='none'%3E%3Cpath d='M8 12C60 6 140 8 192 11 194 45 193 83 190 112 130 117 61 114 9 111 6 78 7 45 8 12Z'/%3E%3C/svg%3E");mask-image:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 200 120' fill='none' stroke='%23000' stroke-width='4' stroke-linecap='round' stroke-linejoin='round' preserveAspectRatio='none'%3E%3Cpath d='M8 12C60 6 140 8 192 11 194 45 193 83 190 112 130 117 61 114 9 111 6 78 7 45 8 12Z'/%3E%3C/svg%3E");
}

/* 16A.5 Sticky notes. A real object: square-ish, one flat colour, a
   slight tilt, and a shadow that is heavier at the bottom edge where the
   paper lifts. Text on one is --ink and the meta line is --ink2. Never
   the pen (it does not clear 4.5:1 on the pink or the blue), never
   muted, never an accent, and never a link or a button: a sticky note is
   something someone left, not something you press.

   It keeps its colours in the dark palette, like the pinned scrap, and
   for the same reason: turning the lights off does not turn a yellow
   sticky grey. Its ink is pinned with it. */
.st-postit{
  --note:var(--note-y);
  /* position is not an author knob on paper objects: an inline
     position:absolute is exactly what put a sticky over a paragraph on the
     homepage (Ian, 2026-08-31). A sticky or taped card is an object IN the
     flow of its lane. */
  position:relative !important;display:block;box-sizing:border-box;
  width:196px;min-height:196px;padding:var(--s5) var(--s4) var(--s4);
  background:var(--note);
  color:#111110;
  font-family:var(--hand);font-weight:700;
  font-size:var(--fs-note-xs);line-height:1.25;
  transform:rotate(var(--tilt,-1.6deg));
  box-shadow:0 1px 1px rgba(28,24,14,.10),
             2px 4px 7px rgba(28,24,14,.10),
             5px 12px 20px rgba(28,24,14,.10);
  background-image:linear-gradient(200deg,rgba(255,255,255,.42),rgba(255,255,255,0) 42%);
}
.st-postit > :last-child{margin-bottom:0;}
.st-postit--pink {--note:var(--note-p);}
.st-postit--blue {--note:var(--note-b);}
.st-postit--green{--note:var(--note-g);}
/* The curl: the bottom-right corner lifting off the board. One gradient,
   no extra element, and it sits under the text. */
.st-postit::after{
  content:"";position:absolute;right:0;bottom:0;width:34px;height:34px;
  background:linear-gradient(135deg,rgba(28,24,14,0) 46%,rgba(28,24,14,.10) 52%,rgba(28,24,14,0) 78%);
  pointer-events:none;
}
/* Who left it. Mono, small, dark enough on all four colours. */
.st-postit__by{
  display:block;margin-top:var(--s3);
  font-family:var(--mono);font-weight:400;font-size:var(--fs-chip);
  letter-spacing:1px;text-transform:uppercase;color:#33332B;
}
/* A board of them. Sticky notes never sit in a neat grid; they overlap a
   little and each takes its own tilt. */
.st-board{display:flex;flex-wrap:wrap;gap:var(--s5) var(--s4);align-items:flex-start;}

/* 16A.6 Taped on at an angle. §16's .st-stuck is the card; its ::before
   is one strip of tape across the top. This is the other way people
   actually stick a card up: a strip over each top corner, at 45°, so the
   card hangs between them. ::before becomes the left corner and ::after
   the right, and the corner tape is squarer than the top strip because
   it is a torn piece rather than a pulled length. */
.st-stuck--taped::before,.st-stuck--taped::after{
  content:"";position:absolute;top:-13px;width:62px;height:22px;z-index:2;
  background:linear-gradient(180deg,rgba(255,255,255,.34),rgba(255,255,255,.05) 48%,rgba(70,58,28,.06)),
             repeating-linear-gradient(90deg,rgba(70,58,28,.05) 0 1px,transparent 1px 4px),
             rgba(214,201,158,.5);
  box-shadow:0 2px 5px rgba(28,24,14,.14);
  -webkit-mask-image:linear-gradient(90deg,transparent,#000 11%,#000 89%,transparent);
  mask-image:linear-gradient(90deg,transparent,#000 11%,#000 89%,transparent);
  left:auto;transform:none;
}
.st-stuck--taped::before{left:-16px;transform:rotate(-42deg);}
.st-stuck--taped::after {right:-16px;transform:rotate(42deg);}
/* A taped card sits flatter than a pinned one: two anchors, less swing. */
.st-stuck--taped{--tilt:-0.8deg;}

/* 16A.7 The aside lane. A human aside never floats over flowing text: it
   lives in a lane (a spread's rail, or the flow after the thing it
   annotates). .st-aside pairs one aside with one mark pointing at the
   thing beside the lane.

   The ground, measured, not assumed: the spread's gutter (--s7, 44px)
   plus the rail's padding-left (--s5, 24px) plus its 1px border is 69px
   between the rail's own content and the body column's right edge. That
   is a fact about #how's two paragraphs specifically, which carry
   .st-body-sm's measure cap and so end 54px short of the column edge
   besides; it is not a property the grid hands every body column, and a
   .st-lede or a full-width .st-body running to 68ch has only the 69px.
   So the mark's LEFT edge is capped in the calc below at 64px past the
   8px gap (69px minus a hair of clearance), by min(), rather than by
   trusting an instance's --mark-s to stay small: 8px gap + up to 56px of
   reach is the whole budget, whatever scale an instance asks for. No
   instance may raise --mark-s or --turn inside a lane; the clamp is
   what makes that a fact about the CSS rather than a rule an author has
   to remember.
   --drop nudges the aside down its lane to sit beside the thing it is
   about, per instance, inline. It is desktop-only by construction: the
   stacked lane below 920px overrides it, so no phone answer is needed
   per instance. */
.st-aside{position:relative;display:block;width:max-content;max-width:100%;margin-top:var(--drop,0px);}
.st-aside > .st-mark{
  position:absolute;top:2px;margin:0;
  --mark-s:.85;
  left:calc(-8px - min(var(--mark-w) * var(--mark-s), 56px));
}

/* The mirror. An aside in a LEFT-hand lane annotates the thing to its right,
   so the mark swaps sides. Same clamped reach, same budget, so the clearance
   argument above still holds; only the edge it hangs off changes. The arrow
   mask already points right, so no flip is needed, just a --turn per
   instance. */
.st-aside--right > .st-mark{
  left:auto;
  right:calc(-8px - min(var(--mark-w) * var(--mark-s), 56px));
}

/* 16A.8 One aside, one line. .st-note / -sm / -xs are inline-block (§6), so
   two asides in one lane sit on the same line at different heights and read
   as drawn on top of each other (privacy.html, Ian, 2026-08-31). In a lane
   they are blocks. width:max-content keeps the tilt box tight to the words,
   so the rotation still pivots about the note and not about the whole lane. */
.st-spread__rail > .st-note,.st-spread__rail > .st-note-sm,.st-spread__rail > .st-note-xs,
.st-aside > .st-note,.st-aside > .st-note-sm,.st-aside > .st-note-xs{
  display:block;width:max-content;max-width:100%;
}
.st-spread__rail > .st-note + .st-note,.st-spread__rail > .st-note + .st-note-sm,
.st-spread__rail > .st-note-sm + .st-note,.st-spread__rail > .st-note-sm + .st-note-sm,
.st-spread__rail > .st-note-xs + .st-note-xs{margin-top:var(--s5);}

@media (max-width:920px){
  .st-aside{margin-top:var(--s5);}
  .st-aside > .st-mark{display:none;} /* stacked lanes point at nothing */
}

/* 16A.9 The margin note. An aside in the body column reads as stray text in a
   funny font (Ian, on the live privacy page, 2026-08-31 evening). On a page
   whose body is one plain column there is still a lane: the empty band beside
   the measure. A margin note is a child of the paragraph it annotates, never a
   sibling with an offset, so it cannot go stale when the copy is edited. It
   only takes the margin when the margin is genuinely there. */
.st-annotated{position:relative;}
.st-margin-note{display:block;margin:var(--s3) 0 var(--s5);width:max-content;max-width:100%;}
@media (min-width:1120px){
  .st-margin-note{
    position:absolute;left:100%;top:0;margin:0 0 0 var(--s5);
    max-width:300px;
  }
}

/* =====================================================================
   17. MOTION
   Opacity and transform. That is the whole list for anything that moves,
   plus background-size on the sweep and stroke-dashoffset on a drawn line.
   Never a layout property. Nothing loops. No parallax, no scroll-jacking,
   no pinned sections, no carousel, no autoplaying video.

   The reveal gate: everything hidden for motion is hidden ONLY under
   .st-motion-ready, which stica.js adds on load. With JS off, everything
   is visible. Never write a hide-state that is not gated this way.

   RETIRED 2026-08-06 and not to be rebuilt: the cursor-following dot, the
   hover ring and the click ink ripple. A permanent mousemove handler and a
   loop that never stops, invisible on the platform that matters most.
   ===================================================================== */

.st-motion-ready .st-reveal{
  opacity:0;transform:translateY(14px);
  transition:opacity var(--dur-4) var(--ease),transform var(--dur-4) var(--ease);
  will-change:opacity,transform;
}
.st-motion-ready .st-reveal.st-visible{opacity:1;transform:none;}
.st-motion-ready .st-reveal[data-reveal-order="2"]{transition-delay:60ms;}
.st-motion-ready .st-reveal[data-reveal-order="3"]{transition-delay:120ms;}
.st-motion-ready .st-reveal[data-reveal-order="4"]{transition-delay:180ms;}
.st-motion-ready .st-reveal[data-reveal-order="5"]{transition-delay:240ms;}

/* Highlighter sweep. Round 3: background-size animates per LINE BOX, so a
   phrase that wraps onto two lines paints two independent square-ended
   bars that grow at once, reading as an accidental text selection rather
   than one pen stroke — worst on mobile, where the wrap is near-certain.
   white-space:nowrap keeps every swept phrase on one line; only ever wrap
   a short phrase in this span. */
.st-sweep{
  /* nowrap stays: without it a wrapped phrase paints two independent
     square-ended bars (see the block comment above), which reads as an
     accidental text selection rather than a highlighter stroke. Every
     current usage is a short (three-word) fragment for exactly this
     reason; keep it that way rather than removing nowrap. */
  position:relative;display:inline;white-space:nowrap;
  /* Round 3 (method): 18% read as not-there-at-all against cream/white —
     the one live usage besides this page (coaches) gets more legible too. */
  --sweep:color-mix(in srgb,var(--acc) 30%,transparent);
  background-image:linear-gradient(var(--sweep),var(--sweep));
  background-repeat:no-repeat;background-position:left center;background-size:100% 55%;
  border-radius:var(--r-sm);padding-bottom:1px;
}
.st-motion-ready .st-sweep{
  background-size:0% 55%;
  transition:background-size var(--dur-5) var(--ease);
  will-change:background-size;
}
.st-motion-ready .st-sweep.st-drawn{background-size:100% 55%;}

/* A drawn line: the spine, a ring, the CTA flourish */
.st-ring-wrap{position:relative;display:inline-block;}
.st-ring-wrap svg.st-ring{
  position:absolute;pointer-events:none;overflow:visible;
  top:50%;left:50%;transform:translate(-50%,-50%);
  width:calc(100% + 28px);height:calc(100% + 24px);
}
.st-ring path{
  fill:none;stroke:var(--pen);stroke-width:2;stroke-linecap:round;
  vector-effect:non-scaling-stroke;stroke-dasharray:320;stroke-dashoffset:0;
}
.st-motion-ready .st-ring path{
  stroke-dashoffset:320;
  transition:stroke-dashoffset var(--dur-6) var(--ease);
  will-change:stroke-dashoffset;
}
.st-motion-ready .st-ring.st-drawn path{stroke-dashoffset:0;}

/* Hand annotation fade-in */
.st-motion-ready .st-note,
.st-motion-ready .st-note-sm,
.st-motion-ready .st-note-xs{opacity:0;transition:opacity var(--dur-2) ease 200ms;}
.st-motion-ready .st-note.st-drawn,
.st-motion-ready .st-note-sm.st-drawn,
.st-motion-ready .st-note-xs.st-drawn{opacity:1;}


/* =====================================================================
   18. BREAKPOINTS
   Exactly two. Every page uses these and only these. Anything else is a bug.
   Mobile-first is non-negotiable: check 375px before calling a page done,
   and it must reflow cleanly at 320px.
   ===================================================================== */

@media (max-width:920px){
  :root{--gutter:24px;--sec-y:var(--s8);--sec-y-roomy:var(--s9);}
  .st-split,.st-hero-split,.st-cols-2{grid-template-columns:1fr;gap:var(--s6);}
  .st-cols-3{grid-template-columns:1fr;}
  .st-cols-4{grid-template-columns:1fr 1fr;}
  .st-spread{grid-template-columns:1fr;gap:var(--s5);}
  .st-spread__rail{
    border-left:0;border-top:1px dashed var(--line2);
    padding:var(--s4) 0 0;
  }
  .st-hero,.st-hero-split{padding-block:var(--s8) var(--s7);}
  /* Decorative side art hides. */
  .st-decor{display:none;}
  /* Round 2: nav collapses HERE, not at 640. Five links plus two pills
     need ~780px to sit on one line without squeezing; below 920 the row
     used to shrink each pill until its own text wrapped onto two lines,
     which also dragged the wordmark highlight out of place two
     breakpoints up (fixed independently in .st-brand, but the crowd was
     the trigger). The primary action stays on screen so a visitor never
     has to open the menu to find the one thing the header is for. */
  .st-nav{display:flex;gap:var(--s3);}
  .st-nav a:not(.st-btn--pill){display:none;}
  .st-nav .st-btn--pill-outline{display:none;}
  /* Round 3 (teams): .st-head__inner has three flex children here (brand,
     nav, toggle); space-between turns the middle one (now just the pill)
     into a dead-centre floater, anchored to nothing. Auto margins are
     resolved before justify-content distributes remaining space, so this
     pulls nav flush against the toggle and both flush against the right
     edge, leaving space-between nothing left to distribute — brand stays
     pinned left, exactly as before. No markup change, so every page using
     this header is fixed at once. */
  .st-nav{margin-left:auto;}
  .st-navtoggle{display:inline-flex;}
  /* Final CTA keeps step with the section rhythm here instead of holding
     its full 96px at every width. */
  .st-cta{padding-block:var(--s8);}
}

@media (max-width:640px){
  :root{--gutter:20px;--sec-y:var(--s7);--sec-y-roomy:var(--s8);}
  .st-cols-4{grid-template-columns:1fr;}
  .st-prices{grid-template-columns:1fr;}
  .st-cta{padding-block:var(--s7);}
  /* Hand notes stop being absolutely positioned and flow inline. */
  .st-note,.st-note-sm,.st-note-xs{position:static !important;display:block;width:max-content;max-width:100%;}
  .st-brand{font-size:26px;}
  /* No .dot override here: its width, height and clearance are in em
     against .st-brand's own font-size (2026-09-01), so 26px above scales
     the circle correctly on its own; a pinned px override went under
     spec at this size and is deleted, not converted. */
  .st-footer__inner{flex-direction:column;align-items:flex-start;gap:var(--s4);}
  .st-footer__nav{display:grid;grid-template-columns:1fr 1fr;gap:var(--s3) var(--s5);width:100%;}
}

/* Round 3: a hero's own CTA row goes full-width and stacks below 480px.
   flex-wrap already stopped the two buttons overlapping at any width, but
   two uneven-width buttons left-aligned and wrapped read as an accident,
   not a decision. Scoped to .st-hero__cta so ordinary button rows
   (the mobile nav panel, a form's actions) are unaffected. */
@media (max-width:480px){
  .st-hero__cta .st-btnrow{flex-direction:column;align-items:stretch;}
  .st-hero__cta .st-btnrow > a{width:100%;text-align:center;}
}

/* Hero-split price-first: opt-in modifier for a hero that pairs the pitch
   with a price/action card rather than an illustration or note. On a
   narrow screen the card comes first, so the cost is visible before a
   long scroll through the pitch. The ordinary hero-split pages (text
   beside a note or a panel) do not carry this class and are unaffected. */
@media (max-width:920px){
  .st-hero-split--price-first > :first-child{order:2;}
  .st-hero-split--price-first > :last-child{order:1;}
}


/* =====================================================================
   19. REDUCED MOTION
   Nothing on this site conveys information through motion, so reducing it
   loses nothing. Every page must be checked with the setting on.
   ===================================================================== */

@media (prefers-reduced-motion: reduce){
  *,*::before,*::after{
    animation-duration:.01ms !important;
    animation-iteration-count:1 !important;
    transition-duration:.01ms !important;
    scroll-behavior:auto !important;
  }
  /* Final frames, explicitly. */
  .st-motion-ready .st-reveal{opacity:1 !important;transform:none !important;}
  .st-motion-ready .st-sweep{background-size:100% 55% !important;}
  .st-motion-ready .st-ring path{stroke-dashoffset:0 !important;}
  .st-motion-ready .st-note,
  .st-motion-ready .st-note-sm,
  .st-motion-ready .st-note-xs{opacity:1 !important;}
  .st-cta__flourish path{stroke-dashoffset:0 !important;animation:none !important;}
  .st-stuck{transition:none;}
  .st-stuck:hover{transform:rotate(var(--tilt,-1.4deg));}
}


/* =====================================================================
   20. UTILITIES — the only ones. Do not add to this list per page.
   ===================================================================== */

.st-sr{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0 0 0 0);white-space:nowrap;border:0;}
.st-centre{text-align:center;}
.st-stack{display:flex;flex-direction:column;}
.st-stack-2{gap:var(--s2);} .st-stack-3{gap:var(--s3);} .st-stack-4{gap:var(--s4);}
.st-stack-5{gap:var(--s5);} .st-stack-6{gap:var(--s6);}
.st-flow > * + *{margin-top:var(--s4);}

/* ---------------------------------------------------------------------------
   THEME TOGGLE (added 2026-08-07)
   The site had a complete dark palette and no way for a visitor to ask for it:
   data-theme="auto" followed the device and nothing else. This is the control.
   The icon shows what the NEXT click gives you, which is what its label says
   too: a moon while you are in light, a sun while you are in dark.
   Wired by assets/theme.js, which also carries the choice to app.stica.co.uk.
   --------------------------------------------------------------------------- */
.st-themetoggle{
  display:inline-flex;align-items:center;justify-content:center;flex:none;
  width:34px;height:34px;padding:0;
  /* --line2, not --line: this button is transparent and sits directly on the
     graph ground, where --line measures 1.101:1 and is no edge at all. It was
     invisible on every light page of all three sites and the 2026-08-29 sweep
     missed it, because the probe was reading the ground's own colour on the
     wrong scale and thought the button sat on black. */
  border:1px solid var(--line2);border-radius:999px;
  background:transparent;color:var(--ink2);cursor:pointer;
  transition:color var(--dur-1) var(--ease),border-color var(--dur-1) var(--ease);
}
.st-themetoggle:hover{color:var(--ink);border-color:var(--line-ui);}
.st-themetoggle:focus-visible{outline:2px solid var(--acc-line);outline-offset:2px;}
.st-themetoggle svg{display:block;}
.st-themetoggle__sun{display:none;}
:root[data-theme="dark"] .st-themetoggle__sun{display:block;}
:root[data-theme="dark"] .st-themetoggle__moon{display:none;}
@media (prefers-color-scheme: dark){
  :root[data-theme="auto"] .st-themetoggle__sun{display:block;}
  :root[data-theme="auto"] .st-themetoggle__moon{display:none;}
}
/* In the mobile panel it sits in the button row and earns a label, because a
   bare icon in a full-width stack reads as a mistake rather than a control. */
.st-navpanel .st-themetoggle{width:auto;height:auto;gap:8px;padding:10px 16px;border-radius:var(--r-lg);}
.st-navpanel .st-themetoggle::after{content:"Dark mode";font-size:var(--fs-body-sm);}
:root[data-theme="dark"] .st-navpanel .st-themetoggle::after{content:"Light mode";}
@media (prefers-color-scheme: dark){
  :root[data-theme="auto"] .st-navpanel .st-themetoggle::after{content:"Light mode";}
}


/* =====================================================================
   21. THE APP, PICTURED
   Real screenshots of Stica Coach, shot off the running app in both
   themes. Each figure ships BOTH shots and the theme decides which one
   is shown -- and it shows the OPPOSITE one on purpose (Ian, 2026-08-28):
   the dark screenshot on the light page, the light screenshot on the
   dark page. A picture of a screen in the same palette as the page it
   sits on dissolves into it; the contrast is what makes it read as a
   thing rather than a panel.

   The visibility idiom is the theme toggle's, three rules down: the
   default is the light page, [data-theme="dark"] wins explicitly, and
   the "auto" case is the device preference.
   ===================================================================== */

/* Every rule here is written `figure.st-shot`, not `.st-shot`, because §9b
   above already claims that class for a DIFFERENT component: a whole-image
   link carrying its own border, shadow and clipped corners. Measured in the
   browser on the front page, an unscoped figure took both, so each shot sat
   in a bordered box inside a bordered box. The outer chrome is reset here
   rather than §9b being renamed, because §9b is still what the coach
   prototype uses. */
figure.st-shot{
  margin:0;border:0;box-shadow:none;background:none;
  overflow:visible;border-radius:0;
}
figure.st-shot img{
  display:block;width:100%;height:auto;
  border-radius:var(--r-lg);border:1px solid var(--line);
  background:var(--surface);
}
/* A phone shot is a phone: it never grows to the width of a column. */
figure.st-shot--phone img{max-width:300px;margin-inline:auto;}
.st-shot__cap{
  margin:var(--s2) 0 0;font-size:var(--fs-small);line-height:1.5;color:var(--mut);
}
.st-shot--phone .st-shot__cap{text-align:center;}

/* The swap. Each of these carries the `img` type selector so it outranks
   `figure.st-shot img{display:block}` above: without it the class rule loses
   on specificity and BOTH shots render, one under the other. That is what the
   first render of this section actually did. */
/* The page shows the OPPOSITE shot on purpose, per the section header above:
   a picture of a screen in the same palette as the page it sits on dissolves
   into it. Confirmed by Ian on 2026-08-28 (ans-20260828-144841-a164) after a
   session read it as a bug and briefly made them match. It is not a bug. The
   class on each img names the file, so `--light` is the light-looking shot. */
/* Light page: the dark shot. */
figure.st-shot img.st-shot__img--light{display:none;}
/* Dark page: the light shot. */
:root[data-theme="dark"] figure.st-shot img.st-shot__img--light{display:block;}
:root[data-theme="dark"] figure.st-shot img.st-shot__img--dark{display:none;}
@media (prefers-color-scheme: dark){
  :root[data-theme="auto"] figure.st-shot img.st-shot__img--light{display:block;}
  :root[data-theme="auto"] figure.st-shot img.st-shot__img--dark{display:none;}
}

/* The four rules above key on the PAGE's theme, which is right everywhere the
   ground follows the page. A .st-section--dark band does not: it is a solid
   dark slab whatever the page is set to. So on a light page those rules serve
   the dark shot onto a dark slab and the screen disappears into its own
   ground, which is what Ian saw on 2026-08-29. Inside that band the pairing is
   fixed rather than conditional: always the light shot, so the picture is
   always the opposite of what it sits on. Repeated under each of the three
   cases above because each has to be beaten on its own specificity. */
.st-section--dark figure.st-shot img.st-shot__img--light{display:block;}
.st-section--dark figure.st-shot img.st-shot__img--dark{display:none;}
:root[data-theme="dark"] .st-section--dark figure.st-shot img.st-shot__img--light{display:block;}
:root[data-theme="dark"] .st-section--dark figure.st-shot img.st-shot__img--dark{display:none;}
@media (prefers-color-scheme: dark){
  :root[data-theme="auto"] .st-section--dark figure.st-shot img.st-shot__img--light{display:block;}
  :root[data-theme="auto"] .st-section--dark figure.st-shot img.st-shot__img--dark{display:none;}
}

/* A 1440px-wide screen shot at 350px is a smudge, not evidence. Measured on
   the front page at 390px: the dashboard came out 350x247, a 4.1x reduction,
   with nothing in it readable. Below 640 the landscape shots therefore sit in
   their own sideways scroller at a width where the words survive, and the
   caption stays put outside it. The phone shot is exempt: it is already the
   right size for the screen it is being looked at on. */
@media (max-width:640px){
  figure.st-shot:not(.st-shot--phone) .st-shot__frame{
    overflow-x:auto;overflow-y:hidden;-webkit-overflow-scrolling:touch;
    border-radius:var(--r-lg);
  }
  figure.st-shot:not(.st-shot--phone) .st-shot__frame img{min-width:680px;}
  figure.st-shot:not(.st-shot--phone) .st-shot__frame:focus-visible{
    outline:2px solid var(--acc-line);outline-offset:2px;
  }
}
