/* #2718 — the public site's stylesheet. Tokens only (ADR 0040): every
   colour, radius and spacing is a --tokklr-* variable from tokens.css, so
   the site tracks the apps' palette in light AND dark for free. No
   framework, no CDN — the site promises "no third-party anything" and its
   own stylesheet keeps that promise first. */

* { box-sizing: border-box; }

body.site {
  margin: 0;
  background: var(--tokklr-color-background);
  color: var(--tokklr-color-text);
  font-family: "Geist", system-ui, sans-serif;
  line-height: 1.6;
}

/* Links. Both colours are measured against every ground the site paints behind
   running text, in both themes -- see tests/web/test_site_link_contrast.py.

   `primary-on-cream` now resolves per theme (it was the one token in its family
   with no dark value, so dark mode inherited the cream-ground red and measured
   2.14:1 on a card). Hover deliberately does NOT go to `primary-pressed`: that
   token is a BACKGROUND fill in admin.css and style.css, so retuning it for
   text would repaint buttons elsewhere, and at #A84A2D it measures 2.83:1 on
   dark anyway.

   Hover resolves to `text` instead, which is the same move the tier badge below
   already documents: a resolved light/dark pair that clears both ends without a
   theme block here. Reading it as "the link firms up to full text strength on
   hover" is also the honest description of what it looks like. Colour is not
   the only cue either -- prose links keep the browser's underline. */
.site a { color: var(--tokklr-color-primary-on-cream); }
.site a:hover { color: var(--tokklr-color-text); }

.site-wrap {
  max-width: 1040px;
  margin: 0 auto;
  padding: 0 var(--tokklr-spacing-5);
}

/* --- header --- */
.site-header {
  border-bottom: 1px solid var(--tokklr-color-line);
  background: var(--tokklr-color-surface);
}
.site-header .site-wrap {
  display: flex;
  align-items: center;
  gap: var(--tokklr-spacing-5);
  min-height: 64px;
  flex-wrap: wrap;
  padding-top: var(--tokklr-spacing-2);
  padding-bottom: var(--tokklr-spacing-2);
}
/* The wordmark. It carries NO `color`: it is an <a>, so `.site a` -- a class
   plus a type selector -- outranks this single class and paints it with the
   link colour regardless. The declaration that used to sit here named `text`
   and had never once taken effect, so the rule stated an intent the cascade
   refuses and the site has always rendered the mark in the brand colour.

   Removed rather than promoted to `.site a.site-logo`, which would work but
   would repaint the header. What the site looks like today is what has been
   reviewed; changing it is a design call, not a tidy-up. If the wordmark should
   read as `text`, raising the selector is the one-line way to do it.

   `text-decoration` DOES apply -- `.site a` sets only a colour, so nothing
   outranks it here. */
.site-logo {
  font-weight: 700;
  font-size: 1.35rem;
  letter-spacing: 0.02em;
  text-decoration: none;
}
.site-nav {
  display: flex;
  gap: var(--tokklr-spacing-4);
  flex-wrap: wrap;
  margin-left: auto;
}
.site-nav a {
  text-decoration: none;
  color: var(--tokklr-color-text-muted);
  padding: var(--tokklr-spacing-1) var(--tokklr-spacing-2);
  border-radius: var(--tokklr-radius-sm);
}
.site-nav a[aria-current="page"] {
  color: var(--tokklr-color-text);
  background: var(--tokklr-color-primary-soft);
}
.site-nav a.site-signin {
  color: var(--tokklr-color-text);
  border: 1px solid var(--tokklr-color-line-strong);
  border-radius: var(--tokklr-radius-pill);
  padding: var(--tokklr-spacing-1) var(--tokklr-spacing-4);
}

/* --- hero --- */
.site-hero .site-wrap {
  display: grid;
  grid-template-columns: 1.1fr 0.9fr;
  gap: var(--tokklr-spacing-8);
  align-items: center;
  padding-top: var(--tokklr-spacing-9);
  padding-bottom: var(--tokklr-spacing-9);
}
.site-hero h1 {
  font-size: clamp(2rem, 5vw, 3.1rem);
  line-height: 1.15;
  margin: 0 0 var(--tokklr-spacing-4);
}
.site-hero p { font-size: 1.15rem; color: var(--tokklr-color-text-muted); }
.site-hero img {
  width: 100%;
  height: auto;
  border-radius: var(--tokklr-radius-xl);
}
.site-store-cta {
  display: inline-block;
  margin-top: var(--tokklr-spacing-5);
  padding: var(--tokklr-spacing-3) var(--tokklr-spacing-6);
  border-radius: var(--tokklr-radius-pill);
  background: var(--tokklr-color-primary);
  color: var(--tokklr-color-text-on-primary);
  text-decoration: none;
  font-weight: 600;
}
/* Raised only to outrank `.site a`, which would otherwise paint the label with
   the link colour. It must repeat the rule above rather than pick a new token:
   this said `surface`, which is #FBF7EE in light -- indistinguishable from
   `text-on-primary`, so it looked correct -- and #1E1A14 in dark, putting
   near-black on the terra fill at 3.38:1.

   Same shape as the link defect this change set is about: a token that flips
   with the theme standing in for one that does not. `text-on-primary` is
   #FBF7EE in both themes and reads 4.79:1 on the fill. */
.site a.site-store-cta { color: var(--tokklr-color-text-on-primary); }
.site-store-soon {
  display: inline-block;
  margin-top: var(--tokklr-spacing-5);
  padding: var(--tokklr-spacing-3) var(--tokklr-spacing-6);
  border-radius: var(--tokklr-radius-pill);
  border: 1px dashed var(--tokklr-color-line-strong);
  color: var(--tokklr-color-text-muted);
}

/* --- bands + cards --- */
.site-band { padding: var(--tokklr-spacing-9) 0; }
.site-band.alt { background: var(--tokklr-color-surface); }
/* The band heading. `h1` is in here because five of the six pages open with
   their page title inside a band rather than the hero, and that title was
   marked up as an `h2`: the document outline started at level 2 and a screen
   reader navigating by heading found no page title at all (WCAG 1.3.1). The
   level moved; the size deliberately did not, so this is a semantic fix with
   no visual change. The hero `h1` on the home page keeps its own larger rule. */
.site-band h1,
.site-band h2 {
  font-size: 1.8rem;
  margin: 0 0 var(--tokklr-spacing-3);
}
.site-band > .site-wrap > p.lede {
  max-width: 640px;
  color: var(--tokklr-color-text-muted);
  margin-top: 0;
}
.site-cols {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: var(--tokklr-spacing-6);
  margin-top: var(--tokklr-spacing-6);
}
.site-card {
  background: var(--tokklr-color-surface-elevated);
  border: 1px solid var(--tokklr-color-line);
  border-radius: var(--tokklr-radius-lg);
  padding: var(--tokklr-spacing-6);
}

/* --- the six-phase arc on /method (#2718 W10) ---
 *
 * An <ol>, because the order IS the claim: each step adds exactly one new
 * rhythmic or dynamic idea. The default list marker cannot be styled to sit
 * in a badge, so the number is drawn from a counter and the marker is
 * suppressed -- `list-style: none` on an <ol> keeps the semantics for
 * assistive tech while removing the visual marker, which is why the element
 * is still an <ol> rather than a <ul> with numbers typed into the copy. */
/* The arc opens a new section under the page's <h1>, so it is an <h2> by
 * document structure -- it was an <h3> under an <h2> title, and moved with it
 * when the title became the page's real heading. At the default size that put it at 18.7px between 17.6px
 * card headings above and 16.8px step headings below -- a 1.9px step where a
 * section divider should be, so it read as one more card title. Measured on the
 * rendered page. This restores a visible tier between the page heading and the
 * steps without changing the heading level, which stays correct. */
.site-band .site-arc-heading {
  font-size: 1.35rem;
  margin-top: var(--tokklr-spacing-8);
  /* Restores the UA `h3 { margin-block-end: 1em }` this element used to get
     for free. It was an <h3> matching one class rule that declared no
     bottom margin, so 1em (21.6px) fell through. As an <h2> it matches
     `.site-band h1, .site-band h2`, whose `margin` SHORTHAND sets all four
     sides -- and this rule wins font-size and margin-top on specificity but
     is silent on margin-bottom, so the band's 12px applied and the gap
     narrowed by 9.6px.

     Measured, not reasoned about: an adversarial review rendered both trees
     and read getComputedStyle, and every element below the arc heading on
     /method had moved up ~10px. `1em` rather than a literal 1.35rem so it
     stays tied to the font-size above if that is ever retuned. */
  margin-bottom: 1em;
}

.site-arc {
  list-style: none;
  counter-reset: site-arc;
  margin: var(--tokklr-spacing-6) 0 0;
  padding: 0;
  display: grid;
  gap: var(--tokklr-spacing-4);
}
.site-arc li {
  counter-increment: site-arc;
  position: relative;
  padding: var(--tokklr-spacing-5) var(--tokklr-spacing-6) var(--tokklr-spacing-5)
    calc(var(--tokklr-spacing-6) * 2 + 1rem);
  background: var(--tokklr-color-surface-elevated);
  border: 1px solid var(--tokklr-color-line);
  border-radius: var(--tokklr-radius-lg);
}
.site-arc li::before {
  content: counter(site-arc);
  position: absolute;
  left: var(--tokklr-spacing-5);
  top: var(--tokklr-spacing-5);
  width: 1.75rem;
  height: 1.75rem;
  display: grid;
  place-items: center;
  border-radius: 50%;
  background: var(--tokklr-color-primary);
  color: var(--tokklr-color-text-on-primary);
  font-weight: 700;
  font-size: 0.9rem;
}
.site-arc h3,
.site-arc h4 { margin: 0 0 var(--tokklr-spacing-2); font-size: 1.05rem; }
.site-arc p { margin: 0; color: var(--tokklr-color-text-muted); }
/* --- block badges (#2718 W11) ---
 *
 * Steven, 2026-08-28: "every little block should have a visual badge in it as
 * well." One per content block, from `contract/graphics/siteBadges`.
 *
 * Sized in rem rather than by the intrinsic pixels: the served PNG is 256px so
 * it stays crisp on a 2x display, and the width/height attributes in the markup
 * are there to reserve the box before it loads, not to size it. Without both,
 * the card reflows as each badge arrives.
 *
 * The panels are transparent with ink linework, which reads on the white card
 * and would NOT read on the dark one. The site's dark block redefines the card
 * ground, so a dark twin of this family is owed the moment these appear on it;
 * `build_panels.py` records that gap at the source. */
.site-card-badge {
  display: block;
  width: 3.5rem;
  height: 3.5rem;
  margin: 0 0 var(--tokklr-spacing-4);
}

/* The band variant leads a full-width section rather than a card, so it sits
 * slightly larger and keeps the heading's own top margin. */
.site-band-badge {
  display: block;
  width: 4.5rem;
  height: 4.5rem;
  margin: 0 0 var(--tokklr-spacing-3);
}

.site-card img {
  width: 96px;
  height: 96px;
  object-fit: contain;
  border-radius: var(--tokklr-radius-md);
  margin-bottom: var(--tokklr-spacing-3);
}
/* Card heading. Both levels, one size: the home page's cards sit under an
   h2 section heading and stay h3, while the pages whose title moved to h1
   promoted theirs to h2. The level follows the outline, the size does not. */
.site-card h2,
.site-card h3 { margin: 0 0 var(--tokklr-spacing-2); font-size: 1.1rem; }
.site-card p { margin: 0; color: var(--tokklr-color-text-muted); }

/* --- the two tier tables share one column grid (#2718 W9) ---
 *
 * The price table and the comparison below it are read as ONE grid: you look
 * down a tier's column to see what that money buys. Both were `width: 100%`
 * already and STILL did not line up, because `table-layout` defaults to `auto`
 * and each table sized its own columns to its own content -- the price table's
 * label column is "Solo / 1 seat" and the comparison's is a feature sentence,
 * so the tier columns started at different x positions in each.
 *
 * `table-layout: fixed` plus one shared column grid is the fix, and the widths
 * live HERE rather than on either table so changing one cannot desynchronise
 * them. The four widths must total 100%. */
.site-tiertable { width: 100%; table-layout: fixed; }
.site-tiertable-label { width: 34%; }
.site-tiertable-tier { width: 22%; }

/* --- pricing table --- */
.site-pricing {
  border-collapse: collapse;
  margin-top: var(--tokklr-spacing-6);
  background: var(--tokklr-color-surface-elevated);
  border-radius: var(--tokklr-radius-lg);
  overflow: hidden;
}
.site-pricing th, .site-pricing td {
  padding: var(--tokklr-spacing-4);
  text-align: left;
  border-bottom: 1px solid var(--tokklr-color-line);
}
.site-pricing th { background: var(--tokklr-color-surface); }
.site-pricing td.price { font-weight: 600; white-space: nowrap; }

/* --- free of charge, for everyone (#2718 W9) ---
 *
 * Rendered ABOVE the tier table and outside it. The tuner, the metronome and
 * the starter lessons cost nothing to anybody, and a tick in a tier column is
 * a claim about what a subscription buys -- so these must not live in that
 * table at all. */
.site-free {
  list-style: none;
  /* The bottom margin is load-bearing, not rhythm: with `0` the cards sat
   * flush against the next <h2>, measured at exactly 0px against 58px of
   * clearance everywhere else on the page, so the free block read as a
   * heading for the tier table rather than as its own section. */
  margin: var(--tokklr-spacing-6) 0 var(--tokklr-spacing-8);
  padding: 0;
  display: grid;
  gap: var(--tokklr-spacing-3);
  grid-template-columns: repeat(auto-fit, minmax(15rem, 1fr));
}
/* The badge sits beside the text rather than above it: these items are one
 * short line each, and a stacked badge would make three tall boxes out of
 * three short ones for no gain. `align-items: center` keeps the pairing level
 * when a translation wraps to two lines. */
.site-free li {
  display: flex;
  align-items: center;
  gap: var(--tokklr-spacing-4);
  padding: var(--tokklr-spacing-4);
  border: 1px solid var(--tokklr-color-line);
  border-radius: var(--tokklr-radius-md);
  background: var(--tokklr-color-surface-elevated);
  font-weight: 600;
}

/* --- the feature comparison (#2718 W6/W7) ---
 *
 * These three classes shipped in W6 with NO RULE ANYWHERE, which an
 * adversarial pass caught by looking at the rendered page rather than its
 * markup. The consequences were all invisible to the suite:
 *   - `.site-visually-hidden` did nothing, so the screen-reader label beside
 *     each mark printed on screen: every cell read "checkmark Included";
 *   - the <caption>, meant for assistive tech only, rendered as visible
 *     centred text above the table;
 *   - the table itself had no chrome at all, sitting unstyled directly under
 *     the fully styled price table above it.
 * The comparison reuses the price table's proven chrome rather than inventing
 * its own, so the two read as siblings. */
.site-compare {
  border-collapse: collapse;
  margin-top: var(--tokklr-spacing-6);
  background: var(--tokklr-color-surface-elevated);
  border-radius: var(--tokklr-radius-lg);
  overflow: hidden;
}
.site-compare th, .site-compare td {
  padding: var(--tokklr-spacing-4);
  text-align: left;
  border-bottom: 1px solid var(--tokklr-color-line);
  vertical-align: top;
}
.site-compare thead th { background: var(--tokklr-color-surface); }
/* The ticks sit LEFT, under their left-aligned column headers.
 *
 * This rule used to say `text-align: center` and never once did it: `.site-compare td`
 * is a two-part selector and outranks this single class, so every tick has
 * always rendered left. Measured rather than assumed -- computed `text-align`
 * on a tick cell reads `left`.
 *
 * Left is also the right answer now that the column grid is fixed and the
 * course row carries sentences: a centred tick under a left-aligned header,
 * beside left-aligned copy, is the one that would look misplaced. So the
 * declaration is removed rather than given the specificity to win. */
.site-compare-cell {
  font-weight: 600;
  white-space: nowrap;
}

/* The course-depth row (#2718 W9). It is the row the table exists for -- the
 * course is SPLIT across the tiers -- so it leads the table and carries real
 * copy per tier instead of a tick. Text cells must undo two things the tick
 * cells set: `white-space: nowrap`, which would force a sentence onto one
 * line and blow the fixed column grid apart, and the centring, which makes
 * multi-line copy hard to scan. */
.site-compare-text {
  /* `white-space` is the one that matters: it must beat the tick cells'
   * `nowrap`, and does so on source order at equal specificity, because the
   * text cells carry both classes. Without it a sentence stays on one line and
   * bursts the fixed column grid. */
  white-space: normal;
  font-weight: 400;
}
.site-compare-depth th[scope="row"], .site-compare-depth .site-compare-text {
  background: var(--tokklr-color-surface);
}
.site-compare-depth th[scope="row"] { font-weight: 700; }

/* The canonical clip-rect pattern: removed from the visual flow without being
 * removed from the accessibility tree, which `display: none` would do. */
.site-free-badge {
  flex: 0 0 auto;
  width: 2.5rem;
  height: 2.5rem;
}

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

/* --- footer --- */
.site-footer {
  border-top: 1px solid var(--tokklr-color-line);
  padding: var(--tokklr-spacing-7) 0;
  color: var(--tokklr-color-text-muted);
  font-size: 0.95rem;
}
.site-footer .site-wrap {
  display: flex;
  gap: var(--tokklr-spacing-5);
  flex-wrap: wrap;
  justify-content: space-between;
}
/* `flex-wrap` here is not tidiness, it is the fix for a live defect.
 *
 * The footer carries the locale switcher: seven links, one per supported
 * locale. As a non-wrapping flex row that is 521px of content, so on a 375px
 * phone it pushed the document to 521px and EVERY page on the site scrolled
 * sideways -- the header nav already wrapped, so nothing else gave it away.
 * Measured on the rendered page at the mobile preset; `test_site_stylesheet_
 * covers_its_markup.py` pins it. Adding an eighth locale must not reintroduce
 * this, which is why the rule is on the container rather than a media query
 * tuned to seven. */
.site-footer nav { display: flex; flex-wrap: wrap; gap: var(--tokklr-spacing-4); }

/* #2853 — the locale picker is a disclosure, so the footer carries ONE control
 * instead of seven links. That is what actually retires the 521px overflow the
 * rule above was written for; the rule stays because the legal nav beside it is
 * still a wrapping row.
 *
 * `position: relative` on the details and `absolute` on the panel keep the open
 * panel out of flow, so opening it cannot reflow the footer or push the page. */
.site-langpicker { position: relative; }
.site-langpicker > summary {
  cursor: pointer;
  list-style: none;
  color: var(--tokklr-color-text-muted);
}
.site-langpicker > summary::-webkit-details-marker { display: none; }
.site-langpicker > summary::after { content: " \25BE"; }
.site-langpicker[open] > summary::after { content: " \25B4"; }
.site-langpicker-panel {
  position: absolute;
  bottom: 100%;
  inset-inline-start: 0;
  z-index: 2;
  display: flex;
  flex-direction: column;
  gap: var(--tokklr-spacing-2);
  padding: var(--tokklr-spacing-4);
  margin-block-end: var(--tokklr-spacing-2);
  background: var(--tokklr-color-surface);
  border: 1px solid var(--tokklr-color-line);
  border-radius: var(--tokklr-radius-md);
  white-space: nowrap;
}

@media (max-width: 720px) {
  .site-hero .site-wrap { grid-template-columns: 1fr; }
  .site-cols { grid-template-columns: 1fr; }

  /* The shared column grid is a DESKTOP device, and on a phone it was actively
   * harmful. `table-layout: fixed` with 22% tier columns gives each one about
   * 74px at 375px wide, and `td.price` carries `white-space: nowrap`, so
   * "10.99 per month" could not fit: the three prices overlapped each other and
   * the Complete column was clipped away entirely. Introduced by the W9 change
   * that aligned the two tables, and invisible to every structural test.
   *
   * Aligning the two tables cannot work at this width anyway, so the fixed grid
   * is released and the content sizes the columns again. */
  .site-tiertable { table-layout: auto; }
  .site-pricing td.price { white-space: normal; }
  .site-pricing th, .site-pricing td,
  .site-compare th, .site-compare td { padding: var(--tokklr-spacing-3); }

  /* 15rem is wider than a 375px viewport can give a grid track once the wrap's
   * padding is taken, so `auto-fit` could not fall back to one column and the
   * page scrolled sideways. */
  .site-free { grid-template-columns: 1fr; }
}

/* The tier badge on a feature card. Offline downloads are entitled to the
   Complete tier alone; every other card in that list is on every plan, so a
   flat list read as though this one was too.

   The wash carries the hue and `text` carries the words, and that pairing is
   measured rather than chosen: `text` is a resolved light/dark pair and clears
   both ends (about 15:1 light, 10.9:1 dark) with no theme block needed here.

   This note used to add that `primary-on-cream` measured 2.48:1 here in dark
   mode -- true, and the exact defect #1793 spent a sweep removing. But the fix
   landed on this one badge while `.site a` kept the same token for every link
   on the site, so the note read as a closed defect while the general case went
   on shipping. The token now carries a dark value of its own, and the guard in
   tests/web/test_site_link_contrast.py measures every ground rather than the
   page background alone. That is the part that makes the next fix general
   instead of local. */
.site-tier-badge {
  display: inline-block;
  margin: 0 0 0.4rem;
  padding: 0.15rem 0.55rem;
  border-radius: 999px;
  background: var(--tokklr-color-primary-soft);
  color: var(--tokklr-color-text);
  font-size: 0.72rem;
  font-weight: 600;
  letter-spacing: 0.04em;
  text-transform: uppercase;
}

/* --- the contact form + page CTAs (#2718 W6/W7) ---
 *
 * Every class below was referenced by markup that shipped with NO RULE. The
 * consequences ranged from cosmetic to a silent data loss:
 *
 *   .site-hp is the HONEYPOT. Unstyled it renders as a visible "Website" input
 *   on a public contact form, and the server treats a filled honeypot as a bot
 *   and discards the message while answering exactly like success. A real
 *   visitor who filled it would have been told their message was on its way and
 *   it would have gone nowhere. That is the whole defect class this block
 *   exists to close, and it is why the guard beside it scans templates for
 *   classes no stylesheet defines. */
.site-hp {
  position: absolute;
  left: -9999px;
  width: 1px;
  height: 1px;
  overflow: hidden;
}

.site-narrow { max-width: 46rem; }

/* The contact form is a PANEL, not loose inputs on the page ground (#2718 W9).
 *
 * It previously rendered as unbordered fields floating directly on the body
 * background with no focus treatment beyond the browser default -- the only
 * form on the site, and the one surface where a visitor is asked to type. It
 * now sits in the same elevated card the tables use, so the page reads as one
 * design system rather than a styled marketing site with a plain form stapled
 * to the end of it. */
.site-form {
  display: grid;
  gap: var(--tokklr-spacing-2);
  margin-top: var(--tokklr-spacing-6);
  padding: var(--tokklr-spacing-6);
  border: 1px solid var(--tokklr-color-line);
  border-radius: var(--tokklr-radius-lg);
  background: var(--tokklr-color-surface-elevated);
}
.site-form label {
  font-weight: 600;
  margin-top: var(--tokklr-spacing-4);
}
/* The first label would otherwise push a gap between the card's top padding
 * and its own content, doubling the space above the first field only. */
.site-form label:first-of-type { margin-top: 0; }
.site-form input, .site-form textarea {
  width: 100%;
  padding: var(--tokklr-spacing-3);
  border: 1px solid var(--tokklr-color-line-strong);
  border-radius: var(--tokklr-radius-md);
  background: var(--tokklr-color-surface);
  color: var(--tokklr-color-text);
  font: inherit;
}
.site-form textarea { resize: vertical; min-height: 9rem; }
/* A visible, token-coloured focus ring on every control. The browser default
 * is the accessibility floor, not a design, and on a dark surface it is close
 * to invisible. `:focus-visible` so a mouse click does not draw one. */
.site-form input:focus-visible, .site-form textarea:focus-visible {
  outline: 2px solid var(--tokklr-color-primary);
  outline-offset: 2px;
  border-color: var(--tokklr-color-primary);
}
.site-form > p:last-child {
  margin: var(--tokklr-spacing-5) 0 0;
  color: var(--tokklr-color-text-muted);
}

.site-cta {
  justify-self: start;
  margin-top: var(--tokklr-spacing-5);
  padding: var(--tokklr-spacing-3) var(--tokklr-spacing-6);
  border: 0;
  border-radius: var(--tokklr-radius-pill);
  background: var(--tokklr-color-primary);
  color: var(--tokklr-color-text-on-primary);
  font: inherit;
  font-weight: 600;
  cursor: pointer;
}
.site-cta:hover { filter: brightness(1.08); }
.site-cta:focus-visible {
  outline: 2px solid var(--tokklr-color-primary);
  outline-offset: 3px;
}

.site-note {
  margin-top: var(--tokklr-spacing-5);
  padding: var(--tokklr-spacing-4);
  border-radius: var(--tokklr-radius-md);
  border-left: 4px solid var(--tokklr-color-line-strong);
  background: var(--tokklr-color-surface-elevated);
}
.site-note-ok { border-left-color: var(--tokklr-color-primary); }
.site-note-warn { border-left-color: var(--tokklr-color-danger); }

.site-mail-line { margin-top: var(--tokklr-spacing-6); }
.site-mail { font-weight: 600; }

.site-pricing-cta { margin-top: var(--tokklr-spacing-6); }
.site-teacher-ctas { display: flex; flex-wrap: wrap; gap: var(--tokklr-spacing-4); align-items: center; }
.site-teacher-contact { color: var(--tokklr-color-primary); font-weight: 600; }

/* --- the pages that used to wear Pico (#3475) ---
 *
 * Steven, 2026-09-06: *"privacy, legal and the sign in page all have different
 * layouts and styling than the actual primary website, and i want that to be
 * fixed, the primary site has the proper styling. the administrative backend is
 * different, and thats good, its currently fine as it is, but the marketing
 * theme should be visible on all frontend accessible pages."*
 *
 * The six marketing pages write their own markup against the classes above.
 * The pages that moved here do not: they are forms, cards, definition lists and
 * rendered Markdown, and every one of those elements was styled by PicoCSS,
 * which is class-less and styles bare tags. So this section does the same job
 * for the elements those pages actually use, in the site's tokens.
 *
 * EVERYTHING IS SCOPED TO `.site-app`, which only `site_app_base.html` and
 * `site_doc_base.html` set. That is deliberate: the six marketing pages must
 * render byte-identically after this block as before it, and a bare `input` or
 * `article` rule in this file could not promise that. It also means the two
 * chromes cannot drift into styling the same element two ways.
 *
 * SPECIFICITY. `.site a` is (0,1,1) and outranks any single-class rule, which
 * the links section above already documents. Every link rule below therefore
 * carries `.site-app` as well, or it would lose to a rule 600 lines up.
 */

/* The footer sits at the BOTTOM on a short page. The six marketing pages are
 * all taller than a viewport, so the site never needed this and does not get it
 * here either -- the rule is scoped to `.site-app`, whose pages are mostly
 * short. Measured in headless Chromium at 1200x900 on `/legal` with one
 * document published: the card ended at row 302, the footer's top border landed
 * at row 360, its last ink at row 413, and 486 of the window's 900 pixels were
 * empty ground below it. A bordered footer floating in the middle of the window
 * reads as a page that failed to finish loading, on the page a store reviewer
 * is handed. With this rule the same page puts that border at row 811. `flex: 1 0 auto` on <main> so it grows into the slack and never
 * shrinks below its content. */
body.site-app {
  min-height: 100vh;
  display: flex;
  flex-direction: column;
}
body.site-app > main { flex: 1 0 auto; }

/* The page panel. Every moved template's content is one `<article>` -- the card
 * idiom they inherited from Pico -- and it lands inside
 * `.site-band > .site-wrap.site-narrow`, so this is the elevated surface the
 * marketing cards and tables already use, at reading width. */
.site-app main article {
  background: var(--tokklr-color-surface-elevated);
  border: 1px solid var(--tokklr-color-line);
  border-radius: var(--tokklr-radius-lg);
  padding: var(--tokklr-spacing-7);
}
/* Pico gave `article > header` a sunk band; the site's vocabulary for "this is
 * the title block" is a rule, not a second surface. */
.site-app main article > header {
  margin: 0 0 var(--tokklr-spacing-6);
  padding-bottom: var(--tokklr-spacing-5);
  border-bottom: 1px solid var(--tokklr-color-line);
}
.site-app main article > header > :last-child { margin-bottom: 0; }
.site-app main article > footer {
  margin-top: var(--tokklr-spacing-7);
  padding-top: var(--tokklr-spacing-5);
  border-top: 1px solid var(--tokklr-color-line);
  color: var(--tokklr-color-text-muted);
}
/* The legal document page wraps its prose in an `<article>` and its version /
 * language footer OUTSIDE it, so that the alias and the canonical URL compare
 * equal (see legal_document.html). Give that footer the same treatment. */
.site-app main > .site-band > .site-wrap > div > footer,
.site-app main section > footer {
  margin-top: var(--tokklr-spacing-6);
  color: var(--tokklr-color-text-muted);
}
.site-app main article > *:first-child { margin-top: 0; }
.site-app main article > *:last-child { margin-bottom: 0; }

/* A heading step inside the panel. `.site-band h1, .site-band h2` sets BOTH to
 * 1.8rem, which is right for a marketing band whose h2 is the page title and
 * wrong here, where the h1 is the page title and the h2s below it open
 * subsections. Two classes beat that rule's one. */
.site-app main article h2 {
  font-size: 1.25rem;
  margin: var(--tokklr-spacing-7) 0 var(--tokklr-spacing-3);
}
.site-app main article h3 {
  font-size: 1.05rem;
  margin: var(--tokklr-spacing-6) 0 var(--tokklr-spacing-2);
}
.site-app main article section { margin-top: var(--tokklr-spacing-6); }

/* ---- forms.
 *
 * These pages type credentials, one-time codes and promo codes into bare
 * `<input>`s wrapped in `<label>`s -- the class-less shape Pico styled. The
 * declarations mirror `.site-form` above property for property, so the contact
 * form and the sign-in form are the same control, not two that look alike. */
.site-app form { margin: 0 0 var(--tokklr-spacing-5); }
.site-app label {
  display: block;
  font-weight: 600;
  margin-top: var(--tokklr-spacing-4);
}
.site-app form > label:first-of-type,
.site-app fieldset > label:first-of-type { margin-top: 0; }
.site-app input[type="text"],
.site-app input[type="email"],
.site-app input[type="password"],
.site-app input[type="number"],
.site-app input[type="tel"],
.site-app input[type="url"],
.site-app input[type="search"],
.site-app select,
.site-app textarea {
  width: 100%;
  margin-top: var(--tokklr-spacing-2);
  padding: var(--tokklr-spacing-3);
  border: 1px solid var(--tokklr-color-line-strong);
  border-radius: var(--tokklr-radius-md);
  background: var(--tokklr-color-surface);
  color: var(--tokklr-color-text);
  font: inherit;
}
.site-app textarea { resize: vertical; min-height: 9rem; }
/* A checkbox or a radio is not a text field: `width: 100%` and 12px of padding
 * on one is a stretched grey slab with a tick lost in the middle of it. The
 * sign-up page's consent box and the checkout page's plan radios are both this
 * shape. */
.site-app input[type="checkbox"],
.site-app input[type="radio"] {
  width: auto;
  margin: 0 var(--tokklr-spacing-2) 0 0;
  accent-color: var(--tokklr-color-primary);
}
.site-app label:has(> input[type="checkbox"]),
.site-app label:has(> input[type="radio"]) { font-weight: 400; }
/* A visible, token-coloured focus ring on every control. The browser default is
 * the accessibility floor, not a design, and on a dark surface it is close to
 * invisible. `:focus-visible` so a mouse click does not draw one. */
.site-app input:focus-visible,
.site-app select:focus-visible,
.site-app textarea:focus-visible {
  outline: 2px solid var(--tokklr-color-primary);
  outline-offset: 2px;
  border-color: var(--tokklr-color-primary);
}
.site-app fieldset {
  margin: 0 0 var(--tokklr-spacing-5);
  padding: 0;
  border: 0;
}
.site-app legend {
  font-weight: 600;
  padding: 0;
  margin-bottom: var(--tokklr-spacing-2);
}
/* The field hint under an input -- `<small>` after the control, which Pico
 * blockified and muted. */
.site-app form small { display: block; color: var(--tokklr-color-text-muted); }

/* ---- buttons.
 *
 * `.site-cta` is the marketing button and these are the same control, so the
 * declarations are its. `a[role="button"]` is in the list because three moved
 * pages use that shape for a primary action, and it has to beat `.site a`. */
.site-app button,
.site-app input[type="submit"],
.site-app a[role="button"] {
  display: inline-block;
  margin-top: var(--tokklr-spacing-5);
  padding: var(--tokklr-spacing-3) var(--tokklr-spacing-6);
  border: 1px solid transparent;
  border-radius: var(--tokklr-radius-pill);
  background: var(--tokklr-color-primary);
  color: var(--tokklr-color-text-on-primary);
  font: inherit;
  font-weight: 600;
  text-decoration: none;
  cursor: pointer;
}
.site-app button:hover,
.site-app input[type="submit"]:hover,
.site-app a[role="button"]:hover {
  filter: brightness(1.08);
  color: var(--tokklr-color-text-on-primary);
}
.site-app button:focus-visible,
.site-app input[type="submit"]:focus-visible,
.site-app a[role="button"]:focus-visible {
  outline: 2px solid var(--tokklr-color-primary);
  outline-offset: 3px;
}
/* `secondary` and `outline` are Pico's class names and four moved templates
 * carry them on the actions that are NOT the page's primary one -- cancel,
 * delete, sign out. They must stay visually secondary now that Pico is gone,
 * or a delete button reads as the thing to press. Both are drawn from the text
 * tokens rather than a fill, so neither needs a text-on-* pair. */
.site-app button.secondary,
.site-app button.outline {
  background: transparent;
  border-color: var(--tokklr-color-line-strong);
  color: var(--tokklr-color-text);
}
.site-app button.secondary:hover,
.site-app button.outline:hover {
  background: var(--tokklr-color-surface-sunk);
  color: var(--tokklr-color-text);
  filter: none;
}

/* ---- notices.
 *
 * Twenty-seven `role="alert"` / `role="status"` / `role="note"` paragraphs
 * across the moved pages: soft-fail messages, confirmations and the QR panel's
 * live region. They reuse `.site-note`'s shape so a notice looks the same
 * wherever a reader meets one. */
.site-app main p[role="alert"],
.site-app main p[role="status"],
.site-app main p[role="note"] {
  padding: var(--tokklr-spacing-4);
  border-radius: var(--tokklr-radius-md);
  border-left: 4px solid var(--tokklr-color-line-strong);
  background: var(--tokklr-color-surface);
}
.site-app main p[role="alert"] { border-left-color: var(--tokklr-color-danger); }
.site-app main p[role="status"] { border-left-color: var(--tokklr-color-primary); }
/* The QR panel's status line is a live region that is EMPTY until the poll
 * says something. An empty bordered box on first paint is a rendering artefact,
 * not a notice. */
.site-app main p[role="alert"]:empty,
.site-app main p[role="status"]:empty { display: none; }
/* The one class-based error box the moved pages use, ported from admin.css so
 * it does not resolve to nothing here. */
.site-app .tokklr-form-errors {
  padding: var(--tokklr-spacing-3) var(--tokklr-spacing-4);
  border-radius: var(--tokklr-radius-sm);
  border-left: 4px solid var(--tokklr-color-danger);
  background: var(--tokklr-color-surface);
}
.site-app .tokklr-form-errors ul { margin: var(--tokklr-spacing-1) 0 0; }
.site-app .tokklr-muted { color: var(--tokklr-color-text-muted); }
/* `.tokklr-authform` is the credential form, and it is the reason this whole
 * block exists rather than a rename: the class is asserted by
 * `test_auth_form_styling.py` and its declarations lived in `style.css`, which
 * these pages no longer load. The field rules above already cover its inputs;
 * this is the spacing the class itself carried. */
.site-app .tokklr-authform { margin-bottom: var(--tokklr-spacing-6); }

/* ---- structured content: definition lists, tables, code, rules.
 *
 * The subscription panel is a `<dl>`, the legal prose is rendered Markdown that
 * may contain a table at any time, and the recovery codes, the TOTP manual key
 * and the pairing code are all `<code>`. Pico styled every one of these. */
.site-app dl { margin: 0; }
.site-app dt { font-weight: 600; color: var(--tokklr-color-text-muted); }
.site-app dd { margin: 0 0 var(--tokklr-spacing-3); }
.site-app main table { width: 100%; border-collapse: collapse; }
.site-app main th,
.site-app main td {
  padding: var(--tokklr-spacing-3);
  border-bottom: 1px solid var(--tokklr-color-line);
  text-align: start;
}
.site-app main thead th { background: var(--tokklr-color-surface); }
.site-app main code {
  padding: 0 var(--tokklr-spacing-1);
  border-radius: var(--tokklr-radius-xs);
  background: var(--tokklr-color-surface-sunk);
  color: var(--tokklr-color-text);
  font-family: ui-monospace, "SFMono-Regular", "Geist Mono", Menlo, monospace;
  font-size: 0.9em;
}
.site-app main hr {
  border: 0;
  border-top: 1px solid var(--tokklr-color-line);
  margin: var(--tokklr-spacing-6) 0;
}
.site-app main blockquote {
  margin: var(--tokklr-spacing-5) 0;
  padding-inline-start: var(--tokklr-spacing-5);
  border-inline-start: 4px solid var(--tokklr-color-line-strong);
  color: var(--tokklr-color-text-muted);
}
/* The `<details>` behind the sign-in page's user-id + PIN fields. It is not the
 * footer's language picker, so it gets the ordinary disclosure treatment rather
 * than that one's absolute panel. */
.site-app main details {
  margin: var(--tokklr-spacing-4) 0;
  padding: var(--tokklr-spacing-4);
  border: 1px solid var(--tokklr-color-line);
  border-radius: var(--tokklr-radius-md);
  background: var(--tokklr-color-surface);
}
.site-app main details > summary { cursor: pointer; font-weight: 600; }

/* ---- the QR panels (ADR 0156 / Q91, and the TOTP enrolment QR).
 *
 * `.tokklr-qr` is set by `qr_code.py` on the SVG it renders, and it is that
 * SVG's ONLY size constraint: without it a fixed-width symbol overflows a
 * phone. `[data-qr-code]` is the same code in text, always shown beside the
 * picture for a reader with no camera. Both were in `style.css`. */
.site-app .tokklr-qr {
  display: block;
  max-width: 100%;
  height: auto;
  border-radius: var(--tokklr-radius-md);
}
.site-app [data-qr-code] {
  font-family: ui-monospace, "SFMono-Regular", "Geist Mono", Menlo, monospace;
  font-size: 1.35rem;
  font-weight: 600;
  letter-spacing: 0.18em;
  margin: var(--tokklr-spacing-2) 0;
}
.site-app [data-qr-code]:empty { display: none; }

/* ---- the /web footer's two switcher forms.
 *
 * The marketing footer's language control is a disclosure of links, because
 * there the locale is in the path. On /web it is a cookie, so the control has
 * to POST -- and a `<select>` in a footer must not inherit the full-width field
 * treatment the page body's controls get. */
.site-app .site-switchers { display: flex; flex-wrap: wrap; gap: var(--tokklr-spacing-4); }
.site-app .site-switchers form { margin: 0; }
.site-app .site-switchers select {
  width: auto;
  margin: 0;
  padding: var(--tokklr-spacing-1) var(--tokklr-spacing-2);
  font-size: 0.95rem;
}
.site-app .site-switchers button { margin: 0 0 0 var(--tokklr-spacing-2); }

@media (max-width: 720px) {
  .site-app main article { padding: var(--tokklr-spacing-5); }
}
