/* ===========================================================================
   Toorova public site — THE design system.

   ONE stylesheet. Every static page in web/ links this file and nothing else
   of its own. Before this consolidation, nine pages each carried an inline
   <style> block with its own copy of the palette, its own type sizes and its
   own idea of what a card was; the site therefore read as nine sites that
   happened to share a logo. If you are about to add a page-specific <style>
   block, add a component here instead — and if you genuinely cannot, leave a
   comment in the page saying why.

   The shared header and footer are NOT here. They are generated into every
   page by `dart run tool/web_chrome.dart` and styled by web/chrome.css, whose
   tokens are all `--tv-` prefixed so the two files never collide.

   TWO PAGE ARCHETYPES, one component set
   -------------------------------------
   1. MARKETING pages (about, educators, courses, resources, support) —
      a navy `.hero` band, then `.band` sections directly on the page
      background. Wide space is spent on structure: `.split` puts the section
      label in its own column, `.grid` lays tiles out, `.facts` turns claims
      into numbers. Body copy never runs wider than `--measure`.
   2. ARTICLE / REFERENCE pages (guides, help, privacy, terms) —
      the same hero, then a `.doc` two-column: a sticky table of contents in
      one column and a `.card` sheet of prose in the other, capped at
      `--measure`. A 1400px monitor gets navigation in the extra space, not
      120-character lines.

   Rules that hold everywhere: one h1 per page, a visible focus ring, AA
   contrast in both colour schemes, and nothing that needs a network request.
   Served from the same origin because firebase.json's CSP is `style-src
   'self' 'unsafe-inline'` and these pages never load the Flutter bundle.
   ======================================================================== */

/* ---- Tokens ------------------------------------------------------------ */

:root{
  /* Palette. Names kept from the pages that owned them first, so existing
     markup keeps working — but the VALUES are now ToorovaColors', taken from
     lib/core/theme/toorova_colors.dart rather than picked to look similar.
     The static pages and the Flutter app were two shades of the same idea
     (#F7F9FF against #EAEFF9 in light, #070B14 against #0A0F18 in dark), and
     a page that is a different shade of white reads as a different website.
     If the app's palette moves, move these with it. */
  --bg:#EAEFF9; --card:#ffffff; --ink:#0F172A; --sub:#475569; --muted:#55637A;
  --line:#C9D3E8; --link:#1D4ED8; --field:#F4F7FE; --grad1:#2563EB; --grad2:#1D4ED8;
  /* The CTA card's SURFACE, deliberately separate from --grad1/--grad2.
     Those two are also the figure accent (.fig-* below), drawn ON the page
     ground; this pair is drawn UNDER white text. One token cannot serve both:
     dark --grad2 (#3E8BFF) gives the figures a healthy 5.9:1 on the dark
     ground, but only 3.05:1 behind the CTA's body copy, and darkening it to
     fix the card would drop the diagrams to 2.9:1. So the card keeps the same
     brand blue in both schemes, which is a deliberate single-look surface. */
  --cta1:#2563EB; --cta2:#1D4ED8;
  --tint:#EEF3FF; --focus:#1D4ED8; --accent:#1D4ED8;
  --pill:#EEF3FF; --pill-ink:#31456B;
  --yes:#0F766E; --no:#9A3412;
  --btn-ink:#ffffff;
  /* Form controls only. `--line` is a HAIRLINE — right for a card edge, and a
     WCAG 1.4.11 failure on an input, where the border is the only thing that
     says a control is there at all: #C9D3E8 measures 1.5:1 against the card
     and the input's own fill is 1.07:1, so the box is effectively invisible.
     1.4.11 wants 3:1 for the visual boundary of a control. This token is that
     boundary and nothing else, so card and table hairlines stay hairlines. */
  --field-line:#78899F;
  /* ELEVATION — the app's ladder, not a shadow picked by eye.
     ToorovaElevation (lib/core/theme/toorova_elevation.dart) has four rungs;
     three of them are surfaces a web page can have. Every value below is that
     file's numbers, cast in the palette's own shadow navy (`shadowTint`
     #0B1120 here, #03060E on the dark ground) and multiplied by the palette's
     `shadowStrength` — 1.0 light, 2.4 dark, because a near-black shadow over
     a near-black ground has to be pushed hard before it reads at all.

     Two passes from --e2 up, exactly as the app does it: a tight pass that
     anchors the object to the page and a wide one that carries the ambient
     falloff. One pass at a big blur looks like fog; one pass at a small blur
     looks like a border. The pair is what reads as depth. Every pass has a
     negative spread — a shadow as wide as the card that casts it is a halo
     drawn around an edge, not light falling past one. */
  --e1:0 2px 8px -2px rgba(11,17,32,.06);
  --e2:0 2px 6px -2px rgba(11,17,32,.05), 0 8px 24px -6px rgba(11,17,32,.09);
  --e3:0 4px 10px -3px rgba(11,17,32,.08), 0 24px 56px -12px rgba(11,17,32,.18);
  /* A card EDGE, which is not the same thing as a hairline rule. `--line` is
     the divider between two ideas — an <hr>, a table row, the rail beside the
     table of contents — and it stays exactly where it was. This is the rim of
     an object that is lifted off the page: once elevation is doing the work
     of saying "this is on top", a full-strength border only fights it. */
  --edge:rgba(15,23,42,.09);
  /* The lit top edge. On the light ground a card is already white, so there is
     no highlight to add and this is deliberately nothing; on the dark ground
     the same one-pixel inset is what makes a surface look like it is catching
     the light from above rather than being a paler rectangle. */
  --edge-lit:rgba(255,255,255,0);
  --shadow:var(--e3);
  --shadow-sm:var(--e2);

  /* Spacing scale. Everything on the site steps through these. */
  --s1:6px; --s2:10px; --s3:16px; --s4:24px; --s5:34px; --s6:48px; --s7:72px;

  /* Type scale. Four sizes of heading, three of body — no more. */
  /* The desktop ceilings are up (60->68, 36->42). These are page TITLES on a
     wide monitor and they were sized for the middle of the range; the phone
     end of each clamp is untouched, so nothing moves below ~1150px. */
  --t-display:clamp(34px,5.4vw,68px);
  --t-h2:clamp(24px,3.1vw,42px);
  --t-h3:19px;
  --t-body:17px;
  --t-sm:15.5px;
  --t-xs:14px;
  --t-label:13px;

  --radius:16px; --radius-lg:20px; --radius-sm:11px;

  /* The readable column. `ch` is the width of a zero, which is far wider than
     the average proportional character, so 68ch measures out at nearly 90
     characters. 33em lands on ~68 — the middle of the 60-75 range. */
  --measure:33em;
  --page:1200px;
  --gutter:clamp(20px,5vw,48px);

  /* The breath of brand colour at the top of a page. Light enough to read as
     paper with light on it; on the dark ground it is the old hero's glow. */
  --hero-wash:rgba(37,99,235,.11);
}

@media (prefers-color-scheme: dark){

  :root:not([data-theme='light']){
    /* ToorovaColors.dark: background, card, textPrimary, textSecondary,
       textTertiary, outline. Same source as the light block above. */
    --bg:#0A0F18; --card:#1D2848; --ink:#F1F5FF; --sub:#B7C0D6; --muted:#A5B1CC;
    --line:#44506B; --link:#7FB0FF; --field:#0B1526; --grad1:#2563EB; --grad2:#3E8BFF;
    --cta1:#2563EB; --cta2:#1D4ED8;
    --tint:#101F3A; --focus:#7FB0FF; --accent:#7FB0FF;
    --pill:#15243F; --pill-ink:#B9C9EA;
    --field-line:#7288BE;
    --yes:#5EEAD4; --no:#FDBA74;
    --btn-ink:#08101F;
    /* shadowStrength 2.4 and shadowTint #03060E — see the light block. */
    --e1:0 2px 8px -2px rgba(3,6,14,.14);
    --e2:0 2px 6px -2px rgba(3,6,14,.12), 0 8px 24px -6px rgba(3,6,14,.22);
    --e3:0 4px 10px -3px rgba(3,6,14,.19), 0 24px 56px -12px rgba(3,6,14,.43);
    --edge:rgba(255,255,255,.09);
    --edge-lit:rgba(255,255,255,.07);
    --shadow:var(--e3);
    --shadow-sm:var(--e2);
    --hero-wash:rgba(62,139,255,.20);
  }
}
:root[data-theme='dark']{
    /* ToorovaColors.dark: background, card, textPrimary, textSecondary,
       textTertiary, outline. Same source as the light block above. */
    --bg:#0A0F18; --card:#1D2848; --ink:#F1F5FF; --sub:#B7C0D6; --muted:#A5B1CC;
    --line:#44506B; --link:#7FB0FF; --field:#0B1526; --grad1:#2563EB; --grad2:#3E8BFF;
    --cta1:#2563EB; --cta2:#1D4ED8;
    --tint:#101F3A; --focus:#7FB0FF; --accent:#7FB0FF;
    --pill:#15243F; --pill-ink:#B9C9EA;
    --field-line:#7288BE;
    --yes:#5EEAD4; --no:#FDBA74;
    --btn-ink:#08101F;
    /* shadowStrength 2.4 and shadowTint #03060E — see the light block. */
    --e1:0 2px 8px -2px rgba(3,6,14,.14);
    --e2:0 2px 6px -2px rgba(3,6,14,.12), 0 8px 24px -6px rgba(3,6,14,.22);
    --e3:0 4px 10px -3px rgba(3,6,14,.19), 0 24px 56px -12px rgba(3,6,14,.43);
    --edge:rgba(255,255,255,.09);
    --edge-lit:rgba(255,255,255,.07);
    --shadow:var(--e3);
    --shadow-sm:var(--e2);
    --hero-wash:rgba(62,139,255,.20);
  }


/* ---- Base -------------------------------------------------------------- */

*{box-sizing:border-box}
html{scroll-behavior:smooth;-webkit-text-size-adjust:100%}
@media (prefers-reduced-motion: reduce){ html{scroll-behavior:auto} *{transition:none !important} }

body{margin:0;background:var(--bg);color:var(--ink);
  /* Inter, the app's reading face. Declared in chrome.css, which every page
     loads. Before this the static half of the site rendered in the system
     font while the Flutter half rendered in Inter, and one website in two
     typefaces reads as two websites. */
  font-family:var(--tv-font-text,-apple-system,BlinkMacSystemFont,"Segoe UI",Inter,Roboto,Helvetica,Arial,sans-serif);
  font-size:var(--t-body);line-height:1.62;-webkit-font-smoothing:antialiased;
  /* A long URL or an unbreakable token must never widen the page. */
  overflow-wrap:break-word}

/* Keyboard focus is visible on every interactive thing, in both themes. */
a:focus-visible,button:focus-visible,summary:focus-visible,
input:focus-visible,select:focus-visible,textarea:focus-visible,
[tabindex]:focus-visible{
  outline:3px solid var(--focus);outline-offset:3px;border-radius:6px}

.skip{position:absolute;left:-9999px;top:0;z-index:20;background:var(--card);color:var(--link);
  padding:12px 18px;border-radius:0 0 12px 0;font-weight:700;text-decoration:none}
.skip:focus{left:0}

main{display:block}

/* Space Grotesk on headings, Inter on prose — the same split ToorovaType
   makes in the app: geometric for what you look AT, humanist for what you
   look THROUGH. `.kicker` and the stat numbers are display type too. */
h1,h2,h3,.kicker,.hero-stats b,.facts b,.plan-price,.statement{
  font-family:var(--tv-font-display,inherit)}
h1,h2,h3{color:var(--ink);letter-spacing:-.02em}
h2{font-size:var(--t-h2);line-height:1.2;margin:0 0 var(--s2)}
h3{font-size:var(--t-h3);line-height:1.32;margin:0 0 var(--s1)}
p{margin:0 0 var(--s3);color:var(--sub)}
p strong,li strong{color:var(--ink)}
ul,ol{margin:0 0 var(--s3);padding-left:20px;color:var(--sub)}
li{margin:0 0 var(--s2)}
a{color:var(--link)}
a:hover{text-decoration-thickness:2px}
hr{border:0;border-top:1px solid var(--line);margin:var(--s5) 0}
code{font-size:.92em;background:var(--tint);border:1px solid var(--line);
  border-radius:5px;padding:1px 5px}
img{max-width:100%;height:auto}

/* Anything anchored gets clearance so a jumped-to heading is not welded to
   the top edge of the window. */
section,[id]{scroll-margin-top:24px}

/* ---- The measured column ----------------------------------------------- */
/* Wide space is for structure. Prose is capped here and nowhere else. */

.measure{max-width:var(--measure)}
.prose > p, .prose > ul, .prose > ol, .prose > h3, .prose > blockquote,
.card > p, .card > ul, .card > ol, .card > h3, .card > blockquote,
.card > section > p, .card > section > ul, .card > section > ol{
  max-width:var(--measure)}
.prose > h2, .card > h2, .card > section > h2{max-width:26ch}

.lead{font-size:18px;color:var(--sub);max-width:var(--measure)}

/* ---- Hero band --------------------------------------------------------- */
/* Always the brand navy, in both colour schemes, so it meets the generated
   header seamlessly and every page opens the same way. */

/* THE HERO IS NOT A BAND ANY MORE.
 *
 * It used to be a fixed navy block with white type, in both colour schemes, on
 * top of a page that followed the theme. In light mode that produced a dark
 * slab, then a light page, then a dark footer — while the Flutter marketing
 * pages one click away had no band at all: their hero sits directly on the
 * page ground. Two page shapes is two websites, and this was the more visible
 * half of it.
 *
 * What is left is a wash, not a slab: a breath of brand colour at the top of
 * the page, over the page's own ground, with the page's own ink. It reads as
 * the same surface the app's audience pages use, and it inverts with the theme
 * instead of ignoring it. */
.hero{position:relative;overflow:hidden;
  background:
    radial-gradient(62% 120% at 50% -12%, var(--hero-wash), transparent 62%),
    var(--bg);
  color:var(--ink)}
.hero-inner{max-width:var(--page);margin:0 auto;padding:44px var(--gutter) 52px}
@media (min-width:900px){ .hero-inner{padding:64px var(--gutter) 72px} }
.hero h1{font-size:var(--t-display);line-height:1.06;margin:0 0 var(--s3);
  letter-spacing:-.03em;font-weight:700;max-width:18ch;color:var(--ink)}
.hero .lede{margin:0;font-size:clamp(17px,1.8vw,21px);color:var(--sub);max-width:56ch}
.hero .lede a,.hero .lede2 a{color:var(--link)}
.hero .lede + .meta{margin-top:var(--s4)}
.hero .lede2{margin:var(--s3) 0 0;font-size:16.5px;color:var(--muted);max-width:56ch}
.kicker{margin:0 0 var(--s2);font-size:var(--t-label);font-weight:700;letter-spacing:.16em;
  text-transform:uppercase;color:var(--accent)}
/* Breadcrumb — the one piece of navigation the shared header cannot carry,
   because it is per-page. Guides use it to get back to the hub. */
.crumb{margin:0 0 var(--s4)}
.crumb a{font-size:var(--t-sm);color:var(--link);text-decoration:none;font-weight:600;white-space:nowrap}
.crumb a:hover{color:var(--ink);text-decoration:underline}
/* Effective date / operator block on the legal pages. */
.meta{margin:0;font-size:var(--t-sm);color:var(--muted);line-height:1.75;max-width:70ch}
.meta strong{color:var(--ink)}
.meta a{color:var(--link)}

/* A hero that carries a figure beside the headline. Below 940px it simply
   stacks, which is the right answer on a phone. */
.hero-grid{display:grid;gap:var(--s5);align-items:start}
@media (min-width:940px){
  .hero-grid{grid-template-columns:minmax(0,1.15fr) minmax(0,.85fr);
    gap:clamp(40px,6vw,88px)}
}

/* Numbers, on the dark band. A row under the headline on a phone; a vertical
   spec list beside it once the hero splits in two, because three stats of
   uneven label length wrap into a ragged block in any other arrangement. */
.hero-stats{display:grid;gap:var(--s4) var(--s5);margin:var(--s5) 0 0;padding:0;
  list-style:none;grid-template-columns:repeat(auto-fit,minmax(142px,1fr))}
@media (min-width:940px){
  .hero-stats{grid-template-columns:1fr;gap:0;margin:0}
  .hero-stats li{border-top:1px solid var(--line);padding:var(--s4) 0 0;
    margin:0 0 var(--s4)}
  .hero-stats li:first-child{border-top:0;padding-top:0}
  .hero-stats li:last-child{margin-bottom:0}
  .hero-stats span{margin-top:4px}
  /* Four of them would make a column half the height of the window. */
  .hero-stats.pairs{grid-template-columns:1fr 1fr;gap:0 var(--s5)}
  /* And four numbers cannot each be the loudest thing on the page. A pair of
     columns halves the room a figure has, so the figure steps down with it —
     "130–200" broke across two lines at the single-stat size. */
  .hero-stats.pairs b{font-size:clamp(30px,3.4vw,42px)}
  .hero-stats.pairs li{border-top:1px solid var(--line);
    padding-top:var(--s4);margin-bottom:var(--s4)}
}
/* A quantity is the most persuasive thing a marketing page owns and it was
   being set one notch above body copy. Display face, real size, tabular
   figures (below), and the label under it in micro-caps so the pair reads as
   a figure with a caption rather than as a bolded sentence. */
.hero-stats b{display:block;font-size:clamp(34px,4.4vw,52px);font-weight:700;
  letter-spacing:-.035em;color:var(--ink);line-height:1}
.hero-stats span{display:block;margin-top:7px;font-size:var(--t-xs);color:var(--muted);
  text-transform:uppercase;letter-spacing:.08em;line-height:1.45}

/* Buttons live in the hero and on the page — one surface now, either way. */
.btnrow{display:flex;gap:12px;flex-wrap:wrap;margin:var(--s5) 0 0}
/* The line under the primary button ("418 lessons, 31 paths, ...") sat tight
   under the pill, so the pill and the sentence read as one stacked block
   rather than as an action and a footnote about it. */
.btnrow + .meta{margin-top:var(--s3)}
/* One button, everywhere. It used to be drawn twice — a light-blue pill for
   the navy hero and an accent pill for the page — because the hero was a
   different surface. It is not any more, so the second drawing is gone and
   `.band .btn` no longer has to undo the first. */
.btn{display:inline-block;background:var(--accent);color:var(--btn-ink);
  text-decoration:none;
  font-weight:700;font-size:16.5px;padding:13px 28px;border-radius:999px;
  border:1px solid transparent;transition:filter .18s ease,border-color .18s ease}
.btn:hover{filter:brightness(1.08)}
.btn.ghost{background:var(--card);color:var(--link);border-color:var(--line)}
.btn.ghost:hover{border-color:var(--link);filter:none}

/* ---- Layout ------------------------------------------------------------ */

.wrap{max-width:var(--page);margin:0 auto;padding:0 var(--gutter)}
.wrap.narrow{max-width:820px}
/* Retained: `.wide` was the opt-in on the old pages and is now the default. */
.wrap.wide{max-width:var(--page)}

/* A marketing section. Rules of the road: one idea, one band, a hairline
   between. */
.band{border-top:1px solid var(--line);padding:var(--s6) 0 0;margin:var(--s6) 0 0}
.band:first-child{border-top:0;margin-top:0;padding-top:var(--s6)}
.band > h2{margin-bottom:var(--s3)}
/* A heading and the row of cards it introduces are two things, not one. With
   only the heading's own 16px between them the cards crowded the sentence,
   and at the widths where the heading wraps to two lines it collided with
   the top of the first card. The same air goes under a card row before the
   footnote that qualifies it. */
.band > h2 + .grid,
.band > .lede + .grid,
.band.tutor > .grid{margin-top:var(--s5)}
.grid + .meta,
.plans + .meta,
.band > .grid + .meta{margin-top:var(--s4)}
.band .kicker{color:var(--muted)}
/* "Your personal tutor" is the claim the whole product rests on, not a section
   label — so the tutor band's eyebrow opts out of the muted micro-caps every
   other band uses and reads as a line of type: accent colour, sentence case,
   set between h3 and h2 so it leads the eye into "Meet Tooki" instead of
   sitting above it as a tag. Deliberately scoped to `.band.tutor` alone; the
   quiet eyebrow is right everywhere else and is what makes this one land. */
.band.tutor > .kicker{color:var(--accent);font-size:clamp(20px,2.2vw,28px);
  font-weight:700;letter-spacing:-.01em;text-transform:none;line-height:1.2;
  margin:0 0 var(--s1)}
.intro{margin:0 0 var(--s4);color:var(--sub);max-width:var(--measure);font-size:18px}

/* Grid children default to `min-width:auto`, which lets a wide child (a table,
   a long URL) push the whole grid — and therefore the page — sideways. Every
   grid on the site opts out of that; wide things scroll inside their own box
   instead. */
.split > *,.rail > *,.doc > *,.grid > *,.flow > *,.facts > *,.hero-grid > *{min-width:0}

/* The editorial two-column: the section's label sticks in the left column
   while its content scrolls past on the right. This is the layout a phone
   cannot have, and the reason a wide window is worth something here. */
.split{display:grid;gap:var(--s4)}
@media (min-width:1000px){
  .split{grid-template-columns:minmax(200px,270px) minmax(0,1fr);
    gap:var(--s5) clamp(36px,5vw,72px);align-items:start}
  .split__label{position:sticky;top:26px}
}
.split__label h2{margin:0 0 var(--s2)}
.split__label p{font-size:var(--t-sm);color:var(--muted);margin:0;max-width:34ch}
.split__body > :first-child{margin-top:0}
.split__body > :last-child{margin-bottom:0}

/* Main column plus a narrow rail — a form and the things worth putting beside
   it. Below 900px the rail simply follows the content. */
.rail{display:grid;gap:var(--s4)}
@media (min-width:900px){
  .rail{grid-template-columns:minmax(0,1fr) 320px;
    gap:clamp(28px,4vw,56px);align-items:start}
}

/* The article sheet: an elevated surface that holds prose. It overlaps the
   hero by design — the page reads as one object rather than two bands. */
.card{background:var(--card);border:1px solid var(--line);border-radius:var(--radius-lg);
  padding:var(--s5) var(--s4);margin-top:-24px;box-shadow:var(--shadow);
  /* The hero is positioned, so a box that overlaps it has to be positioned too
     or the navy band paints over its top edge. */
  position:relative;z-index:1}
@media (min-width:700px){ .card{padding:var(--s6) var(--s5)} }
.card > h2{margin-top:var(--s6)}
.card > h2:first-child, .card > section:first-of-type > h2{margin-top:0}
.card > h3{margin-top:var(--s4)}
.card > section + section{margin-top:var(--s6)}

/* ---- The document layout (reference + legal) --------------------------- */
/* Table of contents in its own column, sticky, on anything wide enough to
   hold one; a compact row of pills above the content on a phone. */

.doc{display:grid;gap:var(--s4);align-items:start}
@media (min-width:1000px){
  /* Narrower than the marketing pages on purpose: with the nav in its own
     column the sheet lands at about 70 characters with no slack inside it,
     which is the whole point of giving a reference page two columns. */
  /* Centered, and the hero above it narrowed to match. The block was capped
     at 900 with no auto margin, so inside the 1200 page column it sat hard
     left and every pixel of the leftover 300 piled up on the right — which
     reads as a page that failed to load something rather than as margin.
     Slack either side is margin; slack on one side is a mistake. */
  .doc{max-width:900px;margin-inline:auto;
    grid-template-columns:230px minmax(0,1fr);
    gap:clamp(28px,3.4vw,46px)}
  /* A reference page is one column, so the title sits over the thing it
     titles instead of reaching 300px further right than the sheet below it. */
  body:has(.doc) .hero-inner{max-width:900px}
  .doc__nav{position:sticky;top:26px;max-height:calc(100vh - 52px);overflow:auto;
    /* The sheet beside it is pulled up over the hero; the nav is not, or its
       heading would sit on the navy band and lose its contrast. */
    margin-top:var(--s3)}
  .doc .card{margin-top:-24px}
}
/* The column IS the measure here, so the prose inside it does not need a
   second one — that would only add slack on the right. */
.doc .card{--measure:100%}

.toc{background:var(--tint);border:1px solid var(--line);border-radius:var(--radius);
  padding:var(--s3) 18px;margin:-24px 0 0;
  /* Overlaps the positioned hero, so it has to be positioned itself. */
  position:relative;z-index:1}
.toc h2{font-size:var(--t-label);letter-spacing:.14em;text-transform:uppercase;
  color:var(--muted);margin:0 0 var(--s2);font-weight:700}
.toc ul{margin:0;padding:0;list-style:none;display:flex;flex-wrap:wrap;gap:7px}
.toc li{margin:0}
.toc a{display:inline-block;background:var(--card);border:1px solid var(--line);
  border-radius:999px;padding:5px 12px;font-size:var(--t-sm);font-weight:600;
  text-decoration:none;transition:border-color .18s ease,color .18s ease}
.toc a:hover{border-color:var(--link)}
@media (min-width:1000px){
  .toc{margin:0;background:transparent;border:0;padding:0 0 0 var(--s3);
    border-left:2px solid var(--line)}
  .toc ul{display:block}
  .toc li{margin:0 0 2px}
  .toc a{display:block;background:transparent;border:0;border-radius:8px;
    padding:6px 8px;font-weight:600;color:var(--sub);line-height:1.35}
  .toc a:hover{color:var(--link);background:var(--tint)}
}

/* ---- Tiles / grids ----------------------------------------------------- */

/* Row gap and column gap are NOT the same measurement here. Above 640px the
   grid is two or three columns and 16px is a column gutter between tiles that
   sit side by side. Below it every grid collapses to ONE column, and that same
   16px silently becomes the vertical gap between whole cards — a phone then
   shows a run of tiles pressed together, while the sections around them are
   96px apart. The stacked state gets a real row gap; the wide state is
   unchanged. */
.grid{display:grid;gap:var(--s4) var(--s3);grid-template-columns:1fr;
  margin:var(--s4) 0 0;padding:0;list-style:none}
.grid.auto{grid-template-columns:repeat(auto-fit,minmax(250px,1fr))}
@media (min-width:640px){ .grid{gap:var(--s3)}
  .grid.two,.grid.three,.grid.four{grid-template-columns:1fr 1fr} }
@media (min-width:960px){
  .grid.three{grid-template-columns:repeat(3,1fr)}
  .grid.four{grid-template-columns:repeat(4,1fr)}
}

/* 10px, not 6px, between the heading and the sentence under it. A tile is the
   most repeated object on the site and it carries three registers of text
   (h3, body, note); at 6px they read as one block with the first line in bold
   rather than as a title and its answer. Padding stays at 20px so the tile
   itself does not grow. */
.tile{background:var(--card);border:1px solid var(--line);border-radius:var(--radius);
  padding:20px;margin:0;display:flex;flex-direction:column;gap:var(--s2)}
.tile h3{margin:0}
.tile p{margin:0;font-size:16px;max-width:none}
.tile .note{font-size:14.5px;color:var(--muted)}
.tile.linked{position:relative;transition:border-color .18s ease, transform .18s ease,
  box-shadow .18s ease}
.tile.linked:hover{border-color:var(--link);transform:translateY(-2px);box-shadow:var(--shadow-sm)}
.tile a.tilelink{font-weight:700;text-decoration:none;color:var(--link)}
/* The whole tile is the hit target; the link keeps the accessible name. */
.tile a.tilelink::after{content:"";position:absolute;inset:0}
.tile .arrow{margin-top:auto;padding-top:var(--s2);font-size:var(--t-sm);
  font-weight:700;color:var(--link)}

/* Facts as numbers rather than sentences. */
.facts{display:grid;gap:var(--s4) var(--s5);margin:var(--s4) 0 0;padding:0;list-style:none;
  grid-template-columns:repeat(auto-fit,minmax(150px,1fr))}
.facts div,.facts li{min-width:0;margin:0}
.facts b{display:block;font-size:clamp(34px,4.4vw,52px);line-height:1;
  letter-spacing:-.035em;color:var(--link);font-weight:700}
.facts span{display:block;margin-top:8px;color:var(--muted);font-size:14.5px;line-height:1.45}

/* Numbered steps. */
.flow{list-style:none;margin:var(--s4) 0 0;padding:0;display:grid;gap:12px;
  grid-template-columns:repeat(auto-fit,minmax(210px,1fr))}
@media (min-width:620px){ .flow.pairs{grid-template-columns:1fr 1fr} }
.flow li{position:relative;min-width:0;border:1px solid var(--line);
  border-radius:var(--radius);padding:18px;background:var(--field);margin:0}
.flow .n{display:inline-grid;place-items:center;width:26px;height:26px;border-radius:999px;
  background:var(--link);color:#fff;font-size:14px;font-weight:700}
@media (prefers-color-scheme: dark){
 :root:not([data-theme='light']) .flow .n{color:#08101F} }
:root[data-theme='dark'] .flow .n{color:#08101F}

.flow b{display:block;margin:12px 0 5px;font-size:16px;color:var(--ink)}
.flow i{font-style:normal;color:var(--sub);font-size:var(--t-sm);line-height:1.5}

/* Short claims, one line each — the honest-limitations list, the rules list. */
.rules{list-style:none;margin:var(--s3) 0 0;padding:0;display:grid;gap:13px;
  max-width:var(--measure)}
.rules li{padding-left:18px;position:relative;color:var(--sub);line-height:1.55;margin:0}
.rules li::before{content:"";position:absolute;left:0;top:10px;width:7px;height:7px;
  border-radius:999px;background:var(--link)}
.rules li b,.rules li strong{color:var(--ink)}
.rules.tight{gap:10px}
/* A rules list whose items are a TITLE and a paragraph rather than a bolded
   opening clause and the rest of the sentence. The audience pages' read-through
   bands are that shape — MarketingListPanel draws the title on its own line —
   and the plain `.rules` bold has to stay inline for /about and /support. */
.rules.stack li b{display:block;margin-bottom:3px}
/* Two columns of short claims where the list is long and the window is wide. */
@media (min-width:860px){ .rules.cols{grid-template-columns:1fr 1fr;gap:13px var(--s5);max-width:none} }

/* A plain raised surface: a panel inside a band that is not a link tile and
   not the article sheet. The two halves of a "start here / talk to us" row,
   for instance. */
.surface{background:var(--card);border:1px solid var(--line);
  border-radius:var(--radius);padding:22px;margin:0}
.surface > :last-child{margin-bottom:0}
.surface .note{font-size:14.5px;color:var(--muted)}

/* Yes / no columns — what a teacher can and cannot see. */
.see{border-radius:var(--radius);border:1px solid var(--line);background:var(--card);padding:22px}
.see h3{display:flex;align-items:center;gap:8px}
.see.yes h3{color:var(--yes)}
.see.no h3{color:var(--no)}
.see ul{margin:12px 0 0;padding-left:20px;color:var(--sub);font-size:15.2px}
.see li{margin:0 0 9px}

/* Pills: difficulty bands, counts, minutes. */
.pills{display:flex;flex-wrap:wrap;gap:7px;margin:0 0 var(--s3);padding:0;list-style:none}
.pill{background:var(--pill);color:var(--pill-ink);border-radius:999px;padding:4px 11px;
  font-size:14px;font-weight:600;white-space:nowrap;margin:0}
/* The difficulty band. Named `level`, not `band`: `.band` is the section
   layout class, and a <li class="pill band"> would inherit it. */
.pill.level{background:transparent;border:1px solid var(--line);color:var(--sub)}

/* Quieter inset blocks: a definition, a caveat, a single important sentence. */
.panel{background:var(--tint);border:1px solid var(--line);border-radius:var(--radius-sm);
  padding:var(--s3) 18px;margin:var(--s4) 0 0;max-width:var(--measure)}
.panel p:last-child,.panel ul:last-child{margin-bottom:0}
.panel .plabel{display:block;font-size:13px;font-weight:700;letter-spacing:.12em;
  text-transform:uppercase;color:var(--muted);margin-bottom:6px}
.callout{background:var(--pill);border:1px solid var(--line);border-radius:var(--radius-sm);
  padding:18px 20px;margin:var(--s4) 0 0;color:var(--sub);font-size:15.4px;max-width:40em}
.callout strong{color:var(--ink)}

/* A single sentence set large: the one claim a page is built around. Used
   once per page at most — a page of pull quotes is a page with no argument. */
.statement{margin:0;padding:0;border:0;max-width:22ch;
  font-size:clamp(23px,3vw,33px);line-height:1.22;font-weight:700;
  letter-spacing:-.025em;color:var(--ink)}
.statement em{font-style:normal;color:var(--link)}
.statement.wide{max-width:30ch}
.statement + p{margin-top:var(--s4)}

/* ---- Catalogue --------------------------------------------------------- */
/* Used by the generated pages web/courses.html and web/cursos.html. The
   markup comes from tool/generate_courses_page.py; the styling lives here so
   the catalogue is the same product as the rest of the site.

   THE ARTWORK IS CSS AND SVG, ON PURPOSE
   --------------------------------------
   A catalogue of 32 paths that is 32 identical white rectangles cannot be
   scanned; the eye has nothing to catch. Every card therefore carries a
   cover: a tinted wash, the site's dotted grid, and one geometric mark drawn
   in inline SVG that says what the path is about. None of it is an image
   file — there is no illustration budget, no new binary in the repo, no
   second origin to add to firebase.json's CSP, and it recolours itself in
   dark mode instead of going grey.

   Every cover reads one custom property, `--h`, a hue the generator stamps on
   the <article>. Stages get their own hue and the paths inside a stage sit a
   few degrees either side of it, so a stage reads as a family from across the
   page and its members are still told apart. The whole span stays inside the
   cyan-to-blue window the brand lives in: nothing here goes green, purple or
   warm. Shape, not colour, is what makes a path unmistakable — 32 hues would
   be a rainbow, and 32 marks is a legend. */

/* A row of jump links under the hero. Stays a pill row at every width — the
   grid beside it wants the full page, so this cannot become a side rail. */
.jump{background:var(--card);border:1px solid var(--line);border-radius:var(--radius);
  padding:var(--s3) 18px;margin-top:-24px;box-shadow:var(--shadow);
  position:relative;z-index:1}
.jump h2{font-size:var(--t-label);margin:0 0 var(--s2);text-transform:uppercase;
  letter-spacing:.12em;color:var(--muted);font-weight:700}
.jump ol{display:flex;flex-wrap:wrap;gap:8px 10px;margin:0;padding:0;list-style:none}
.jump li{margin:0}
.jump a{display:inline-flex;align-items:center;gap:8px;background:var(--pill);
  color:var(--pill-ink);border:1px solid var(--line);border-radius:999px;padding:6px 13px;
  font-size:var(--t-sm);font-weight:600;text-decoration:none;
  transition:border-color .18s ease,color .18s ease}
.jump a:hover{border-color:var(--link);color:var(--link)}
/* The same hue the stage's cards wear, so the nav and the page below it are
   visibly the same set of things. */
.jump a::before{content:"";width:9px;height:9px;border-radius:999px;flex:none;
  background:hsl(var(--h,210) 72% 48%)}

/* ---- A stage ----------------------------------------------------------- */
/* A short coloured segment laid over the hairline that opens the section, in
   the stage's own hue: the cheapest possible "a new chapter starts here" on a
   page this long, and it ties the section to the cards inside it. */
.band.stage{position:relative}
.band.stage::before{content:"";position:absolute;top:-2px;left:0;width:76px;height:3px;
  border-radius:999px;background:hsl(var(--h,210) 72% 48%)}

/* ---- One path ---------------------------------------------------------- */
/* Cover first, then title, one-line tag, description, counts, and its lessons
   behind a disclosure so the page is scannable before it is exhaustive. */
/* Three across on a 1120px page. Wider than `.grid.auto`'s tiles because a
   card here carries a cover, a description and four counts; four across turns
   the description into a column of two-word lines. */
.grid.paths{grid-template-columns:repeat(auto-fit,minmax(min(100%,300px),1fr))}

.path{--h:210;background:var(--card);border:1px solid var(--line);
  border-radius:var(--radius);padding:0;overflow:hidden;display:flex;
  flex-direction:column;margin:0;
  transition:border-color .18s ease,transform .18s ease,box-shadow .18s ease}
.path:hover{border-color:hsl(var(--h) 70% 52%);transform:translateY(-2px);
  box-shadow:var(--shadow-sm)}

.cover{position:relative;aspect-ratio:16/9;overflow:hidden;
  border-bottom:1px solid var(--line);
  background:
    radial-gradient(112% 132% at 14% -10%, hsl(var(--h) 84% 62% / .34), hsl(var(--h) 84% 62% / 0) 64%),
    linear-gradient(148deg, hsl(var(--h) 56% 96%) 0%, hsl(var(--h) 44% 88%) 100%)}
/* The dotted grid, the one texture the site already uses to say "this is
   material, not decoration". */
.cover::before{content:"";position:absolute;inset:0;
  background-image:radial-gradient(hsl(var(--h) 44% 40% / .2) 1px, transparent 1px);
  background-size:15px 15px}
.cover svg{position:relative;display:block;width:100%;height:100%}

/* The five inks every mark is drawn with. Declared once; no cover carries a
   colour of its own, so dark mode is five overrides rather than 32. */
.c-fill{fill:hsl(var(--h) 68% 40%)}
.c-mid{fill:hsl(var(--h) 52% 58%)}
.c-soft{fill:hsl(var(--h) 60% 46% / .22)}
.c-acc{fill:hsl(var(--h) 88% 50%)}
.c-line{fill:none;stroke:hsl(var(--h) 68% 40%);stroke-width:3.2;
  stroke-linecap:round;stroke-linejoin:round}
.c-line.thin{stroke-width:2;opacity:.6}
.c-line.acc{stroke:hsl(var(--h) 88% 50%)}

@media (prefers-color-scheme: dark){

  /* On the dark ground the wash inverts and every ink lightens; a mark drawn
     for white paper is invisible here, so the five inks are restated rather
     than dimmed. */
  :root:not([data-theme='light']) .cover{background:
    radial-gradient(112% 132% at 14% -10%, hsl(var(--h) 74% 56% / .3), hsl(var(--h) 74% 56% / 0) 64%),
    linear-gradient(148deg, hsl(var(--h) 40% 20%) 0%, hsl(var(--h) 46% 12%) 100%)}
  :root:not([data-theme='light']) .cover::before{background-image:radial-gradient(hsl(var(--h) 60% 76% / .16) 1px, transparent 1px)}
  :root:not([data-theme='light']) .c-fill{fill:hsl(var(--h) 72% 70%)}
  :root:not([data-theme='light']) .c-mid{fill:hsl(var(--h) 48% 52%)}
  :root:not([data-theme='light']) .c-soft{fill:hsl(var(--h) 64% 68% / .2)}
  :root:not([data-theme='light']) .c-acc{fill:hsl(var(--h) 92% 76%)}
  :root:not([data-theme='light']) .c-line{stroke:hsl(var(--h) 72% 70%)}
  :root:not([data-theme='light']) .c-line.acc{stroke:hsl(var(--h) 92% 76%)}
  :root:not([data-theme='light']) .jump a::before{background:hsl(var(--h,210) 78% 66%)}
  :root:not([data-theme='light']) .band.stage::before{background:hsl(var(--h,210) 78% 66%)}
  :root:not([data-theme='light']) .tbar i{background:hsl(var(--h,210) 78% 62%)}
}
:root[data-theme='dark'] .cover{background:
    radial-gradient(112% 132% at 14% -10%, hsl(var(--h) 74% 56% / .3), hsl(var(--h) 74% 56% / 0) 64%),
    linear-gradient(148deg, hsl(var(--h) 40% 20%) 0%, hsl(var(--h) 46% 12%) 100%)}
:root[data-theme='dark'] .cover::before{background-image:radial-gradient(hsl(var(--h) 60% 76% / .16) 1px, transparent 1px)}
:root[data-theme='dark'] .c-fill{fill:hsl(var(--h) 72% 70%)}
:root[data-theme='dark'] .c-mid{fill:hsl(var(--h) 48% 52%)}
:root[data-theme='dark'] .c-soft{fill:hsl(var(--h) 64% 68% / .2)}
:root[data-theme='dark'] .c-acc{fill:hsl(var(--h) 92% 76%)}
:root[data-theme='dark'] .c-line{stroke:hsl(var(--h) 72% 70%)}
:root[data-theme='dark'] .c-line.acc{stroke:hsl(var(--h) 92% 76%)}
:root[data-theme='dark'] .jump a::before{background:hsl(var(--h,210) 78% 66%)}
:root[data-theme='dark'] .band.stage::before{background:hsl(var(--h,210) 78% 66%)}
:root[data-theme='dark'] .tbar i{background:hsl(var(--h,210) 78% 62%)}


.path-body{padding:18px 20px 16px;display:flex;flex-direction:column;flex:1}
.path h3{font-size:18.5px;margin:0 0 4px}
.path .tag{margin:0 0 var(--s2);color:var(--muted);font-size:var(--t-sm);font-style:italic}
.path p.d{margin:0 0 var(--s3);color:var(--sub);font-size:15px}
.path .pills{margin:auto 0 12px}
.path details{border-top:1px solid var(--line);padding-top:10px}
.path summary{cursor:pointer;font-size:15px;font-weight:700;color:var(--link);
  list-style:none;padding:2px 0}
.path summary::-webkit-details-marker{display:none}
.path summary::before{content:"\25B8";display:inline-block;width:1em}
.path details[open] summary::before{content:"\25BE"}
.path details ol{margin:10px 0 2px;padding-left:20px;color:var(--sub);font-size:14.2px}
.path details li{margin:0 0 6px}
.path details li .m{color:var(--muted);font-size:13.5px;white-space:nowrap}

/* ---- Where the time goes ----------------------------------------------- */
/* The page used to claim a flat "64 hours of material", which is the sum of
   the authored per-lesson estimates read out to two significant figures. It
   is a real number and it is more precise than it has any right to be — it
   averages 48 seconds a screen, which is fair for a lesson card and
   optimistic for a lab. So the page now gives a range and shows its working
   here instead. */
.tbar{display:block;height:8px;min-width:64px;margin:7px 0 0;border-radius:999px;
  background:var(--pill);overflow:hidden}
.tbar i{display:block;height:100%;width:var(--w,0%);border-radius:999px;
  background:hsl(var(--h,210) 72% 48%)}
td.num,th.num{text-align:right;font-variant-numeric:tabular-nums;white-space:nowrap}
tfoot th,tfoot td{border-top:2px solid var(--line);border-bottom:0;
  color:var(--ink);font-weight:700}

/* The closing invitation on a long page. */
.cta{text-align:center;margin-top:var(--s6)}
.cta h2{margin:0 0 var(--s2)}
.cta p{margin:0 auto;color:var(--sub);max-width:52ch}
.cta .btnrow{justify-content:center;margin-top:var(--s4)}

/* ---- Tables ------------------------------------------------------------ */
/* A table is one of the few things worth giving real width to. It scrolls
   inside its own box rather than widening the page. */

.tablewrap{overflow-x:auto;-webkit-overflow-scrolling:touch;margin:var(--s3) 0 0;
  border:1px solid var(--line);border-radius:var(--radius-sm);background:var(--card)}
table{border-collapse:collapse;width:100%;min-width:420px;font-size:15px}
caption{text-align:left;color:var(--muted);font-size:14.5px;padding:var(--s2) 14px 0}
th,td{text-align:left;padding:11px 14px;border-bottom:1px solid var(--line);vertical-align:top}
tr:last-child th,tr:last-child td{border-bottom:0}
thead th{font-size:13.5px;text-transform:uppercase;letter-spacing:.06em;color:var(--muted);
  font-weight:700}
tbody th{font-size:15px;color:var(--ink);font-weight:600;white-space:nowrap}
td{color:var(--sub)}

/* ---- Disclosure (FAQ, lesson lists) ------------------------------------ */

.faq{border:1px solid var(--line);border-radius:var(--radius-sm);margin:0 0 10px;
  background:var(--card);overflow:hidden;transition:border-color .18s ease}
.faq:hover{border-color:var(--link)}
.faq>summary{cursor:pointer;list-style:none;padding:15px 44px 15px 18px;position:relative;
  font-weight:700;font-size:15.5px;color:var(--ink)}
.faq>summary::-webkit-details-marker{display:none}
/* The ring, INSIDE the clip. `.faq` is `overflow:hidden` so its corners stay
   round, and the summary's border box is exactly `.faq`'s padding box — so the
   global 3px-outset focus ring at the top of this file was drawn outside the
   clip and painted nothing. Keyboarding through the ~48 questions on /help had
   no visible focus at all. Same ring, drawn 3px in. */
.faq>summary:focus-visible{outline-offset:-3px}
.faq>summary::after{content:"+";position:absolute;right:18px;top:50%;transform:translateY(-50%);
  font-size:20px;font-weight:600;color:var(--muted);line-height:1}
.faq[open]>summary::after{content:"–"}
.faq[open]>summary{border-bottom:1px solid var(--line)}
.faq .body{padding:16px 18px 4px}
.faq .body > :last-child{margin-bottom:14px}
.faq .body p,.faq .body ul,.faq .body ol{max-width:var(--measure)}

/* ---- Forms ------------------------------------------------------------- */

form{margin:0}
.field{margin:0 0 var(--s3)}
label{display:block;font-size:15px;font-weight:700;margin:0 0 6px;color:var(--ink)}
.req{color:var(--link)}
input,select,textarea{width:100%;font:inherit;font-size:15.5px;color:var(--ink);
  background:var(--field);border:1px solid var(--field-line);border-radius:var(--radius-sm);
  padding:11px 13px;transition:border-color .18s ease}
input:hover,select:hover,textarea:hover{border-color:var(--muted)}
input:focus,select:focus,textarea:focus{border-color:var(--link)}
textarea{min-height:120px;resize:vertical}
/* Two fields side by side once there is room for both. */
.row{display:grid;gap:14px;grid-template-columns:repeat(auto-fit,minmax(210px,1fr))}
button[type=submit]{width:auto;background:var(--accent);color:var(--btn-ink);border:0;
  border-radius:999px;padding:12px 28px;font:inherit;font-weight:700;font-size:15.5px;
  cursor:pointer;transition:filter .18s ease}
button[type=submit]:hover{filter:brightness(1.08)}
button[type=submit][disabled]{opacity:.6;cursor:default;filter:none}
.status{display:none;margin-top:14px;padding:12px 14px;border-radius:var(--radius-sm);
  font-size:var(--t-sm)}
.status.ok{display:block;background:rgba(45,212,191,.12);border:1px solid rgba(45,212,191,.4);
  color:#0F766E}
.status.err{display:block;background:rgba(248,113,113,.12);border:1px solid rgba(248,113,113,.4);
  color:#b91c1c}
@media (prefers-color-scheme: dark){

  :root:not([data-theme='light']) .status.ok{color:#5EEAD4}
  :root:not([data-theme='light']) .status.err{color:#FCA5A5}
}
:root[data-theme='dark'] .status.ok{color:#5EEAD4}
:root[data-theme='dark'] .status.err{color:#FCA5A5}


/* ---- Guide diagrams ---------------------------------------------------- */
/* The eight guides in web/guides/ illustrate with inline SVG: no image files
   and no external requests, so the enforced CSP can never blank one out, and
   every colour is a page token so the figures follow light and dark with the
   rest of the site. This lived as an identical <style> block copied into all
   eight pages, with a note to fold it in here once the stylesheet rewrite
   landed. It landed. */
figure.fig{margin:var(--s4) 0 0;padding:16px 14px 12px;background:var(--tint);
  border:1px solid var(--line);border-radius:var(--radius-sm);max-width:var(--measure)}
figure.fig svg{display:block;width:100%;max-width:440px;height:auto;margin:0 auto}
figure.fig figcaption{margin:12px 2px 0;font-size:13.5px;color:var(--muted);
  line-height:1.5;text-align:center}
.fig-t{font-family:inherit;font-size:13px;fill:var(--sub)}
.fig-t.hi{fill:var(--ink);font-weight:700;font-size:14px}
.fig-t.sm{font-size:12px;fill:var(--muted)}
.fig-box{fill:var(--card);stroke:var(--line);stroke-width:1.5}
.fig-box.ac{stroke:var(--grad2);stroke-width:2}
.fig-fill{fill:var(--grad2);opacity:.85}
.fig-soft{fill:var(--grad2);opacity:.16}
.fig-axis{stroke:var(--line);stroke-width:1.5;fill:none}
.fig-dash{stroke:var(--muted);stroke-width:1.5;fill:none;stroke-dasharray:5 5;opacity:.7}
.fig-line{fill:none;stroke:var(--grad2);stroke-width:2.5;stroke-linecap:round;stroke-linejoin:round}
.fig-line.alt{stroke:var(--muted);stroke-dasharray:6 5;stroke-width:2}
.fig-dot{fill:var(--grad2)}
.fig-ah{fill:var(--muted)}
.fig-con{stroke:var(--muted);stroke-width:1.5;fill:none;opacity:.9}
/* On a phone the card's own padding is the widest thing in the way, so a
   figure claims most of it back — at 320px that is the difference between a
   legible label and a smudge. Stays inside the card: the pull is 20px against
   24px of padding. */
@media (max-width:560px){
  figure.fig{margin-left:-20px;margin-right:-20px;padding:14px 8px 12px;max-width:none}
}

/* ---- Tail -------------------------------------------------------------- */
/* The generated footer follows <main> immediately, so the last band needs its
   own clearance or the two collide. */
main{margin-bottom:var(--s6)}

/* ===========================================================================
   The front page — web/landing.html and web/inicio.html.

   `/` used to be the Flutter shell, which meant four megabytes of engine
   before the pitch could paint. It is HTML now (see
   tool/generate_landing_page.py), so these are the components that page needs
   and the rest of the site does not. Everything else on it — .band, .card,
   .grid, .btn, .kicker, .intro — is the same component set every other page
   uses, which is the point.
   ======================================================================== */

/* THE ONE GRADIENT.
 *
 * The hero keeps its own ground: the app's hero is a dark space scene, and a
 * front page that opens on the ice-blue page ground reads as a document rather
 * than as a product. One gradient and a dot field, painted once — no video, no
 * parallax, no render loop.
 *
 * What changed: it is the APP'S gradient now. ToorovaColors.brandGradient is
 * a diagonal sweep from #1E6FD6 (bright azure) into #0D47A1 (deep navy), and
 * the site was carrying a near-flat #0E1A38 slab with a 30%-alpha wash over
 * it — the same idea at a strength nobody could see. So the linear pass is the
 * brand sweep, taken down toward the palette's shadow navy the way
 * `brandGradientDeep` does, and the azure radial is spent at full brand
 * saturation in the TOP RIGHT, which is the one part of this hero no text
 * crosses at any width: the headline column stops at 50% and the preview card
 * over there paints its own surface.
 *
 * This is the site's single gradient moment, and it is deliberately the only
 * one. A gradient on every band is not a brand, it is a template — so the
 * bands below this hero sit on the flat page ground, and the only other
 * blue-on-blue surface anywhere is the closing CTA card, whose two stops are
 * four percent apart and read as one solid blue.
 *
 * Contrast, measured against the darkest stop the copy actually crosses
 * (#0F2F63): the headline 12.0:1, the lede 9.4:1, the small print 7.8:1, and
 * the dimmest text on the hero (#93A9CE) 5.5:1. Every one of those is above
 * AA, and above it with room, because the gradient moves under them. */
/* THE HERO IS A SKY, and it is made of gradients so it weighs nothing.
   Four glows over one diagonal base: cool blue high on the right where the
   planet sits, violet down its right flank, and the amber the mascot wears
   entering from the left edge and again low on the right, so the warm and
   the cool meet across the middle where the headline sits. */
.home-hero{color:#F2F5FF;
  background:
    /* Teal behind the big planet, which is what makes the top right read as
       lit rather than merely dark. */
    radial-gradient(38% 52% at 86% 6%, rgba(34,160,196,.34), rgba(34,160,196,0) 66%),
    radial-gradient(54% 72% at 98% 0%, rgba(42,110,214,.44), rgba(42,110,214,0) 66%),
    /* Violet down the right flank and into the lower corner. */
    radial-gradient(46% 68% at 104% 62%, rgba(124,88,228,.42), rgba(124,88,228,0) 68%),
    radial-gradient(38% 50% at 88% 104%, rgba(150,92,214,.30), rgba(150,92,214,0) 68%),
    /* The warm side: a broad glow at the left edge, and a small echo of it
       low on the right so the two halves talk to each other. */
    radial-gradient(42% 96% at -10% 18%, rgba(236,150,34,.46), rgba(236,150,34,0) 66%),
    radial-gradient(26% 40% at 4% 82%, rgba(214,122,40,.20), rgba(214,122,40,0) 70%),
    radial-gradient(52% 82% at 10% 104%, rgba(28,62,140,.50), rgba(28,62,140,0) 72%),
    linear-gradient(152deg,#12275A 0%,#0C1B3C 46%,#070C1C 100%)}
/* The bands and planets. Behind the dot grid and the constellation, in front
   of the gradient; never in front of the copy, and never hit-testable. */
.hero-sky{position:absolute;inset:0;width:100%;height:100%;
  pointer-events:none;z-index:0;
  /* Held off the headline's column on a wide screen, so the type sits on
     flat ground rather than across a crest. */
  -webkit-mask-image:linear-gradient(100deg,#000 0%,rgba(0,0,0,.62) 20%,rgba(0,0,0,.62) 38%,#000 60%);
  mask-image:linear-gradient(100deg,#000 0%,rgba(0,0,0,.62) 20%,rgba(0,0,0,.62) 38%,#000 60%)}
.home-hero::before{content:"";position:absolute;inset:0;pointer-events:none;
  background-image:radial-gradient(rgba(200,222,255,.20) 1px, transparent 1px);
  background-size:34px 34px;opacity:.5;
  -webkit-mask-image:radial-gradient(70% 60% at 60% 20%,#000,transparent);
  mask-image:radial-gradient(70% 60% at 60% 20%,#000,transparent)}
.hero-stars{position:absolute;inset:0;width:100%;height:100%;
  pointer-events:none;z-index:0;
  /* Faded out where the copy sits, so the constellation is atmosphere and
     never something the headline has to compete with. */
  -webkit-mask-image:radial-gradient(110% 95% at 55% 28%,#000,rgba(0,0,0,.25) 70%,transparent);
  mask-image:radial-gradient(110% 95% at 55% 28%,#000,rgba(0,0,0,.25) 70%,transparent)}
.home-hero .hero-inner{position:relative;z-index:1;padding-top:clamp(40px,6vw,76px);
  padding-bottom:clamp(48px,7vw,88px)}
.home-hero h1{color:#F2F5FF;max-width:14ch}
.home-hero .lede{color:#C6D4EE}
.home-hero .lede2,.home-hero .note{color:#93A9CE}
/* On the dark hero the primary is the SATURATED brand blue with white type,
   the way the app draws it. `.btn` alone resolves to the accent, and in the
   dark palette the accent is a light blue with dark ink — correct on a light
   page, and a secondary-looking pill on this one. */
.home-hero .btn{background:linear-gradient(180deg,#2563EB,#1D4ED8);color:#fff;
  box-shadow:0 14px 30px -16px rgba(37,99,235,.8)}
.home-hero .btn.ghost{background:rgba(255,255,255,.06);color:#DCE9FF;
  border-color:rgba(159,194,255,.5);box-shadow:none}
.home-hero .btn.ghost:hover{border-color:#9FC2FF;background:rgba(255,255,255,.12)}
@media (min-width:940px){ .home-hero .hero-grid{align-items:center} }

/* The three ways in, on one line: two store lockups and the browser. The
   browser option is a TONAL pill rather than a home-made badge — Apple and
   Google require their marks at equal weight to each other, and a third badge
   in the same register reads as a store nobody has heard of. */
.stores{display:flex;flex-wrap:wrap;gap:12px 14px;align-items:center;
  margin:var(--s3) 0 0}
.store{display:inline-flex;line-height:0;border-radius:6px;opacity:.85;
  transition:opacity .15s ease}
.store:hover{opacity:1}
.store img{height:40px;width:auto;display:block}
.store:focus-visible,.store-web:focus-visible{outline:2px solid var(--focus);
  outline-offset:3px}
/* The browser door, as a link rather than a pill. It is the third way in, not
   a third decision, and a filled control here would weigh as much as the
   primary six lines above it. */
.store-web{display:inline-flex;align-items:center;height:40px;
  padding:0 18px;border-radius:8px;border:1px solid var(--line);
  font-size:15px;font-weight:700;text-decoration:none;color:var(--link);
  transition:border-color .15s ease,background-color .15s ease}
.store-web:hover{border-color:var(--link);
  background:color-mix(in srgb, var(--link) 8%, transparent)}
.home-hero .store-web{color:#BFD8FF;border-color:rgba(191,216,255,.38)}
.home-hero .store-web:hover{border-color:#BFD8FF;
  background:rgba(191,216,255,.10)}

/* Secondary buttons that separate from the page by HUE, not by luminance. An
   outline pill on a pale ground is a white pill on a white page. */
.btn.tonal{background:color-mix(in srgb, var(--accent) 16%, transparent);
  color:var(--accent);border-color:color-mix(in srgb, var(--accent) 45%, transparent);
  font-size:15.5px;padding:11px 22px}
.btn.tonal:hover{filter:none;border-color:var(--accent)}
.btn.light{background:#fff;color:#0F172A}

/* The product preview beside the headline: what a path looks like once you are
   inside. Static markup, because it is a picture of the product rather than
   the product. */
.preview{background:var(--card);border:1px solid var(--line);
  border-radius:var(--radius);padding:var(--s3);box-shadow:var(--shadow);
  color:var(--ink)}
.preview-head{display:grid;grid-template-columns:1fr auto;
  align-items:center;gap:2px 12px;padding:0 4px var(--s3)}
.preview-head b{font-size:17px;font-weight:700}
.preview-head span{grid-column:1;color:var(--muted);font-size:14px}
.preview-head em{grid-column:2;grid-row:1 / span 2;font-style:normal;
  font-weight:700;font-size:14px;color:var(--accent);
  border:2px solid var(--accent);border-radius:999px;padding:8px 10px}
.preview-row{display:flex;justify-content:space-between;align-items:center;
  gap:12px;padding:13px 14px;border:1px solid var(--line);
  border-radius:12px;margin-top:8px;font-size:15px;font-weight:600}
.preview-row i{font-style:normal;color:var(--muted);font-weight:600;font-size:14px}
.preview-row.now{background:var(--tint);border-color:var(--accent)}
/* What the audience pages' previews need and the front page's does not: a
   closing footnote in the product's own words (the guardian dashboard's privacy
   line, the pooled-seat note) and, on /for-schools, the licence card's seat
   meter drawn straight onto the panel the way EducatorLicenceCard draws it. */
.preview .line{margin:0;font-size:15px;font-weight:700;color:var(--ink)}
.preview .tbar{margin-top:9px}
.preview .meta{margin:11px 0 0}

/* Tooki, drawn as vectors in the page (tool/generate_landing_page.py). The
   section introduces a character, so it shows her rather than describing her.
   The band's copy is capped at --measure, so on a wide screen the column to
   its right is empty and she goes there, with the three cards keeping the full
   width underneath; only column-gap is set, so the copy's own vertical rhythm
   is untouched. Below 900px she is centred under the intro and scales with the
   viewport — never wider than the gutter allows, so she cannot push the page
   sideways. */
.tooki{display:block;height:auto;max-width:100%;
  width:clamp(124px,32vw,168px);margin:0 auto var(--s4)}
/* The two soft parts follow the reader's colour scheme, at the strengths
   _TookiPainter uses: barely-there on a light page, brighter against dark. */
.tk-aura{opacity:.10}
.tk-shadow{opacity:.11}
@media (prefers-color-scheme: dark){
  :root:not([data-theme='light']) .tk-aura{opacity:.20}
  :root:not([data-theme='light']) .tk-shadow{opacity:.26}
}
:root[data-theme='dark'] .tk-aura{opacity:.20}
:root[data-theme='dark'] .tk-shadow{opacity:.26}
@media (min-width:900px){
  .band.tutor{display:grid;grid-template-columns:minmax(0,1fr) auto;
    column-gap:clamp(28px,5vw,64px);align-items:start}
  .band.tutor > .kicker,.band.tutor > h2,.band.tutor > .intro{grid-column:1}
  .band.tutor > .tooki{grid-column:2;grid-row:1 / span 3;align-self:center;
    width:clamp(160px,17vw,200px);margin:0}
  .band.tutor > .grid{grid-column:1 / -1}
}

/* The five stages, as a numbered spine. */
.stages{list-style:none;margin:var(--s5) 0 0;padding:0;
  display:grid;gap:var(--s3)}
.stages li{display:grid;grid-template-columns:auto 1fr;gap:var(--s3);
  align-items:start;background:var(--card);border:1px solid var(--line);
  border-radius:var(--radius);padding:var(--s3) var(--s4)}
.stages b{display:grid;place-items:center;width:34px;height:34px;
  border-radius:999px;background:var(--accent);color:var(--btn-ink);
  font-size:16px}
.stages h3{margin:0 0 4px;font-size:19px}
.stages p{margin:0;color:var(--sub);font-size:15.5px}
.stages .meta{margin-top:6px;color:var(--muted);font-size:var(--t-sm)}

/* One plan, one column of what is in it. */
.plan{max-width:var(--measure);margin:var(--s5) auto 0;text-align:left}
.plan-tag{margin:0 0 var(--s3);font-weight:700;font-size:19px}
.plan ul{margin:0;padding-left:1.15em;display:grid;gap:9px;
  color:var(--sub);font-size:16px}
.plan .meta{margin-top:var(--s3)}

/* The closing band: the one place on the page that is brand blue end to end. */
.final{padding-bottom:var(--s7)}
.final-card{background:linear-gradient(160deg,var(--cta1),var(--cta2));
  border-radius:calc(var(--radius) + 6px);color:#fff;text-align:center;
  padding:clamp(34px,5vw,64px) var(--s4)}
.final-card h2{color:#fff;margin:0 0 var(--s2)}
.final-card p{color:rgba(255,255,255,.96);margin:0 auto;max-width:44ch}
.final-card .btnrow{justify-content:center;margin-top:var(--s6)}

/* ---------------------------------------------------------------------------
   THE TOOKI DEMO — a lesson, played rather than described.

   The tutor section said Tooki "can see the question you just got wrong, and
   which wrong answer you picked". That is the interesting claim and it was
   asked to land as a sentence, next to a drawing. Every AI product on the
   internet says "personal AI tutor"; almost none can show the specific thing
   this one does, which is teach from the particular misconception behind the
   particular option a learner chose.

   So it is a loop of five beats: the question, a wrong answer chosen, Tooki
   naming the misconception rather than the mistake, the learner changing
   something and watching the consequence, and Tooki asking again instead of
   telling. Fifteen seconds, no narration needed.

   CSS ONLY. No script, so nothing to load, nothing to hydrate, nothing the
   content security policy has to be relaxed for, and it cannot break the page
   if it fails. The whole thing is one timeline: each beat runs the same
   keyframes with its own delay, which is why the sequence stays in step
   without a single line of JavaScript coordinating it.
--------------------------------------------------------------------------- */
.demo {
  position: relative;
  margin: 28px auto 0;
  max-width: 460px;
  min-height: 268px;
  border: 1px solid var(--line, #d9e0f0);
  border-radius: 18px;
  background: var(--card, #fff);
  padding: 22px 22px 18px;
  overflow: hidden;
  text-align: left;
}
.demo__beat {
  position: absolute;
  inset: 22px 22px 18px;
  opacity: 0;
  animation: demo-beat 15s infinite;
}
.demo__beat:nth-child(1) { animation-delay: 0s; }
.demo__beat:nth-child(2) { animation-delay: 3s; }
.demo__beat:nth-child(3) { animation-delay: 6s; }
.demo__beat:nth-child(4) { animation-delay: 9s; }
.demo__beat:nth-child(5) { animation-delay: 12s; }

/* Each beat owns a fifth of the loop: in, hold, out, then wait its turn. */
@keyframes demo-beat {
  0%    { opacity: 0; transform: translateY(6px); }
  2%    { opacity: 1; transform: translateY(0); }
  18%   { opacity: 1; transform: translateY(0); }
  20%   { opacity: 0; transform: translateY(-6px); }
  100%  { opacity: 0; transform: translateY(-6px); }
}

.demo__q { font-weight: 650; margin: 0 0 14px; line-height: 1.4; }
.demo__opt {
  display: block;
  border: 1px solid var(--line, #d9e0f0);
  border-radius: 11px;
  padding: 9px 13px;
  margin-bottom: 8px;
  font-size: .95rem;
}
.demo__opt--picked {
  border-color: #d4573f;
  background: rgba(212, 87, 63, .09);
  font-weight: 600;
}
.demo__says {
  display: flex;
  gap: 11px;
  align-items: flex-start;
  margin-top: 4px;
}
/* Tooki's own face, at 26px. This was a flat blue disc — a placeholder that
   shipped, on the one section of the page that is about a character. */
.demo__tooki { flex: 0 0 auto; width: 26px; height: 26px; margin-top: 2px; display: block; }
.demo__says p { margin: 0; line-height: 1.45; }
.demo__slider {
  margin: 16px 0 12px;
  height: 5px;
  border-radius: 99px;
  background: var(--line, #d9e0f0);
  position: relative;
}
.demo__knob {
  position: absolute; top: 50%;
  width: 17px; height: 17px; border-radius: 50%;
  background: var(--accent, #1746c9);
  transform: translate(-50%, -50%);
  animation: demo-knob 15s infinite;
  animation-delay: 9s;
  left: 22%;
}
@keyframes demo-knob {
  0%, 2%  { left: 22%; }
  12%     { left: 82%; }
  20%,100%{ left: 82%; }
}
.demo__curve { display: block; width: 100%; height: 62px; }
.demo__curve path { stroke-width: 2.5; fill: none; stroke-linecap: round; }
.demo__note { font-size: .82rem; opacity: .72; margin: 8px 0 0; }

/* Motion is the point here, so when it is unwelcome the beats simply stack and
   read as a transcript. A demo nobody can sit through is worse than a list. */
@media (prefers-reduced-motion: reduce) {
  .demo { min-height: 0; }
  .demo__beat {
    position: static;
    inset: auto;
    opacity: 1;
    animation: none;
    padding-bottom: 16px;
    border-bottom: 1px solid var(--line, #d9e0f0);
    margin-bottom: 16px;
  }
  .demo__beat:last-child { border-bottom: 0; margin-bottom: 0; padding-bottom: 0; }
  .demo__knob { animation: none; left: 82%; }
}

/* Two plan cards side by side, free first. Falls to one column on a phone,
   where stacking is the only honest layout and the order still puts the free
   tier before the price. */
.plans { display: grid; gap: 18px; grid-template-columns: 1fr; align-items: start; }
@media (min-width: 760px) { .plans { grid-template-columns: 1fr 1fr; } }
.plan-name { font-weight: 700; margin: 0 0 2px; }
.plan-price { font-size: clamp(34px, 3.6vw, 46px); font-weight: 700;
  letter-spacing: -.035em; margin: 0; line-height: 1.04; color: var(--ink); }
/* A 46px price with a bullet list starting 4px under it reads as one block,
   and the free card had no annual line to break the two apart the way the
   premium card accidentally did. The gap belongs to whatever follows the
   price, so both cards get it whether or not they carry a second line. */
.plan-price + ul, .plan-price + .plan-tag { margin-top: 16px; }
.plan-tag + ul { margin-top: 14px; }
.plan ul + .btnrow { margin-top: 20px; }
.plan--free { opacity: .96; }
.btn.ghost { background: transparent; border: 1px solid currentColor; }

/* ---------------------------------------------------------------------------
   Two fixes for things that shipped broken.

   1. THE DEMO COLLAPSED TO A COLUMN OF SINGLE LETTERS.
      `.band.tutor` becomes a two-column grid above 900px — copy on the left,
      Tooki's portrait in an `auto` track on the right. The demo figure was
      dropped in as a third child and landed in that auto track, and because
      every one of its beats is absolutely positioned the figure has no
      intrinsic width at all. An auto track sized to nothing gave it about
      fifty pixels, and the question wrapped one character per line.
      It spans both columns now, like the card grid below it already did.

   2. SMALL PRINT WAS UNREADABLE ON THE DARK HERO.
      `.meta` resolves to `var(--muted)`, which is chosen against the light
      page background. On the near-black hero it is close to invisible — and
      it is carrying the lesson count and the subscription note, which are the
      two lines a visitor scans for substance. Small text needs MORE contrast
      than body copy, not less, so this sits a step brighter than `.lede2`.
--------------------------------------------------------------------------- */
@media (min-width: 900px) {
  .band.tutor > .demo { grid-column: 1 / -1; justify-self: center; width: 100%; }
}
.demo { width: 100%; box-sizing: border-box; }
.home-hero .meta { color: #B8C9E6; }
.home-hero .meta strong { color: #F2F5FF; }

/* ===========================================================================
   TYPOGRAPHIC POLISH
   ===========================================================================
   The type was competent and anonymous: correct sizes, correct weights, and
   nothing that could only be this site. Inter and Space Grotesk were already
   doing the hard part; what was missing was the setting.

   Everything here is additive and reversible. No size, weight, colour or
   family changes — the page is recognisably the same page. What changes is
   how the words sit: where lines break, how a section announces itself, how
   numbers align, how a rule separates one idea from the next.
   =========================================================================== */

/* 1. NO ORPHANS. `balance` distributes a heading across its lines instead of
      leaving one word stranded on the last one. It is the single largest
      difference between type that was set and type that was merely typed, and
      it costs one line. Browsers without it wrap exactly as before. */
h1, h2, h3, .lede, .hero-stats b, .plan-price, .statement, .tile h3,
.split__label p, .hero-stats span {
  text-wrap: balance;
}
/* `pretty` only fixes the LAST lines of body copy — cheaper than balance, and
   what you want on a paragraph, where balancing every line reads as fussy. */
p, li, .intro, .meta, .callout {
  text-wrap: pretty;
}

/* 2. THE KICKER GETS A RULE. An uppercase letterspaced label above a heading
      is the most generic device in web design. A short rule leading into it
      turns the same words into an editorial mark — the reference sites both
      earn their rhythm from dividers rather than decoration. */
.kicker {
  display: flex;
  align-items: center;
  gap: .7em;
}
.kicker::before {
  content: "";
  width: 2.2em;
  height: 1px;
  background: currentColor;
  opacity: .5;
  flex: 0 0 auto;
}
/* The hero eyebrow keeps its rule but the dark band needs it brighter — and
   it needs its own INK, which it did not have. `.kicker` resolves to
   `var(--accent)`, and the light palette's accent is #1D4ED8: a deep blue
   chosen to clear AA as text on white paper, and 1.6:1 against the navy hero.
   The first three words on the front page were effectively invisible to a
   reader whose machine is set to light. This is the hero's own pale blue —
   the same one the ghost button's border uses — at 6.0:1 on the darkest part
   of the gradient the label crosses. */
.home-hero .kicker { color: #9FC2FF; }
.home-hero .kicker::before { opacity: .7; }
/* The tutor band's kicker is a headline in its own right (see the note beside
   `.band.tutor > .kicker`), so it does not want a label's rule. */
.band.tutor > .kicker::before { display: none; }

/* 3. SECTIONS ANNOUNCE THEMSELVES WITH A HAIRLINE, not a gap. Newron's page
      is paced by rules; ours was paced by whitespace alone, which reads as
      continuous scroll rather than chapters. */
.band + .band { border-top: 1px solid var(--line); }
.band.final + .band, .band + .band.final { border-top: 0; }

/* 4. NUMBERS LINE UP. Tabular figures on anything that presents a quantity,
      so "413" and "5,110" share a column edge instead of drifting. Slashed
      zero because a stat that reads as an O is a stat read twice. */
.hero-stats b, .facts b, .plan-price, .m, .num, td.num, .demo__note,
.home-hero .meta, .stages .meta, .pill, .preview-head em, .tbar + .meta,
.path .pills, .plan-tag {
  font-variant-numeric: tabular-nums slashed-zero;
  font-feature-settings: "tnum" 1, "zero" 1;
}

/* 5. LINKS SIT UNDER THE TEXT, not through it. The default underline crosses
      descenders; an offset one reads as a considered choice and stays legible
      at small sizes. */
main a:not(.btn):not(.tv-ghost) {
  text-underline-offset: .18em;
  text-decoration-thickness: from-font;
}

/* 6. DISPLAY TYPE TIGHTENS AS IT GROWS. Tracking that suits 34px is loose at
      68px — optical sizing done by hand, because a variable optical axis is
      not available on these faces. */
@media (min-width: 900px) {
  .hero h1 { letter-spacing: -.035em; }
  h2 { letter-spacing: -.025em; }
}

/* 7. A MEASURE FOR THE LEDE. 56ch is a comfortable line for 17px and a long
      one at 21px, which is what the hero uses. Slightly tighter, so the
      opening paragraph lands in two or three lines rather than four. */
.hero .lede { max-width: 46ch; }
.home-hero .lede2 { max-width: 46ch; }


/* ---------------------------------------------------------------------------
   THE MARKED WORD.

   Origin Financial sets one pivotal word per headline in italic, and it is the
   single device that stops a page of sentence-case headings reading as a
   template. Borrowed, but with our own face: Fraunces italic is what the APP
   already uses for editorial display, so this makes the website look more like
   Toorova rather than more like Origin.

   Used sparingly and on purpose — two headings on the front page. A mark on
   every heading is not emphasis, it is a pattern, and a pattern emphasises
   nothing.
--------------------------------------------------------------------------- */
.mark {
  font-family: 'Fraunces', Georgia, 'Times New Roman', serif;
  font-style: italic;
  font-weight: 600;
  letter-spacing: -.01em;
  /* Fraunces runs small against Space Grotesk at the same size, and its
     italic sits a hair high. Both corrected optically rather than left to
     the metrics. */
  font-size: 1.06em;
  padding-right: .04em;
}
.home-hero .mark { color: #CFE0FF; }

/* ---------------------------------------------------------------------------
   THE LAST FEW DEGREES.

   Everything above fixes what was wrong. This is the part that makes a page
   look like somebody cared: depth on the cards, motion that acknowledges the
   pointer, and a first-line treatment on the opening paragraph. Each is a few
   lines and none of them changes a layout.
--------------------------------------------------------------------------- */

/* Cards lift on approach. A flat card that does nothing under the cursor
   reads as a screenshot; two hundred milliseconds of rise reads as software.
   Transform and shadow only — never a size or a colour, because either would
   reflow or fight the palette. */
.card, .tile {
  transition: transform .22s cubic-bezier(.2,.7,.3,1),
              box-shadow .22s cubic-bezier(.2,.7,.3,1),
              border-color .22s ease;
  will-change: transform;
}
/* Scoped to cards that are TILES IN A ROW. The article sheet on a legal or
   reference page is also a `.card`, and a 900x4000px sheet of privacy policy
   that rises three pixels when the pointer crosses it is not depth, it is the
   page twitching. It is also not a thing a reader can pick up: nothing
   happens if you click it. */
@media (hover: hover) and (prefers-reduced-motion: no-preference) {
  .grid > .card:hover, .plans > .card:hover, .tile:hover {
    transform: translateY(-3px);
    box-shadow: var(--e3);
    border-color: color-mix(in srgb, var(--accent) 30%, var(--edge));
  }
}

/* The plan a visitor is meant to choose earns a ring rather than a shout —
   and it sits one rung higher than the free card beside it, which is the
   cheapest way to say which of two equal rectangles is the offer. */
.plan:not(.plan--free) {
  box-shadow: 0 0 0 1px color-mix(in srgb, var(--accent) 22%, transparent),
              var(--e3);
}

/* The opening paragraph of a band gets a slightly larger measure and a hair
   more leading than body copy. An intro set identically to the paragraphs
   beneath it is not an intro, it is the first paragraph. */
.intro { font-size: clamp(17px, 1.35vw, 19.5px); line-height: 1.62; }

/* Section headings sit closer to their own kicker than to the band above, so
   a label and its heading read as one object. */
.kicker + h2, .kicker + h1 { margin-top: 0; }

/* Buttons get real depth and a press. A CTA that does not move when clicked
   is the most common tell of a template. */
.btn {
  transition: transform .14s cubic-bezier(.2,.7,.3,1), box-shadow .18s ease,
              background-color .18s ease;
}
@media (prefers-reduced-motion: no-preference) {
  .btn:active { transform: translateY(1px); }
}

/* Focus that looks intentional. The default ring is a browser artefact; this
   is the same accent the rest of the page uses, offset so it never crowds the
   glyphs it surrounds. */
:where(a, button, .btn, summary):focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 3px;
  border-radius: 6px;
}

/* ---------------------------------------------------------------------------
   ONE SITE, TWO SECTION IDIOMS.

   The marketing pages announce a section with a `.kicker` above a centred
   heading; the audience and reference pages use `.split`, where a sticky label
   column sits beside the content. Both are good and neither should go — a
   procurement page wants a label you can still see when you are four
   paragraphs into the answer, and a landing page does not.

   What they must not do is look like two different websites, which they did:
   the split label was a bare heading while the marketing kicker had a rule,
   an accent and letterspacing. This gives the split label the same signature
   in its own proportions, so a reader moving from the front page to Schools
   sees the same hand.
--------------------------------------------------------------------------- */
.split__label h2 {
  position: relative;
  padding-top: .55em;
}
.split__label h2::before {
  content: "";
  position: absolute;
  top: 0;
  left: 0;
  width: 2.2em;
  height: 2px;
  border-radius: 2px;
  background: var(--accent);
  opacity: .85;
}
/* The label's sub-line is the same role as `.intro`, so it gets the same
   voice: a step down in size, a step up in leading. */
.split__label p {
  line-height: 1.55;
}

/* Section headings across BOTH idioms sit on the same rhythm, so the eye
   learns one interval instead of two. */
.band > h2, .split__label > h2 { scroll-margin-top: 96px; }

/* ===========================================================================
   FURTHER.
   ===========================================================================
   The previous pass fixed the setting. This one gives the page structure a
   voice: a spine the sections hang off, a display size that actually commands
   the top of the page, and the small asymmetries that separate a designed
   layout from a styled one. Still no new colours and no new fonts.
   =========================================================================== */

/* 1. A NUMBERED SPINE. Sections count themselves. This is the single most
      effective device for making a long page feel authored rather than
      accumulated — the reader always knows how far in they are, and the
      numerals give the left margin something to hold. Counter-based, so
      adding or moving a section renumbers itself. */
main { counter-reset: band; }
.band.split { counter-increment: band; }
@media (min-width: 1080px) {
  .band.split .split__label h2::after {
    content: counter(band, decimal-leading-zero);
    position: absolute;
    right: calc(100% + 26px);
    top: .5em;
    font-family: var(--tv-font-display, inherit);
    font-size: 13px;
    font-weight: 700;
    letter-spacing: .08em;
    color: var(--accent);
    opacity: .38;
    font-variant-numeric: tabular-nums;
  }
}

/* 2. THE HERO COMMANDS THE PAGE. A 68px cap on a 1600px monitor is a heading
      on a big screen; it is not a front page. The ceiling rises and the
      tracking closes with it, which is what large display type wants. */
.home-hero h1 { font-size: clamp(38px, 6.2vw, 88px); letter-spacing: -.04em; }
@media (min-width: 1400px) { .home-hero h1 { max-width: 15ch; } }

/* 3. THE OPENING PARAGRAPH IS SET LIKE AN OPENING PARAGRAPH. One step up in
      size, one step down in colour, and a rule beneath it that says the
      introduction has ended and the section has begun. */
.band > .intro:first-of-type { position: relative; padding-bottom: var(--s3); }
.band > .intro:first-of-type::after {
  content: "";
  position: absolute;
  left: 0; bottom: 0;
  width: 64px; height: 1px;
  background: var(--line);
}

/* 4. CARDS GET A HAIRLINE OF ACCENT AT THE TOP EDGE — visible only on hover,
      so a grid at rest stays calm and a grid under the cursor feels alive.
      Same scope as the lift above, and for the same reason. */
.tile, .card { position: relative; overflow: hidden; }
.tile::after, .grid > .card::after, .plans > .card::after {
  content: "";
  position: absolute; inset: 0 0 auto 0; height: 2px;
  background: linear-gradient(90deg, var(--accent), transparent 72%);
  opacity: 0; transition: opacity .22s ease;
}
@media (hover: hover) and (prefers-reduced-motion: no-preference) {
  .tile:hover::after, .grid > .card:hover::after,
  .plans > .card:hover::after { opacity: .9; }
}

/* 5. LISTS READ AS TYPESET, not as bullets. A hairline marker aligned to the
      text baseline, instead of the browser's disc. */
.split__body ul:not(.grid):not(.flow) { list-style: none; padding-left: 0; }
.split__body ul:not(.grid):not(.flow) > li {
  position: relative; padding-left: 1.35em;
}
.split__body ul:not(.grid):not(.flow) > li::before {
  content: ""; position: absolute; left: 0; top: .72em;
  width: .7em; height: 1px; background: var(--accent); opacity: .55;
}

/* 6. THE FINAL BAND EARNS ITS WEIGHT. The closing card is the last thing a
      reader sees; a flat panel there is a shrug. */
.final-card { position: relative; overflow: hidden; }
.final-card::before {
  content: "";
  position: absolute; inset: -40% -10% auto -10%; height: 180%;
  background: radial-gradient(60% 60% at 50% 0%,
              color-mix(in srgb, var(--accent) 16%, transparent), transparent 70%);
  pointer-events: none;
}
.final-card > * { position: relative; }

/* ===========================================================================
   DEPTH.
   ===========================================================================
   The audit's finding, in one line: every surface on this site was a flat
   fill inside a 1px hairline, and a hairline separates two regions where
   light and shadow make an OBJECT. A page of hairlines reads as a diagram of
   a page. Nothing on it looks like something you could pick up.

   So the primary surfaces — the things a visitor is meant to want, click or
   take away — are defined by light now: the app's own elevation ladder
   (--e1/--e2/--e3 above, which are ToorovaElevation's numbers), a rim rather
   than a border (--edge), and on the dark ground a one-pixel lit top edge
   (--edge-lit), because a surface that catches light from above is the whole
   reason a card looks raised instead of merely paler.

   WHAT STAYS FLAT, AND WHY
   ------------------------
   Elevation is a signal, and a signal every surface sends is not a signal.
   These stay exactly as they were, deliberately:

     .panel .callout      an inset aside — it is meant to sit INTO the page
     .toc .doc__nav       navigation furniture, not content
     .flow li             numbered steps on a --field fill: a well, not a card
     table .tablewrap     dense rows; a shadow per row is noise
     .rules .pills        lists and tags
     .tv-* (chrome.css)   the header and footer are bands, not objects

   And the legal and reference pages keep their quiet: their `.card` is the
   article sheet, which already carried the page's one shadow and does not
   gain a hover state (see the scoping note beside `.grid > .card:hover`).

   ONE RUNG UP FROM THE APP, ON PURPOSE
   ------------------------------------
   ToorovaElevation.card is 2px down and 8px of blur — deliberately nearly
   subliminal, because in the app a card is a thing you are reading inside.
   A marketing tile is a thing you are looking AT, in a grid of twelve, from
   two feet away, and at --e1 the grid is still a grid of rectangles. So a
   tile rests at --e2 (the app's `raised`) and lifts to --e3 under a pointer.
   That is one rung, never two: a hover that promotes a card past a dialog is
   how a hover starts feeling like a bug.
   ======================================================================== */

/* The objects. Fill, rim, lit edge, shadow — in that order, every time. */
.tile,
.surface,
.see,
.stages li,
.path,
.preview,
.jump,
.demo,
.grid > .card,
.plans > .card {
  border-color: var(--edge);
  box-shadow: inset 0 1px 0 0 var(--edge-lit), var(--e2);
}

/* The page's ONE primary object gets the top rung: the article sheet that
   overlaps the hero, the product preview beside the headline, and the tutor
   demo — each is the single thing its section is about. */
.card,
.preview,
.demo,
.jump {
  box-shadow: inset 0 1px 0 0 var(--edge-lit), var(--e3);
}
.card { border-color: var(--edge); }

/* A linked tile is the one surface that has to answer a pointer, so it keeps
   its own hover rather than inheriting the generic one. */
.tile.linked:hover,
.path:hover {
  box-shadow: inset 0 1px 0 0 var(--edge-lit), var(--e3);
}

/* A card row needs room for the light it is casting. `.card` carries
   margin-top:-24px so an article sheet can overlap the hero it sits under;
   inside a grid that is not an overlap, it is 24px eaten out of the 34px the
   band deliberately puts between a heading and the cards it introduces — so
   the row sat 10px under its own sentence, which is the crowding the band
   rule was written to fix. Reset where the overlap is meaningless. */
.grid > .card,
.plans > .card { margin-top: 0; }

/* Light falls from above, so the fill has to as well: on the dark ground a
   card is a lighter rectangle already, and a breath of light along its top
   edge is the difference between a rectangle and a lid. Nothing in light —
   there is no value above white to lift toward.

   The falloff is in PIXELS, not a percentage. A percentage stretches with the
   box, and the tallest `.card` on this site is a four-thousand-pixel privacy
   policy: at `60%` that same rim light becomes a two-thousand-pixel wash down
   the middle of the one surface that is supposed to be quiet. 140px is the
   top edge of a card at any height. */
@media (prefers-color-scheme: dark) {
  :root:not([data-theme='light']) .tile,
  :root:not([data-theme='light']) .surface,
  :root:not([data-theme='light']) .see,
  :root:not([data-theme='light']) .stages li,
  :root:not([data-theme='light']) .path,
  :root:not([data-theme='light']) .preview,
  :root:not([data-theme='light']) .card {
    background-image: linear-gradient(180deg,
      rgba(255,255,255,.035), rgba(255,255,255,0) 140px);
    background-repeat: no-repeat;
  }
}
:root[data-theme='dark'] .tile,
:root[data-theme='dark'] .surface,
:root[data-theme='dark'] .see,
:root[data-theme='dark'] .stages li,
:root[data-theme='dark'] .path,
:root[data-theme='dark'] .preview,
:root[data-theme='dark'] .card {
  background-image: linear-gradient(180deg,
    rgba(255,255,255,.035), rgba(255,255,255,0) 140px);
  background-repeat: no-repeat;
}
/* `.cover` is not in the list above, on purpose: background-image does not
   inherit, so a lift on `.path` cannot reach it — and a rule that said so
   explicitly would out-specify the cover's own artwork and erase it. */

/* The catalogue's covers sit flush against the card body, and a 1px hairline
   between artwork and paper is the one border on the page that is doing real
   work — it is a fold, not an edge. It stays. */

/* Depth costs nothing on a phone except the room to show it. Below 640px the
   grids are one column and the cards run gutter to gutter, so the shadow has
   nowhere to fall sideways; drop to the tight pass, which still lifts the
   card off the ground and does not smear into the card below it. */
@media (max-width: 639px) {
  .tile, .surface, .see, .stages li, .path, .grid > .card, .plans > .card {
    box-shadow: inset 0 1px 0 0 var(--edge-lit), var(--e1);
  }
}

/* The premium plan's ring is restated here because the rules above are the
   same weight and would otherwise take it back off. Same two-part shadow the
   ring always was: a 1px accent stroke, and the top rung under it. */
.plans > .plan:not(.plan--free),
.plans > .plan:not(.plan--free):hover {
  box-shadow: 0 0 0 1px color-mix(in srgb, var(--accent) 22%, transparent),
              var(--e3);
}
@media (max-width: 639px) {
  .plans > .plan:not(.plan--free) {
    box-shadow: 0 0 0 1px color-mix(in srgb, var(--accent) 22%, transparent),
                var(--e2);
  }
}

/* ===========================================================================
   COLOUR PASS — the site was one blue on one grey.

   Every page was built from the same three values: near-black type, white
   cards, a pale blue-grey ground, with a single blue for links and buttons.
   That is calm, and past a certain point calm reads as unfinished: a course
   catalogue where every card is white tells a visitor nothing about what is
   inside any of them.

   So colour arrives from the palette the app already uses, and it arrives
   structurally rather than page by page. Nothing below touches a single
   .html file: the hues rotate with :nth-child, so a page gains colour by
   having cards on it. Text colour is untouched throughout — contrast is a
   promise, and the accents live on borders, tints and marks.
=========================================================================== */

:root{
  --hue-1:#2563EB; /* brand blue      */
  --hue-2:#0EA5A5; /* teal            */
  --hue-3:#7C5CE6; /* violet          */
  --hue-4:#F59E0B; /* amber, Tooki's  */
  --hue-5:#E0567A; /* coral           */
  --hue-6:#16A34A; /* green           */
}

/* ---- section bands ------------------------------------------------------ */
/* A page used to be one flat ground end to end. Alternate bands now carry a
   whisper of tint, which is what gives a long page a rhythm to scroll past. */
.band:nth-of-type(even){
  background:linear-gradient(180deg,
    color-mix(in srgb, var(--hue-1) 5%, transparent),
    transparent 62%)}
.band:nth-of-type(odd) .kicker{color:var(--hue-3)}
.band:nth-of-type(even) .kicker{color:var(--hue-2)}

/* ---- the number that opens a section ----------------------------------- */
.band .step-n,.band .num-lead,.split__n{color:var(--hue-4)}

/* ---- cards in a grid: a coloured edge, rotating through the palette ----- */
.grid .card,.grid .tile{position:relative;overflow:hidden}
.grid .card::before,.grid .tile::before{
  content:"";position:absolute;left:0;right:0;top:0;height:3px;
  background:var(--tv-card-hue,var(--hue-1));opacity:.9}
.grid > :nth-child(6n+1) .card,.grid > :nth-child(6n+1).card,
.grid > :nth-child(6n+1) .tile,.grid > :nth-child(6n+1).tile{--tv-card-hue:var(--hue-1)}
.grid > :nth-child(6n+2) .card,.grid > :nth-child(6n+2).card,
.grid > :nth-child(6n+2) .tile,.grid > :nth-child(6n+2).tile{--tv-card-hue:var(--hue-2)}
.grid > :nth-child(6n+3) .card,.grid > :nth-child(6n+3).card,
.grid > :nth-child(6n+3) .tile,.grid > :nth-child(6n+3).tile{--tv-card-hue:var(--hue-3)}
.grid > :nth-child(6n+4) .card,.grid > :nth-child(6n+4).card,
.grid > :nth-child(6n+4) .tile,.grid > :nth-child(6n+4).tile{--tv-card-hue:var(--hue-4)}
.grid > :nth-child(6n+5) .card,.grid > :nth-child(6n+5).card,
.grid > :nth-child(6n+5) .tile,.grid > :nth-child(6n+5).tile{--tv-card-hue:var(--hue-5)}
.grid > :nth-child(6n+6) .card,.grid > :nth-child(6n+6).card,
.grid > :nth-child(6n+6) .tile,.grid > :nth-child(6n+6).tile{--tv-card-hue:var(--hue-6)}
.grid .card:hover,.grid .tile:hover{
  border-color:color-mix(in srgb, var(--tv-card-hue,var(--hue-1)) 55%, var(--line))}

/* ---- the big numbers ---------------------------------------------------- */
/* Four identical black numerals in a row is a table. Four coloured ones is a
   set of facts, which is what they are. */
.hero-stats li:nth-child(4n+1) b,.facts li:nth-child(4n+1) b{color:var(--hue-1)}
.hero-stats li:nth-child(4n+2) b,.facts li:nth-child(4n+2) b{color:var(--hue-2)}
.hero-stats li:nth-child(4n+3) b,.facts li:nth-child(4n+3) b{color:var(--hue-3)}
.hero-stats li:nth-child(4n+4) b,.facts li:nth-child(4n+4) b{color:var(--hue-4)}

/* ---- the catalogue ------------------------------------------------------ */
/* The covers already carry a hue per course, at 96% and 88% lightness, which
   on a pale page is white with an idea of colour in it. Deepened, and given
   a coloured edge so a grid of thirty-two reads as thirty-two things. */
.cover{background:
  radial-gradient(112% 132% at 14% -10%, hsl(var(--h) 88% 58% / .5), hsl(var(--h) 88% 58% / 0) 66%),
  linear-gradient(148deg, hsl(var(--h) 72% 90%) 0%, hsl(var(--h) 62% 74%) 100%)}
.cover::before{background-image:radial-gradient(hsl(var(--h) 50% 34% / .26) 1px, transparent 1px)}
.path{border-top:3px solid hsl(var(--h) 72% 52%)}
.path .tag{color:hsl(var(--h) 62% 34%)}
.pill{border-color:color-mix(in srgb, var(--hue-1) 26%, var(--line))}
.pill.level{
  background:color-mix(in srgb, var(--hue-4) 16%, transparent);
  border-color:color-mix(in srgb, var(--hue-4) 42%, var(--line))}

@media (prefers-color-scheme:dark){
  :root:not([data-theme='light']) .cover{background:
    radial-gradient(112% 132% at 14% -10%, hsl(var(--h) 72% 52% / .42), hsl(var(--h) 72% 52% / 0) 66%),
    linear-gradient(148deg, hsl(var(--h) 46% 26%) 0%, hsl(var(--h) 52% 16%) 100%)}
  :root:not([data-theme='light']) .path .tag{color:hsl(var(--h) 70% 74%)}
  :root:not([data-theme='light']) .band:nth-of-type(even){
    background:linear-gradient(180deg,
      color-mix(in srgb, var(--hue-1) 10%, transparent), transparent 62%)}
}
:root[data-theme='dark'] .cover{background:
  radial-gradient(112% 132% at 14% -10%, hsl(var(--h) 72% 52% / .42), hsl(var(--h) 72% 52% / 0) 66%),
  linear-gradient(148deg, hsl(var(--h) 46% 26%) 0%, hsl(var(--h) 52% 16%) 100%)}
:root[data-theme='dark'] .path .tag{color:hsl(var(--h) 70% 74%)}
:root[data-theme='dark'] .band:nth-of-type(even){
  background:linear-gradient(180deg,
    color-mix(in srgb, var(--hue-1) 10%, transparent), transparent 62%)}
