/* ═══════════════════════════════════════════════════════════════════════════
   KORVA LANDING — Session 7, Agent J.

   WHY THIS FILE EXISTS AND WHY IT USES ITS OWN PREFIX
   ---------------------------------------------------
   The previous landing page was built on `mk-*` classes that live in
   `static/theme.css`.  theme.css is the shared palette-token layer and is owned
   by another agent this session, so a marketing page whose LAYOUT lives there
   is one refactor away from collapsing.  Everything the landing page needs to
   lay itself out is therefore defined here under the `lnd-` prefix, and this
   file depends on theme.css for COLOUR VALUES ONLY — always through
   `var(--k-token, #fallback)`, so the page is correct whether or not theme.css
   has been merged yet.

   ⚑ WHY `lnd-` AND NOT `lp-`.  This sheet was first written with an `lp-`
   prefix, which turned out to be ALREADY TAKEN: `static/style.css` carries 71
   rules in the `lp-` namespace, left behind by an older landing page, and
   style.css loads BEFORE this file.  The one that bit was

       style.css:3273   .lp-trust { display: flex; flex-direction: column; ... }

   Declaring `display:flex` here did not undo that inherited `flex-direction`,
   so every trust marker rendered with its icon stacked ABOVE the heading
   instead of beside it — a silent visual regression from a stylesheet nobody
   was editing.  `lnd-` was then checked against every file in static/ and
   templates/ and had zero occurrences.  Before inventing a prefix, grep for it.

   LOAD ORDER.  base.html links style.css -> theme.css -> {head_extra} ->
   app.css.  This sheet arrives in head_extra, so it sees the reconciled tokens.
   app.css lands after it but is scoped to app/admin classes and never matches
   `lnd-*`.  Nothing here re-points a shared token, so nothing here can leak into
   a logged-in surface.

   ⚑ CONTRAST IS MEASURED, NOT ASSUMED.  scratch_agent_j/contrast.py computes
   the WCAG 2.1 ratio for all 31 pairs this sheet ships and exits non-zero on a
   failure.  Every number in the comments below came out of that script.
   Re-run it if you touch a colour.

     #005F82  cerulean   7.10 on white, and white on it is ALSO 7.10.  Safe both
                         directions at any size.  This is the brand colour and
                         the primary CTA fill.
     #4f91ee  blue       3.17.  SURFACE ONLY.  Never text, and white text on it
                         also fails — it appears in this file exactly once, as a
                         decorative block, and carries nothing.
     #E63946  coral      4.17.  LARGE TEXT ONLY (>=24px).  Used once, on the
                         hero display word, which is clamped to a 34px minimum.
     #ee4f91  magenta    3.41.  LARGE TEXT ONLY.  Stat numerals only (40px+).
   ═══════════════════════════════════════════════════════════════════════════ */

/* ── Cerulean fallback ─────────────────────────────────────────────────────
   Agent L authors --k-cerulean in theme.css this session.  Until that merge
   lands the token does not exist, so it is declared here with the IDENTICAL
   name and the IDENTICAL value — merging is therefore a no-op, and whichever
   file loads last sets the same colour.  The -deep/-50/-100 ramp is derived
   here and is not part of L's contract.
   ⚑ fallback — L's theme.css wins on merge.                                  */
:root {
  --k-cerulean: #005F82;
}

.lnd {
  /* Locals.  Everything below reads these, so a retheme is one block. */
  --lnd-cerulean:      var(--k-cerulean, #005F82);
  --lnd-cerulean-deep: #004A66;   /*  9.67 on white — hover, deepest band     */
  --lnd-cerulean-50:   #EAF3F7;   /*  tint surface; ink 16.16, muted 4.91     */
  --lnd-cerulean-100:  #CFE4EC;   /*  chip fill / meter track / band body copy */

  --lnd-ink:    var(--k-ink,    #1A1128);   /* 18.19 on white */
  --lnd-muted:  var(--k-muted,  #6B6580);   /*  5.53 on white */
  --lnd-card:   var(--k-card,   #FFFFFF);
  --lnd-bg:     var(--k-bg,     #FCFBFA);
  --lnd-tint:   var(--k-tint,   #F8F7FC);
  --lnd-line:   var(--k-line,   #E7E3EF);
  --lnd-purple: var(--k-purple, #7e3df4);   /*  5.44 — text-safe */
  --lnd-blue:   var(--k-blue,   #4f91ee);   /*  3.17 — SURFACE ONLY */
  /* ⚑ Q5: --lnd-coral IS GONE.  It had exactly one call site, .lnd-accent on
     the hero word "drilling", and it was the last decorative red left in the
     product — a 4.17 pillar-box red sitting in a page that is otherwise warm
     neutrals and one teal.  It could not be fixed by repointing --k-coral,
     which is also the INCORRECT-ANSWER marker and has to stay red, so it is
     fixed here: the accent word is now the brand teal.  6.30 on the tier-1
     hero ground and 6.81 on tier-0, both measured, and the word still reads as
     the accent because everything around it is near-black ink.               */

  /* One family, site-wide (Session 8). These two names are kept because ~10
     landing rules read them; they resolve to the same stack now. The literal
     fallbacks were the last copy of the Fraunces/Manrope pair in the product. */
  --lnd-display: var(--font-geist);
  --lnd-sans: var(--font-geist);

  /* ⚑ Agent P3, Session 8: the marketing pages ran their own geometry — 16px
     cards and two bespoke shadows — while the app ran a different one. A parent
     who clicks "Log in" crossed a visible seam. These now point at the site
     scale: two radii (10 small / 12 card) and three depths. The names are kept
     so every call site in this file is untouched. */
  --lnd-r: 12px;
  --lnd-r-sm: 10px;
  --lnd-shadow: var(--shadow-d1);
  --lnd-shadow-lift: var(--shadow-d2);

  /* ── VERTICAL RHYTHM (Session 41) ────────────────────────────────────────
     ⚑ ONE SCALE, TWO STEPS, AND IT REPLACES FOUR HAND-TUNED CLAMPS. The dark
     landing's bands were `clamp(48px,7vw,96px)`, `clamp(56px,8vw,110px)`,
     `clamp(38px,5vw,64px)` and `clamp(60px,8vw,118px)` — four different curves
     that crossed each other between 768 and 1440, so the gap above a heading
     was larger than the gap above the next one at one width and smaller at the
     next. Measured before: 96 / 110 / 64 / 118 at 1440 and 55 / 61 / 38 / 61 at
     768. Two steps now, and every band picks one.
     ⚑ DECLARED HERE, NOT INVENTED AT THE CALL SITE. ghost_tokens.py fails the
     build on an undeclared name, and a var() behind a typo ships its fallback
     for ever. */
  --lnd-band: clamp(64px, 8vw, 120px);
  --lnd-band-tight: clamp(40px, 5vw, 72px);

  font-family: var(--lnd-sans);
  color: var(--lnd-ink);
  background: var(--lnd-bg);
}

/* ⚑ Overflow discipline.  Every horizontal-overflow bug measured on this page
   traced back to a grid child refusing to shrink below its content.  Grid and
   flex items default to min-width:auto; this one rule is why the page holds
   zero overflow at 375px with a 218-unit SVG matrix and a 4-up option row. */
.lnd *, .lnd *::before, .lnd *::after { box-sizing: border-box; min-width: 0; }
.lnd img, .lnd svg { max-width: 100%; }

/* ══ BANDS & RHYTHM ══════════════════════════════════════════════════════
   ⚑ Q5: THE BANDS WERE ON THE SAME TIER AS THE CARDS SITTING ON THEM, AND
   THAT IS WHY THE PAGE READ AS "TOO WHITE".  Q3's three-tier system is
   tier-1 #F6F1E7 = PAGE, tier-0 #FDFAF4 = CARD, tier-2 #FFFFFF = one focal
   object.  This sheet was written before those tiers existed and mapped
   --lnd-card (tier-0) onto both a BAND and every CARD, so measured:

       .lnd-band--card  #FDFAF4      .lnd-card on it  #FDFAF4
       luminance delta                                0.0000

   A card with a 1px hairline and literally zero fill separation from its
   ground is a rectangle of the same colour, and three of the nine bands were
   like that.  Everything below is one step DOWN the warm ladder, so the
   cards stay where they are and the ground moves out from under them.  New
   deltas: 0.075 on a --card band, 0.116 on a --bg band, both measured in
   scratch_agent_q5/contrast.py along with all 25 text pairs the move creates.

   ⚑ NO NEW COLOURS.  Every value is an existing Q3/Q4 token; the change is
   which token each band names.  --lnd-card and --lnd-tint are still declared
   because ~20 CARD rules read them — only the BAND mapping moved.

   ⚑ THE THREE NEUTRAL BANDS MUST STAY THREE DISTINCT VALUES.  --bg and --tint
   are ADJACENT twice in the page order (2-3 how/samples, 7-8 faq/trust); give
   them the same hex and those seams disappear and the page grows two
   double-height bands.  The running order and its nine grounds are:

     1 hero     --card  #F6F1E7    6 team    --card  #F6F1E7
     2 how      --bg    #FDFAF4    7 faq     --bg    #FDFAF4
     3 samples  --tint  #F2ECDF    8 trust   --tint  #F2ECDF
     4 parent   --card  #F6F1E7    9 cta     --deep  #005F82
     5 method   --sky   #EAF3F7

   Bands 2 and 7 are the light alternate and are the ONLY ones on tier-0, which
   is safe because neither holds a .lnd-card or .lnd-q — band 2 is the ruled
   step list and band 7 is the FAQ. Put a card in either and it needs a tier
   of its own again.                                                          */
.lnd-band { padding: 68px 22px; }
.lnd-band--card { background: var(--surface-tier-1, #F6F1E7); }   /* was tier-0 #FDFAF4 */
.lnd-band--bg   { background: var(--surface-tier-0, #FDFAF4); }   /* was tier-1 #F6F1E7 */
.lnd-band--tint { background: var(--warm-200, #F2ECDF); }         /* unchanged */
.lnd-band--sky  { background: var(--lnd-cerulean-50); }
.lnd-band--deep { background: var(--lnd-cerulean); color: #fff; }
.lnd-band--deep .lnd-h2, .lnd-band--deep .lnd-display, .lnd-band--deep h3 { color: #fff; }
.lnd-band--deep p, .lnd-band--deep .lnd-lead { color: var(--lnd-cerulean-100); } /* 5.39 */
.lnd-band--deep .lnd-eyebrow { color: var(--lnd-cerulean-100); }

.lnd-inner { max-width: 1120px; margin: 0 auto; }
.lnd-inner--narrow { max-width: 800px; }

.lnd-section-head { max-width: 62ch; margin-bottom: 34px; }

.lnd-eyebrow {
  font-size: 12px; font-weight: 700; letter-spacing: .1em; text-transform: uppercase;
  color: var(--lnd-cerulean);           /* 7.10 on white, 6.31 on sky */
}
.lnd-display {
  font-family: var(--lnd-display);
  font-size: clamp(2.15rem, 5.2vw, 3.5rem);   /* min 34.4px — coral is legal here */
  line-height: 1.06; letter-spacing: -.022em; font-weight: 600;
  color: var(--lnd-ink); margin: 0;
}
.lnd-h2 {
  font-family: var(--lnd-display);
  font-size: clamp(1.6rem, 3.1vw, 2.3rem);
  line-height: 1.18; letter-spacing: -.015em; font-weight: 600;
  color: var(--lnd-ink); margin: 0;
}
.lnd-h3 { font-size: 1.06rem; font-weight: 700; line-height: 1.35; margin: 0 0 8px; color: var(--lnd-ink); }
.lnd-lead { font-size: clamp(1rem, 1.25vw, 1.14rem); line-height: 1.68; color: var(--lnd-muted); margin: 0; }
.lnd-measure { max-width: 56ch; }
.lnd-accent { color: var(--lnd-cerulean); }   /* only ever inside .lnd-display — see --lnd-coral's obituary above */

.lnd p { margin: 0 0 14px; }
.lnd p:last-child { margin-bottom: 0; }

/* ══ BUTTONS ═════════════════════════════════════════════════════════════
   Own implementation rather than style.css's .k-btn, because .k-btn--primary
   fills from --accent, a token this page does not control.  A CTA is the one
   element where an inherited colour change could silently break contrast. */
.lnd-btn {
  display: inline-flex; align-items: center; justify-content: center; gap: 8px;
  font-family: var(--lnd-sans); font-size: 16px; font-weight: 700;
  padding: 15px 28px; border-radius: 999px; border: 1.5px solid transparent;
  text-decoration: none; cursor: pointer; line-height: 1.2;
  transition: background .16s ease, color .16s ease, box-shadow .16s ease, transform .16s ease;
}
.lnd-btn--primary { background: var(--lnd-cerulean); color: #fff; box-shadow: var(--lnd-shadow-lift); } /* 7.10 */
.lnd-btn--primary:hover { background: var(--lnd-cerulean-deep); color: #fff; transform: translateY(-1px); } /* 9.67 */
.lnd-btn--quiet { background: transparent; color: var(--lnd-cerulean); border-color: var(--lnd-cerulean-100); }
.lnd-btn--quiet:hover { background: var(--lnd-cerulean-50); color: var(--lnd-cerulean-deep); }
.lnd-btn--on-deep { background: #fff; color: var(--lnd-cerulean-deep); }   /* 9.67 */
.lnd-btn--on-deep:hover { background: var(--lnd-cerulean-50); color: var(--lnd-cerulean-deep); }
.lnd-btn--ghost-deep { background: transparent; color: #fff; border-color: rgba(255,255,255,.55); }
.lnd-btn--ghost-deep:hover { background: rgba(255,255,255,.12); color: #fff; }
.lnd-btn:focus-visible { outline: 3px solid var(--lnd-cerulean); outline-offset: 3px; }

.lnd-cta-row { display: flex; flex-wrap: wrap; gap: 12px; margin-top: 30px; }
.lnd-undercta { margin-top: 14px; font-size: 14px; color: var(--lnd-muted); }
.lnd-link { color: var(--lnd-cerulean); font-weight: 600; text-decoration: underline; text-underline-offset: 2px; }
.lnd-link:hover { color: var(--lnd-cerulean-deep); }
/* ⚑ SESSION 44c: on the dark ground a prose link is CORAL, matching the call
   to action, and turns light blue under the pointer. `--lnd-cerulean` keeps
   its job as the DECORATION accent (eyebrows, tick glyphs, chip borders, the
   focus ring); only the things a reader clicks in running text move. */
.lnd--dark .lnd-link,
.lnd--dark .pge__block a,
.lnd--dark .cm-empty a { color: var(--pub-link); }
.lnd--dark .lnd-link:hover,
.lnd--dark .pge__block a:hover,
.lnd--dark .cm-empty a:hover { color: var(--pub-link-hover); }

/* ══ HERO ════════════════════════════════════════════════════════════════ */
.lnd-hero { display: grid; grid-template-columns: minmax(0, 1.08fr) minmax(0, .92fr); gap: 48px; align-items: center; }
/* ⚑ SPECIFICITY TRAP, measured.  The overflow-discipline rule above,
   `.lnd img, .lnd svg { max-width: 100% }`, scores (0,1,1) — it BEATS a bare
   `.lnd-hero__art` at (0,1,0).  Written that way the cap never applied at any
   width: the illustration was silently sized by its grid column instead (709px
   at a 768px viewport, 493px at 1440px, against an intended 420/300).  It
   looked deliberate, which is why it survived a visual pass.  The compound
   selector below scores (0,2,1) and actually wins.  Any per-image cap on this
   page needs the same treatment. */
.lnd img.lnd-hero__art { width: 100%; max-width: 420px; height: auto; margin: 0 auto; display: block; }

/* Trust strip at the fold.  Statements about the software, not social proof.
   Full-bleed inside lnd-inner rather than inside the hero's copy column — see
   the note in landing.html for why. */
.lnd-fold {
  display: grid; grid-template-columns: repeat(4, minmax(0, 1fr)); gap: 14px 24px;
  margin-top: 34px; padding-top: 22px; border-top: 1px solid var(--lnd-line);
}
.lnd-fold__item { display: flex; align-items: flex-start; gap: 9px; font-size: 14px; line-height: 1.45; color: var(--lnd-muted); font-weight: 600; }
.lnd-fold__item svg { width: 17px; height: 17px; flex: 0 0 17px; margin-top: 2px; fill: none; stroke: var(--lnd-cerulean); stroke-width: 2.1; stroke-linecap: round; stroke-linejoin: round; }

.lnd-facts { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: 16px; margin-top: 18px; }
.lnd-fact {
  background: var(--lnd-cerulean-50); border: 1px solid var(--lnd-cerulean-100);
  border-radius: var(--lnd-r); padding: 20px 20px 18px;
}
.lnd-fact__n { display: block; font-family: var(--lnd-display); font-size: 2.5rem; line-height: 1; font-weight: 600; color: var(--lnd-cerulean); } /* 6.31 on sky */
.lnd-fact__l { display: block; margin-top: 8px; font-size: 13px; line-height: 1.45; color: var(--lnd-muted); } /* 4.91 on sky */

/* ══ GRIDS & CARDS ═══════════════════════════════════════════════════════ */
.lnd-grid { display: grid; gap: 22px; }
.lnd-grid--2 { grid-template-columns: repeat(2, minmax(0, 1fr)); }
.lnd-grid--3 { grid-template-columns: repeat(3, minmax(0, 1fr)); }

.lnd-card {
  background: var(--lnd-card); border: 1px solid var(--lnd-line);
  border-radius: var(--lnd-r); padding: 26px; box-shadow: var(--lnd-shadow);
}
.lnd-card p { font-size: 15px; line-height: 1.66; color: var(--lnd-muted); }
.lnd-card__art { width: 92px; height: 92px; display: block; margin-bottom: 16px; }

/* ══ HOW IT WORKS — editorial steps, not marketing bullets ═══════════════
   At >=1000px the section head sits in its own sticky column beside the steps.
   Left-aligned full-bleed steps with a 62ch measure left ~40% of a 1440px band
   empty, which reads as an unfinished layout rather than as deliberate
   whitespace; giving the head a column makes the same measure look intended. */
.lnd-steps--split { display: grid; grid-template-columns: minmax(0, .8fr) minmax(0, 1.2fr); gap: 56px; align-items: start; }
.lnd-steps--split .lnd-steps__head { position: sticky; top: 96px; margin-bottom: 0; }

.lnd-steps { display: grid; gap: 0; }
.lnd-step {
  display: grid; grid-template-columns: 62px minmax(0, 1fr); gap: 26px;
  padding: 30px 0; border-top: 1px solid var(--lnd-line);
}
.lnd-step:last-child { border-bottom: 1px solid var(--lnd-line); }
.lnd-step__n {
  width: 54px; height: 54px; border-radius: 50%;
  background: var(--lnd-cerulean-50); border: 1px solid var(--lnd-cerulean-100);
  display: flex; align-items: center; justify-content: center;
  font-family: var(--lnd-display); font-size: 1.4rem; font-weight: 600;
  color: var(--lnd-cerulean);                       /* 6.31 on sky */
}
.lnd-step__body h3 { font-family: var(--lnd-display); font-size: 1.32rem; font-weight: 600; margin: 8px 0 10px; letter-spacing: -.01em; }
.lnd-step__body p { font-size: 15px; line-height: 1.7; color: var(--lnd-muted); max-width: 62ch; }
.lnd-step__body .lnd-cite { margin-top: 10px; }

/* ══ SAMPLE CONTENT ══════════════════════════════════════════════════════ */
/* ⚑ Q6: THE VOID WAS A CONTENT-BALANCE PROBLEM AND THREE PASSES TRIED TO SOLVE
   IT AS AN ALIGNMENT ONE.  Worth writing down, because the fourth attempt only
   works by rejecting the premise the first three shared.

     earlier   `align-items: start`    all the slack at the BOTTOM of the short
                                       column — one tall void of bare band.
     Q5        `align-items: center`    the slack SPLIT above and below — two
                                       voids of half the height each.
     Q6 first  `align-items: stretch`   + the footer pushed to the card's foot.
               This looked like the answer and is NOT: it moves 444px of bare
               band INSIDE a bordered card, and a card with a 444px hole in its
               middle advertises the emptiness rather than hiding it.  Rendered,
               it was visibly worse than what it replaced.

   `align-items` cannot fix this, because the surplus height is real: measured by
   scratch_agent_q6/measure_sample.py, `center` left, as COLUMN SHORTFALL in px,
   reading / abstract / quant-writing:

       1920px   340 / 444 / 260        1440px   340 / 444 / 260
       1024px   360 / 429 / 265         768px   673 / 348 / 236

   So the height itself moves.  measure_blocks.py broke each column into its
   sub-blocks and the abstract row — the worst of the three, and the one a reader
   spots — turned out to be mis-assigned rather than unbalanced:

       LEFT   head 58 + stem 26 + figure 541 + WHY 236 + padding 44  = 939
       RIGHT  art 92 + h3 23 + para 100 + para 100 + cite 75 + pad   = 495

   The 236px `.lnd-q__why` block is the written explanation of the puzzle.  It is
   COMMENTARY, exactly like the prose card it sits opposite — the ARTEFACT is the
   figure and its four options.  Moving it into the right column (see
   `.lnd-sample__aside` and the note in landing.html) both shortens the artefact
   column and lengthens the commentary column, which is why one move closes most
   of the gap instead of half of it.  It also reads better: the reader now meets
   the figure, then the options, then everything anyone wrote about it, in the
   column where writing lives.

   ⚑ NO STICKY, AND THE REASON IS ARITHMETIC, NOT TASTE.  Sticky is this sheet's
   own pattern at `.lnd-steps--split .lnd-steps__head`, so it was the obvious
   reach.  It does not work here: sticky only hides a shortfall when the tall
   column is much taller than the VIEWPORT, so that no scroll position shows the
   whole gap at once.  The abstract row is 939px against a 900px viewport, so at
   the scroll position showing the row's foot roughly 350px of bare band is on
   screen beside a pinned card regardless.  Sticky would have cost a
   viewport-height media gate and bought nothing.                            */
.lnd-sample { display: grid; grid-template-columns: minmax(0, 1fr) minmax(0, 1fr); gap: 24px; align-items: start; }

/* ⚑ THE OTHER TWO ROWS HAVE NOTHING TO MOVE, so they are balanced by MEASURE
   instead.  Reading pairs a 370-word passage with a four-option question, and
   quant pairs a worked question with a writing prompt; in both the left block is
   long-form prose and the right is a short list, and neither has a paragraph
   that belongs on the other side.  Widening the prose column shortens it (fewer
   line breaks over a longer measure) while narrowing the list column lengthens
   it (options wrap), so ONE ratio moves both ends toward each other.  Measured
   at 1440, as COLUMN SHORTFALL:

       row            1fr 1fr   1.18fr .82fr
       reading          340px        169px      (also +33px from the new
                                                `.lnd-passage + .lnd-cite` rule
                                                below, which is a real addition
                                                to the passage card's height)
       quant/writing    260px        168px
       abstract         444px         68px      (by the reflow, not this ratio)

   The abstract row keeps 1fr 1fr — it is already balanced by the reflow above,
   and this ratio would over-correct it in the opposite direction (measured: the
   commentary column becomes ~158px the TALLER of the two).  Hence a modifier and
   not a change to the base rule.

   The passage measure goes from ~62 to ~72 characters, which is at the top of
   the comfortable range but inside it; do not push this ratio further to buy
   more balance, because losing readability of the sample passage to tidy a gap
   would be a bad trade on the page whose whole argument is "look at the real
   content".                                                                   */
.lnd-sample--wide { grid-template-columns: minmax(0, 1.18fr) minmax(0, .82fr); }

/* The commentary column when it holds MORE THAN ONE block — the prose card plus
   the moved explanation.  `align-content: start` so the two stay together at the
   top and any residual stays at the foot of the column, undistributed. */
.lnd-sample__aside { display: grid; gap: 22px; align-content: start; }
/* Standalone, the explanation supplies its own top margin from inside `.lnd-q`;
   in the aside the grid gap does that job, so cancel it and avoid a double gap. */
.lnd-sample__aside > .lnd-q__why { margin-top: 0; }

.lnd-tag {
  display: inline-block; font-size: 12px; font-weight: 700; letter-spacing: .05em;
  text-transform: uppercase; padding: 4px 10px; border-radius: 999px;
  background: var(--lnd-cerulean-50); color: var(--lnd-cerulean); border: 1px solid var(--lnd-cerulean-100);
}
/* ⚑ Q5: WAS A LILAC CHIP WITH TEAL INK, which is not a colour scheme.
   `background: #F3ECFE; border-color: #E4D5FC` were hard-coded violet tints,
   but its ink read `var(--lnd-purple)` -> `--k-purple`, and Q4 repointed
   --k-purple at --teal-600 — so the one chip on this page rendered #00506E on
   a violet wash: the last cold tint in the landing sheet, and the label it
   carries is "Example view", the disclosure that the numbers beside it are
   illustrative. That label needs to be MORE prominent than an ordinary
   .lnd-tag, not differently coloured from it, so it is now a FILLED teal chip.
   White on --teal-500 is 7.10 and no new value is introduced.
   ⚑ .lnd-caveat still carries the violet pair (#F3ECFE / #5B2BC0) and is
   LEFT ALONE on purpose: it is /pricing's honesty callout and the wrapper
   _non_affiliation.html defaults to, which Phase 1 owns. */
.lnd-tag--purple {
  background: var(--lnd-cerulean); color: #fff; border-color: var(--lnd-cerulean);
}
.lnd-tagrow { display: flex; gap: 7px; flex-wrap: wrap; margin-bottom: 16px; }

.lnd-passage { font-size: 15px; line-height: 1.78; color: var(--lnd-ink); }
.lnd-passage p { margin: 0 0 13px; }
.lnd-cite { font-size: 13px; line-height: 1.55; color: var(--lnd-muted); margin: 12px 0 0; }
/* ⚑ Q6: a caption directly under 370 words of prose needs a RULE, not 12px.
   `.lnd-passage p` carries a 13px bottom margin which collapses against the
   cite's 12px top, so the excerpt note ("Paragraphs 1, 3 and 5 of five…") sat
   13px under the last line of the story in the same column at nearly the same
   size — it read as a FOURTH PARAGRAPH of the passage rather than as a note
   about it, which on a page whose entire argument is "this text is verbatim
   from the bank" is the one confusion to avoid.  A hairline and real space make
   the register change visible. */
.lnd-passage + .lnd-cite {
  margin-top: 18px; padding-top: 14px; border-top: 1px solid var(--lnd-line);
}

.lnd-q { background: var(--lnd-card); border: 1px solid var(--lnd-line); border-radius: var(--lnd-r); overflow: hidden; box-shadow: var(--lnd-shadow); }
.lnd-q__head {
  display: flex; align-items: center; justify-content: space-between; gap: 10px; flex-wrap: wrap;
  padding: 14px 20px; background: var(--lnd-cerulean-50); border-bottom: 1px solid var(--lnd-cerulean-100);
  font-size: 13px; font-weight: 600; color: var(--lnd-muted);
}
.lnd-q__body { padding: 22px 20px; }
.lnd-q__stem { font-size: 16px; line-height: 1.6; font-weight: 600; color: var(--lnd-ink); margin: 0 0 16px; }
.lnd-stimulus {
  font-size: 15px; line-height: 1.6; padding: 14px 16px; margin: 0 0 16px;
  background: var(--lnd-tint); border-left: 3px solid var(--lnd-cerulean); border-radius: 0 var(--lnd-r-sm) var(--lnd-r-sm) 0;
}
.lnd-q__opts { display: grid; gap: 9px; }
.lnd-q__opt {
  display: flex; align-items: flex-start; gap: 12px; width: 100%; text-align: left;
  padding: 13px 15px; border: 1.5px solid var(--lnd-line); border-radius: var(--lnd-r-sm);
  background: var(--lnd-card); font: inherit; font-size: 15px; line-height: 1.5; color: var(--lnd-ink);
  cursor: default; transition: border-color .15s ease, background .15s ease;
}
button.lnd-q__opt { cursor: pointer; }
button.lnd-q__opt:hover { border-color: var(--lnd-cerulean); background: var(--lnd-cerulean-50); }
button.lnd-q__opt:focus-visible { outline: 3px solid var(--lnd-cerulean); outline-offset: 2px; }
.lnd-q__key {
  flex: 0 0 25px; width: 25px; height: 25px; border-radius: 50%;
  background: var(--lnd-tint); border: 1px solid var(--lnd-line);
  display: inline-flex; align-items: center; justify-content: center;
  font-size: 12px; font-weight: 700; color: var(--lnd-muted);
}
/* The key is revealed only once the block is in its answered state, so the page
   is not an answer sheet on first paint. */
.lnd-q.is-answered .lnd-q__opt.is-correct,
.lnd-q--static .lnd-q__opt.is-correct { border-color: var(--lnd-cerulean); background: var(--lnd-cerulean-50); }
.lnd-q.is-answered .lnd-q__opt.is-correct .lnd-q__key,
.lnd-q--static .lnd-q__opt.is-correct .lnd-q__key { background: var(--lnd-cerulean); border-color: var(--lnd-cerulean); color: #fff; } /* 7.10 */
.lnd-q__opt.is-picked { border-color: var(--lnd-cerulean); }

.lnd-q__why {
  margin-top: 16px; padding: 16px 18px; border-radius: var(--lnd-r-sm);
  background: var(--lnd-cerulean-50); border: 1px solid var(--lnd-cerulean-100);
  font-size: 15px; line-height: 1.68; color: var(--lnd-ink);
}
.lnd-q__why strong { color: var(--lnd-cerulean-deep); }   /* 8.59 on sky */
.lnd-q__why p { margin: 0 0 8px; }
.lnd-q__why p:last-child { margin-bottom: 0; }

.lnd-fo {
  display: flex; gap: 12px; align-items: flex-start; margin-top: 16px;
  padding: 15px 17px; border-radius: var(--lnd-r-sm);
  background: var(--lnd-tint); border: 1px solid var(--lnd-line);
}
.lnd-fo__pic { flex: 0 0 40px; width: 40px; height: 40px; }
.lnd-fo__txt { font-size: 15px; line-height: 1.62; color: var(--lnd-ink); }
.lnd-fo__who { display: block; font-size: 12px; font-weight: 700; letter-spacing: .06em; text-transform: uppercase; color: var(--lnd-cerulean); margin-bottom: 5px; }

/* ── the real abstract figure, rendered by the app's own engine ───────── */
.lnd-abs { margin: 0; }
.lnd-abs__fig {
  background: #fff; border: 1px solid var(--lnd-line); border-radius: var(--lnd-r-sm);
  padding: 14px; max-width: 380px; margin: 0 auto 18px;
}
.lnd-abs__opts { display: grid; grid-template-columns: repeat(4, minmax(0, 1fr)); gap: 10px; }
.lnd-abs__opt { border: 1.5px solid var(--lnd-line); border-radius: var(--lnd-r-sm); padding: 9px 7px 7px; background: #fff; text-align: center; }
.lnd-abs__opt.is-key { border-color: var(--lnd-cerulean); background: var(--lnd-cerulean-50); }
.lnd-abs__key { display: block; font-size: 12px; font-weight: 700; color: var(--lnd-muted); margin-bottom: 6px; }
.lnd-abs__opt.is-key .lnd-abs__key { color: var(--lnd-cerulean-deep); }   /* 8.59 on sky */
.lnd-abs__opt svg { margin: 0 auto; }

/* ══ PARENT VIEW ═════════════════════════════════════════════════════════ */
.lnd-split { display: grid; grid-template-columns: minmax(0, 1fr) minmax(0, 1fr); gap: 56px; align-items: center; }

.lnd-dash { background: var(--lnd-card); border: 1px solid var(--lnd-line); border-radius: 12px; padding: 26px; box-shadow: var(--lnd-shadow-lift); }
.lnd-dash__row { display: flex; align-items: flex-start; justify-content: space-between; gap: 14px; flex-wrap: wrap; }
.lnd-dash__label { font-size: 12px; font-weight: 700; letter-spacing: .08em; text-transform: uppercase; color: var(--lnd-muted); }
.lnd-dash__value { font-family: var(--lnd-display); font-size: 3rem; line-height: 1; font-weight: 600; color: var(--lnd-cerulean); margin-top: 8px; }
.lnd-dash__of { font-size: .42em; color: var(--lnd-muted); }
.lnd-rule { border: 0; border-top: 1px solid var(--lnd-line); margin: 22px 0; }

.lnd-skill { margin-bottom: 16px; }
.lnd-skill:last-of-type { margin-bottom: 0; }
.lnd-skill__top { display: flex; justify-content: space-between; gap: 10px; font-size: 14px; font-weight: 600; margin-bottom: 7px; }
.lnd-skill__top span:last-child { color: var(--lnd-muted); }
.lnd-meter { height: 9px; border-radius: 999px; background: var(--lnd-cerulean-100); overflow: hidden; }
.lnd-meter__fill { height: 100%; border-radius: 999px; background: var(--lnd-cerulean); }  /* 5.39 vs track */
.lnd-meter__fill--mid { background: #2f7fa0; }
.lnd-meter__fill--low { background: #7aa9bd; }

.lnd-ticks { list-style: none; padding: 0; margin: 0; }
.lnd-ticks li { position: relative; padding-left: 30px; margin-bottom: 13px; font-size: 15px; line-height: 1.6; color: var(--lnd-muted); }
.lnd-ticks li::before {
  content: ''; position: absolute; left: 0; top: 6px; width: 17px; height: 17px;
  border-radius: 50%; background: var(--lnd-cerulean-50); border: 1px solid var(--lnd-cerulean-100);
}
.lnd-ticks li::after {
  content: ''; position: absolute; left: 5px; top: 11px; width: 7px; height: 4px;
  border-left: 2px solid var(--lnd-cerulean); border-bottom: 2px solid var(--lnd-cerulean);
  transform: rotate(-45deg);
}

/* ══ METHODOLOGY ═════════════════════════════════════════════════════════ */
.lnd-method { display: grid; gap: 18px; grid-template-columns: repeat(3, minmax(0, 1fr)); }
.lnd-method__item { background: var(--lnd-card); border: 1px solid var(--lnd-line); border-left: 3px solid var(--lnd-cerulean); border-radius: var(--lnd-r-sm); padding: 22px; }
.lnd-method__item h3 { font-size: 1rem; }
.lnd-method__item p { font-size: 15px; line-height: 1.66; color: var(--lnd-muted); margin: 0; }

/* ══ TEAM ════════════════════════════════════════════════════════════════ */

/* ══ FAQ ═════════════════════════════════════════════════════════════════ */
.lnd-faq { max-width: 780px; }
.lnd-faq details { border-bottom: 1px solid var(--lnd-line); }
.lnd-faq summary {
  list-style: none; cursor: pointer; padding: 20px 40px 20px 0; position: relative;
  font-size: 16px; font-weight: 700; line-height: 1.45; color: var(--lnd-ink);
}
.lnd-faq summary::-webkit-details-marker { display: none; }
.lnd-faq summary:focus-visible { outline: 3px solid var(--lnd-cerulean); outline-offset: 2px; border-radius: 10px; }
.lnd-faq summary::after {
  content: ''; position: absolute; right: 8px; top: 27px; width: 9px; height: 9px;
  border-right: 2.2px solid var(--lnd-cerulean); border-bottom: 2.2px solid var(--lnd-cerulean);
  transform: rotate(45deg); transition: transform .18s ease;
}
.lnd-faq details[open] summary::after { transform: rotate(-135deg); top: 31px; }
/* ⚑ THE ANSWER OPENED FLUSH AGAINST THE QUESTION - measured 0px between the
   <summary> and the <p> on all six /pricing renders. A disclosure whose answer
   touches its own question reads as one run-on block at the moment it opens. */
.lnd-faq details p { font-size: 15px; line-height: 1.72; color: var(--lnd-muted); margin: 8px 0 18px; max-width: 66ch; }

/* ══ TRUST ═══════════════════════════════════════════════════════════════
   ⚑ Q5: these six were bare flex rows floating directly on the band, which
   gave the ONE section that is entirely promises less visual weight than the
   six methodology cards above it.  They are now on tier-0 card faces with the
   band at warm-200 underneath — same two radii, same --shadow-d1, no new
   depth — so "what we can actually promise" carries at least as much as
   "how a question is built".  Nothing here hovers: a trust marker is not a
   link and a lift would promise a click that does not exist.                 */
.lnd-trust {
  display: flex; gap: 15px; align-items: flex-start;
  background: var(--lnd-card); border: 1px solid var(--lnd-line);
  border-radius: var(--lnd-r); padding: 20px 20px 19px; box-shadow: var(--lnd-shadow);
}
.lnd-trust__icon {
  flex: 0 0 40px; width: 40px; height: 40px; border-radius: 10px;
  background: var(--lnd-cerulean-50); border: 1px solid var(--lnd-cerulean-100);
  display: inline-flex; align-items: center; justify-content: center;
}
.lnd-trust__icon svg { width: 20px; height: 20px; fill: none; stroke: var(--lnd-cerulean); stroke-width: 1.9; stroke-linecap: round; stroke-linejoin: round; }
.lnd-trust p { font-size: 15px; line-height: 1.62; color: var(--lnd-muted); margin: 0; }

/* ══ ONE RUN, THREE QUESTIONS ════════════════════════════════════════════
   ⚑ EVERY STEM, SKILL AND TIER IN THIS STRIP IS ONE OF THE ITEMS ALREADY
   SHOWN VERBATIM FURTHER UP THIS BAND, re-presented as a sequence.  It shows
   the SHAPE of a session and invents no fourth question to do it; the skill
   names are app.py's SKILL_REGISTRY output, i.e. what the child's own section
   page prints.  See the provenance comment in landing.html.                  */
.lnd-run { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: 0; }
.lnd-run__step {
  position: relative;
  background: var(--lnd-card); border: 1px solid var(--lnd-line);
  padding: 18px 20px 20px;
}
.lnd-run__step:first-child { border-radius: var(--lnd-r) 0 0 var(--lnd-r); }
.lnd-run__step:last-child  { border-radius: 0 var(--lnd-r) var(--lnd-r) 0; }
.lnd-run__step + .lnd-run__step { border-left: 0; }
.lnd-run__n {
  display: inline-flex; align-items: center; justify-content: center;
  width: 26px; height: 26px; border-radius: 50%;
  background: var(--lnd-cerulean-50); border: 1px solid var(--lnd-cerulean-100);
  font-size: 13px; font-weight: 700; color: var(--lnd-cerulean);   /* 6.36 on sky */
  margin-bottom: 11px;
}
/* ⚑ Two LINES, not a wrapping row. As a flex row the short skill name ("Inference")
   left step 1 on one line while steps 2 and 3 wrapped onto two, so the three stems
   started at three different heights and the strip read as ragged rather than as a
   sequence. Grid with one column pins the baseline whatever the name's length. */
.lnd-run__meta {
  display: grid; gap: 3px; margin-bottom: 9px;
  font-size: 12px; font-weight: 700; letter-spacing: .05em; text-transform: uppercase;
  color: var(--lnd-cerulean);
}
.lnd-run__meta span:last-child { color: var(--lnd-muted); }        /* 5.44 on tier-0 */
.lnd-run__stem { font-size: 14.5px; line-height: 1.5; color: var(--lnd-ink); margin: 0 0 10px; }
.lnd-run__state {
  display: inline-block; font-size: 12.5px; font-weight: 600; line-height: 1.4;
  color: var(--lnd-muted); margin: 0;
}

/* ══ FO'S HINT LADDER ════════════════════════════════════════════════════
   ⚑ THE THREE RUNGS ARE RENDERED FROM app.py's FO_HINT_FALLBACK, NOT TYPED
   HERE OR IN THE TEMPLATE.  See the comment in landing.html: a hand-copied
   "sample hint" is exactly the fabrication this page has already had to have
   removed once.                                                              */
.lnd-ladder { display: grid; gap: 12px; }
.lnd-rung {
  display: grid; grid-template-columns: 30px minmax(0, 1fr); gap: 14px; align-items: start;
  background: var(--lnd-card); border: 1px solid var(--lnd-line);
  border-radius: var(--lnd-r-sm); padding: 15px 17px;
}
.lnd-rung__n {
  width: 30px; height: 30px; border-radius: 50%;
  background: var(--lnd-cerulean); color: #fff;                    /* 7.10 */
  display: inline-flex; align-items: center; justify-content: center;
  font-size: 13px; font-weight: 700;
}
.lnd-rung__label {
  display: block; font-size: 11.5px; font-weight: 700; letter-spacing: .07em;
  text-transform: uppercase; color: var(--lnd-cerulean); margin-bottom: 5px;   /* 6.81 */
}
.lnd-rung__say { font-size: 15px; line-height: 1.6; color: var(--lnd-ink); margin: 0; }

/* ══ SCROLL REVEAL ═══════════════════════════════════════════════════════
   ⚑ THE DEFAULT IS VISIBLE, AND THAT IS THE WHOLE SAFETY MODEL.  `.lnd-rise`
   on its own does nothing; landing.html's IntersectionObserver adds
   `.lnd-rise--armed` to the <html> element FIRST and only then observes, so
   with JS off, with the observer unsupported, or if the script throws, every
   block simply renders — a fade-in implemented the other way round (hidden by
   default, revealed by script) hides the entire page when anything breaks.

   The reduced-motion query below re-shows everything even in the armed state,
   so the setting wins over the script without the script having to check it.  */
.lnd-rise { will-change: opacity, transform; }
html.lnd-rise--armed .lnd-rise {
  opacity: 0; transform: translateY(14px);
  transition: opacity .5s cubic-bezier(.2,.6,.3,1), transform .5s cubic-bezier(.2,.6,.3,1);
}
html.lnd-rise--armed .lnd-rise.is-in { opacity: 1; transform: none; }

.lnd-note {
  border: 1px solid var(--lnd-line); border-radius: var(--lnd-r);
  background: var(--lnd-card); padding: 20px 22px;
  font-size: 14px; line-height: 1.68; color: var(--lnd-muted);
}
.lnd-note strong { color: var(--lnd-ink); }

/* ══ MARKETING SUBPAGES — /pricing and /how-it-works ═════════════════════
   Added by Agent M, Session 7.  These two pages were on the `ks-*` system in
   static/surfaces.css while the landing page was here, and the split was
   visible without scrolling: measured at 1440px the content rail was at 240px
   on /pricing against 160px on /, the primary CTA was #2f5fa8 against #005F82,
   12px radius against a pill, and Lexend 600 against Manrope 700.

   Rather than patch one system to imitate the other, the two subpages were
   moved ONTO this one.  The components they needed and this file did not yet
   have are below; everything else they use was already here.

   ⚑ surfaces.css is NOT unlinked from the app — dashboard.html and
   practice_home.html link it themselves and still own the whole `ks-*`
   namespace.  Only the two marketing templates stopped importing it, so this
   change cannot reach a logged-in surface.                                    */

/* Honesty callout — where the copy states a LIMIT rather than a benefit.
   Purple, not red: a limit stated plainly is not an error state.  Carried over
   from .ks-caveat so /pricing's "Two things we do not do" keeps its voice. */
.lnd-caveat {
  background: #F3ECFE;
  border-left: 3px solid var(--lnd-purple);
  border-radius: 0 var(--lnd-r-sm) var(--lnd-r-sm) 0;
  padding: 16px 20px;
  margin: 24px 0;
  /* ⚑ Agent P3, Session 8: the ONE ragged right edge on the marketing pages.
     Measured at 1440 on /pricing, the "Before you subscribe" band set its
     heading and its FAQ list to a 780px measure and then ran this note to the
     full 1120px inner — the three blocks sat at 1280 / 940 / 1280, so a band
     that is otherwise one column read as two. 780 is the FAQ's own measure, so
     the whole band now shares one right edge. Every other .lnd-caveat on the
     site is already inside a narrower column and is unaffected. */
  max-width: 780px;
  font-size: 15px; line-height: 1.66; color: var(--lnd-ink);
}
.lnd-caveat strong { color: #5B2BC0; }        /* 7.53 on #F3ECFE */

/* Price card.  The one element on /pricing that should carry the most depth,
   because it carries the decision. */
.lnd-price-card {
  background: var(--lnd-card);
  border: 1px solid var(--lnd-cerulean-100);
  border-top: 3px solid var(--lnd-cerulean);
  border-radius: 12px;
  padding: 28px 26px;
  box-shadow: var(--lnd-shadow-lift);
}
/* Non-affiliation line inside the price card, above the CTA. Ruled off so it
   reads as a disclosure rather than as one more selling point. Colour is
   .lnd-cite's --lnd-muted (5.53:1) — do not fade it further. */
.lnd-price-card__legal {
  border-top: 1px solid var(--lnd-line);
  padding-top: 14px;
}
.lnd-price { display: flex; align-items: baseline; gap: 7px; margin-top: 16px; }
.lnd-price__amt {
  font-family: var(--lnd-display); font-weight: 600; font-size: 3.25rem;
  line-height: 1; letter-spacing: -.03em; color: var(--lnd-cerulean);   /* 7.10 */
}
.lnd-price__per { font-size: 15px; color: var(--lnd-muted); }           /* 5.53 */

.lnd-btn--block { width: 100%; }

/* /pricing's copy column is wider than its price column; /how-it-works uses the
   plain .lnd-split.  align-items:start so the card does not float against a
   much taller list. */
.lnd-split--offset { grid-template-columns: minmax(0, 1.15fr) minmax(0, .85fr); align-items: start; gap: 44px; }

/* A stack of labelled cards — "SECTION 01", "STEP 1". The label is the eyebrow
   already defined above, so this is only the container. */
.lnd-labelled { display: grid; gap: 18px; grid-template-columns: repeat(2, minmax(0, 1fr)); }
.lnd-labelled .lnd-card__label { display: block; margin-bottom: 10px; }

/* ══ PREMIUM DENSITY ═════════════════════════════════════════════════════
   ⚑ Hover belongs to things that DO something.  A hover lift on a card that is
   not a link is a promise the card cannot keep, so the static content cards on
   these pages deliberately do not lift — depth on them comes from the shadow
   ramp, which is monochrome and tinted from the ink so it never reads as a
   fifth colour.  Only genuinely interactive elements respond.                 */
.lnd-card, .lnd-price-card, .lnd-method__item, .lnd-q, .lnd-note {
  transition: box-shadow .18s cubic-bezier(.2,.6,.3,1),
              border-color .18s cubic-bezier(.2,.6,.3,1);
}
a.lnd-card:hover, a.lnd-card:focus-visible {
  border-color: var(--lnd-cerulean-100);
  box-shadow: var(--lnd-shadow-lift);
  transform: translateY(-2px);
}
a.lnd-card { transition: box-shadow .18s cubic-bezier(.2,.6,.3,1),
                         border-color .18s cubic-bezier(.2,.6,.3,1),
                         transform .18s cubic-bezier(.2,.6,.3,1); }
.lnd-faq summary { transition: color .16s ease; }
.lnd-faq summary:hover { color: var(--lnd-cerulean-deep); }   /* 9.67 */
.lnd-link { transition: color .16s ease; }

/* ══ RESPONSIVE ══════════════════════════════════════════════════════════ */
@media (max-width: 1000px) {
  .lnd-split--offset { grid-template-columns: minmax(0, 1fr); gap: 34px; }
  .lnd-labelled { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  .lnd-hero, .lnd-split { grid-template-columns: minmax(0, 1fr); gap: 40px; }
  .lnd img.lnd-hero__art { max-width: 320px; }
  /* ⚑ Q6: THE SAMPLES STACK AT 1000, NOT AT 760, and the 760 rule below is now
     unreachable for them (kept there because it names five other grids).  Two
     independent reasons, both measured:

     1. The abstract figure is capped at 380px by `.lnd-abs__fig`.  In a 2-column
        grid at a 768px viewport each column is 350px, so the ONE element on this
        page that must not shrink was being shrunk on every tablet.
     2. The reflow above balances the abstract row at a wide measure and
        OVER-balances it at a narrow one — at 768 the commentary column ran 432px
        TALLER than the artefact, i.e. the same void with the sign flipped.  That
        is inherent: moving a block between columns is a fixed transfer, and the
        columns' heights are not.

     One column removes both, and a stacked passage-then-question reads better on
     a tablet than two 350px columns of it ever did.                            */
  .lnd-sample, .lnd-sample--wide { grid-template-columns: minmax(0, 1fr); gap: 22px; }
  .lnd-method { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  .lnd-fold { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  .lnd-steps--split { grid-template-columns: minmax(0, 1fr); gap: 0; }
  .lnd-steps--split .lnd-steps__head { position: static; }
  /* The dashboard preview reads better ABOVE the copy on narrow screens; the
     copy explains a picture the reader has already seen. */
  .lnd-split .lnd-dash { order: -1; }
}
@media (max-width: 1000px) {
  /* The run strip stacks; the rounded corners have to follow it or the middle
     step keeps a square top and the last keeps a square left. */
  .lnd-run { grid-template-columns: minmax(0, 1fr); }
  .lnd-run__step:first-child { border-radius: var(--lnd-r) var(--lnd-r) 0 0; }
  .lnd-run__step:last-child  { border-radius: 0 0 var(--lnd-r) var(--lnd-r); }
  .lnd-run__step + .lnd-run__step { border-left: 1px solid var(--lnd-line); border-top: 0; }
}
@media (max-width: 760px) {
  .lnd-band { padding: 52px 18px; }
  .lnd-section-head { margin-bottom: 26px; }
  .lnd-fold, .lnd-facts { grid-template-columns: minmax(0, 1fr); }
  .lnd-sample, .lnd-grid--2, .lnd-grid--3 { grid-template-columns: minmax(0, 1fr); }
  .lnd-method { grid-template-columns: minmax(0, 1fr); }
  .lnd-cta-row { margin-top: 24px; }
  .lnd-btn { width: 100%; }
  .lnd-step { grid-template-columns: 46px minmax(0, 1fr); gap: 16px; padding: 24px 0; }
  .lnd-step__n { width: 44px; height: 44px; font-size: 1.15rem; }
  .lnd-labelled { grid-template-columns: minmax(0, 1fr); }
  .lnd-price__amt { font-size: 2.75rem; }
}
@media (max-width: 460px) {
  .lnd-grid--4 { grid-template-columns: minmax(0, 1fr); }
  .lnd-fact { flex: 1 1 100%; }
  .lnd-abs__opts { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}

@media (prefers-reduced-motion: reduce) {
  .lnd *, .lnd *::before, .lnd *::after { transition: none !important; animation: none !important; }
  /* Killing the transition is not enough on its own — a translate with no
     transition still moves, it just jumps. The lift itself has to go. Every
     hover transform this sheet declares is listed here; polish.css section 3
     carries the same guarantee for the rest of the site. */
  .lnd-btn--primary:hover,
  a.lnd-card:hover, a.lnd-card:focus-visible { transform: none; }
  /* ⚑ Q5 scroll reveal. The `* { transition: none }` above stops the fade
     ANIMATING but leaves the armed start state — opacity 0, translated —
     applied until the observer fires, so a reduced-motion reader with a slow
     observer sees blank blocks, and one whose browser never fires it sees a
     blank page. This re-shows them outright, which is what the setting asks
     for: not a faster fade, no fade. It beats the armed rule on specificity
     (0,3,1 vs 0,2,1) — every .lnd-rise on this page is inside .lnd — and it
     does not depend on the script running at all. */
  html.lnd-rise--armed .lnd .lnd-rise,
  html.lnd-rise--armed .lnd .lnd-rise.is-in { opacity: 1; transform: none; }
}

/* ═══════════════════════════════════════════════════════════════════════════
   SESSION 40 — THE TYPOGRAPHIC HERO (`hro-`, `shw-`, `fea-`, `tru-`, `cls-`, `pge-`)
   ═══════════════════════════════════════════════════════════════════════════
   Built on the MEASURED Linear pattern, not a described one —
   scratch_agent_s40/linear_reference/analysis.md carries the numbers, and five of
   the brief's assumptions about that page did not survive rendering it: it is
   left-aligned, 64px not 96-128, weight 510 not bold, has no hero CTA at all, and
   does show its product inside the hero.

   ⚑ THE GROUND WAS THE BRAND CERULEAN AND IS NOW CHARCOAL + STEEL (Session 40c).
   Session 40 chose #04202C — the brand cerulean #005F82 at 9% lightness — and
   rejected charcoal for being a different hue. Session 40c's brief specifies
   charcoal #1A1F2E with a steel-blue #3D7BA8 glow by hex, three times, describing it
   as already approved; it was not what shipped, and there is no Session 40b in this
   repo. The specification is applied and the disagreement is recorded rather than
   silently resolved. The Session 40 reasoning is kept above because it is the
   argument for the other choice, and whoever reverts this should read it. */

/* ⚑ CHARCOAL + STEEL BLUE, TO THE SESSION 40c BRIEF'S OWN HEX VALUES.
   ⚑ AND THAT IS A CHANGE, NOT A PRESERVATION — the brief says to keep "Session 40's
   charcoal #1A1F2E hero", but Session 40 shipped the brand cerulean #04202C with a
   cerulean CTA, and there is no Session 40b in this repo. The hex values are given
   three times, so they are taken as a specification and applied; reverting is one
   commit of this block. */
/* ═════════════════════════════════════════════════════════════════════════════
   SESSION 44 · THE DARK PUBLIC GROUND
   ═════════════════════════════════════════════════════════════════════════════
   ⚑ IT IS A TOKEN REPOINT, NOT FORTY OVERRIDES. `.lnd` declares every colour it
   uses as a local (--lnd-ink, --lnd-muted, --lnd-card, --lnd-line, the cerulean
   ramp) and roughly forty rules read those names. Repointing them here switches
   all forty at once, which is the same reason the block exists at all: "a retheme
   is one block", in its own words. Only rules that hard-code a light literal need
   an override, and every one of them is below with the reason.

   ⚑ EVERY VALUE IS MEASURED (scratch_s44/contrast.py), NOT PICKED BY EYE.
   Against the three grounds this sheet paints - page #1A1F2E, band #22283A,
   card #2A3247:
       ink   #EAF1F5   14.38 / 12.84 / 11.9   AA everywhere
       muted #A9C0CB    8.67 /  7.74 /  7.2   AA everywhere, including 15px body
       link  #8FC4DE    8.69 /  7.76 /  7.2   AA everywhere
   The old light values would have been 1.1-1.3:1 here. CLAUDE.md records two
   sweeps that missed exactly this by measuring a declaration instead of a render.

   ⚑ THE BORDERS ARE 1.6:1 AND THAT IS STATED RATHER THAN GLOSSED. #39415A on
   #1A1F2E is well under 3:1. A card border is decoration, not a UI component
   whose state a user must perceive - the card is also carried by a real surface
   step (#2A3247 on #1A1F2E is 1.20) and by its padding - so 1.4.11 does not
   apply to it. Anything that DOES carry state (focus rings, the active nav item,
   form field borders) is handled separately and does clear 3:1. */
.lnd--dark {
  background: #1A1F2E;
  color: #DCE3EE;

  /* the ramp, repointed. Same names, dark values. */
  --lnd-ink:    var(--pub-ink);      /* #FFFFFF - headings, body, labels */
  --lnd-muted:  var(--pub-ink-2);    /* #D4DCE8 - captions, small print   */
  --lnd-card:   #2A3247;
  --lnd-bg:     #1A1F2E;
  --lnd-tint:   #22283A;
  --lnd-line:   #39415A;

  /* ⚑ THE CERULEAN INVERTS, AND THIS IS THE ONE THAT NEEDS WATCHING.
     --lnd-cerulean is #005F82 on light, where it is a 7.10 INK and also the fill
     of `.lnd-btn--primary` under WHITE text. On dark it has to become a light
     steel to stay readable as ink - and the moment it does, white-on-it is 1.9,
     so `.lnd-btn--primary` MUST be overridden below. It is. Do not repoint this
     without checking that button. */
  --lnd-cerulean:      #8FC4DE;   /*  8.69 on the page ground   */
  --lnd-cerulean-deep: #B5DCF0;   /* 11.35 - hover, brighter    */
  --lnd-cerulean-50:   #223247;   /*  chip / tick fill          */
  --lnd-cerulean-100:  #3E5570;   /*  chip border / meter track */
}

/* ── the bands ───────────────────────────────────────────────────
   These four hard-code light surfaces (--surface-tier-*, --warm-200) rather than
   reading a --lnd-* local, so the repoint above cannot reach them.
   ⚑ `--card` IS #22283A ON PURPOSE: it is the exact value the carousel band on
   the landing page already uses, so a visitor moving from / to /pricing sees the
   same two-tone alternation rather than a third shade. */
.lnd--dark .lnd-band--card { background: #22283A; }
.lnd--dark .lnd-band--bg   { background: #1A1F2E; }
.lnd--dark .lnd-band--tint { background: #202638; }
.lnd--dark .lnd-band--sky  { background: #1E2536; }
/* ⚑ THE `--deep` BAND KEEPS THE BRAND CERULEAN AND IS THE ONE LIGHT-ON-COLOUR
   SURFACE LEFT. White on #005F82 is 7.10 and #CFE4EC is 5.39, both measured
   before Session 44 and both still true - but the two rules that paint its body
   copy read `--lnd-cerulean-100`, which has just been repointed to a DARK
   #3E5570. On this band that would be 1.6:1. They are pinned to the literal. */
.lnd--dark .lnd-band--deep { background: #005F82; color: #FFFFFF; }
/* ⚑ #CFE4EC IS 5.39 ON #005F82 AND #D4DCE8 IS 5.14 - both clear AA and the
   named value is very slightly darker, so this one is changed for CONSISTENCY
   of the palette rather than for legibility, and the number is recorded so the
   next reader knows it was checked rather than assumed. */
.lnd--dark .lnd-band--deep p,
.lnd--dark .lnd-band--deep .lnd-lead,
.lnd--dark .lnd-band--deep .lnd-eyebrow { color: var(--pub-ink-2); }
.lnd--dark .lnd-band--deep .lnd-h2,
.lnd--dark .lnd-band--deep .lnd-display,
.lnd--dark .lnd-band--deep h3 { color: #FFFFFF; }

/* ── buttons ──────────────────────────────────────────────────────
   ⚑ THE PRIMARY IS THE HERO'S CORAL, AND THAT IS ONE DECISION IN ONE PLACE.
   It was `var(--lnd-cerulean)` under white text - which the repoint above turns
   into white-on-#8FC4DE at 1.9:1, so it could not have been left alone in any
   case. Given it had to change, it becomes the same gradient `.hro__cta` paints:
   the brief asks for the coral CTA styling to be preserved and for button styling
   to be consistent, and one primary colour per site is what satisfies both.
   ⚑ THE HEX IS #C93F2A, NOT THE BRIEF'S #E85A4F, AND THE REASON IS RECORDED
   ABOVE ON `.hro__cta`: white on #E85A4F is 3.50:1 and these labels are not large
   text. #C93F2A is the same hue measured down to 4.97. Copied deliberately rather
   than re-derived - two coral definitions is how they drift. */
.lnd--dark .lnd-btn--primary {
  background: linear-gradient(180deg, #C93F2A 0%, #B83A26 100%);
  color: #FFFFFF;
  border-color: transparent;
  box-shadow: 0 14px 32px -14px rgba(201, 63, 42, .8);
}
/* ⚑ SESSION 54: THE HOVER WAS LIGHTENED TWICE AND LANDED AT 4.24:1.
   This rule used to restate a LIGHTER gradient (#D6462F -> #C43D28) on top of
   the resting one - and `vibrant.css:126` ALSO applies `filter: brightness(1.06)`
   to `.lnd-btn:hover`, site-wide, as the interaction layer's lift. The two
   compose: #D6462F brightened by 1.06 is #E34A32, and white on it measured
   **4.24** in a browser against a 4.5 floor, on 16px semibold - i.e. not large
   text. Rest is 5.22-5.26, so the button was readable until a pointer touched it.
   ⚑ NO PAGE SWEEP COULD SEE IT. This is the third instance of the family
   CLAUDE.md already records twice (the hero CTA going dark teal on hover, the
   focused email field going white) and it was found the same way: by hovering
   every control and measuring it in that state (`scratch_s54/work/D/states.py`,
   966 hovers), then confirmed on the whole CTA family by
   `scratch_s54/work/D/verify_hover.py`.
   ⚑ THE FIX IS TO DELETE THE SECOND LIGHTENING, NOT TO PICK A THIRD CORAL.
   The lift is already there: vibrant.css gives this button
   `translateY(-1px) scale(1.02)` AND `brightness(1.06)`, which is exactly the
   treatment `.hro__cta` has always used (its own `brightness(1.09)`, measured
   4.58 - passing, and the least headroom on the page). Restating a hover fill
   here was the second coral definition this file's own comment warns about four
   lines up. Measured after the change: **4.73 / 4.76**.
   `color: #FFFFFF` STAYS - `style.css:182` carries a bare
   `a:hover { color: var(--blue-700) }` at (0,1,1) and these buttons are <a>s. */
.lnd--dark .lnd-btn--primary:hover {
  color: #FFFFFF;
}
/* ⚑ THE LAST FIVE TEXT USES OF THE ACCENT, MOVED IN SESSION 44c.
   `--lnd-cerulean` (#8FC4DE) keeps its job as DECORATION on the dark ground -
   chip borders, tick glyphs, the caveat rule, the focus ring - and stops being
   an INK. It measured 6.32 at worst, which clears AA and is exactly the band
   the operator was reading as dim. Nothing that a reader has to read is on it
   any more. */
.lnd--dark .lnd-eyebrow,
.lnd--dark .lnd-card__label,
.lnd--dark .lnd-tag { color: var(--pub-ink-2); }
.lnd--dark .lnd-price__amt { color: var(--pub-ink); }
.lnd--dark .lnd-btn--quiet {
  color: var(--pub-ink-2);
  border-color: #3E5570;
  background: transparent;
}
.lnd--dark .lnd-btn--quiet:hover { background: #223247; color: var(--pub-ink); }
/* `--on-deep` sits on the #005F82 band: white pill, dark ink. `--lnd-cerulean-deep`
   is now a LIGHT #B5DCF0, which on white is 1.4 - so this one is pinned too. */
.lnd--dark .lnd-btn--on-deep { background: #FFFFFF; color: #004A66; }
.lnd--dark .lnd-btn--on-deep:hover { background: #EAF3F7; color: #004A66; }
.lnd--dark .lnd-btn:focus-visible { outline-color: #8FC4DE; }

/* ⚑ THE TAP-TARGET AND PAGE-GROUND RULES THAT WERE HERE ARE IN polish.css.
   They are about the burger, the global footer and the <body> - three things
   base.html renders on EVERY page - and this sheet is linked by five
   templates only, so /login, /register, /terms and /privacy never saw them.
   The footer retune below moved for the same reason. */

/* ── the caveat ─────────────────────────────────────────────────
   This is where the non-affiliation disclosure renders on the marketing pages,
   so it is the last thing that may become hard to read. Its violet wash (#F3ECFE)
   and 7.53-on-violet strong (#5B2BC0) are both light-page values. */
.lnd--dark .lnd-caveat {
  background: #232A3D;
  border-left-color: #8FC4DE;
  color: var(--pub-ink-2);
}
.lnd--dark .lnd-caveat strong { color: var(--pub-ink); }

/* ── odds and ends that hard-code a light value ─────────────────────── */
.lnd--dark .lnd-price-card { border-color: #3E5570; }
.lnd--dark .lnd-faq details { border-bottom-color: #39415A; }
.lnd--dark .lnd-method__item { border-color: #39415A; }
/* the tick glyph is drawn with two borders in the cerulean, which is now light -
   it reads correctly on the dark chip fill, so only the chip needed repointing
   and that came free from --lnd-cerulean-50. Nothing to do here; noted so the
   next reader does not go looking. */


/* ── 1 · the hero ──────────────────────────────────────────────────────────*/
.hro {
  position: relative;
  min-height: 90vh;
  display: flex;
  align-items: center;
  overflow: hidden;
  background: linear-gradient(180deg, #1A1F2E 0%, #171B29 46%, #12151F 100%);
}
/* one soft cerulean glow for depth. No shader, nothing busy — the brief's own limit,
   and Linear's ground is flat with only its product lightening it. */
.hro__glow {
  position: absolute;
  inset: 0;
  pointer-events: none;
  background:
    radial-gradient(56% 46% at 22% 38%,
      rgba(61, 123, 168, .30) 0%, rgba(61, 123, 168, 0) 68%),
    radial-gradient(40% 34% at 82% 8%,
      rgba(61, 123, 168, .16) 0%, rgba(61, 123, 168, 0) 70%);
}
.hro__in {
  position: relative;
  z-index: 1;
  width: min(1180px, 90vw);
  margin-inline: auto;
  /* ⚑ NOT VERTICALLY CENTRED, WHICH IS MEASURED. Linear's headline sits about a
     third down because the product takes the rest; this padding does the same
     without needing a fixed offset. */
  padding: 16vh 0 12vh;
}

/* ⚑ TWO CLASSES, BECAUSE `body.theme-parent h1` IS (0,1,2) AND WINS OVER ONE.
   style.css sets `letter-spacing: -0.03em` on every h1 under the parent theme, so
   the -0.022em measured off Linear was silently overridden — the gate caught it as
   -0.030em at every width while the declaration in this file looked correct.
   `.lnd .hro__title` is (0,2,0), which outranks a class plus two elements. This is
   the fifth time this family of sheets has lost a declaration this way; CLAUDE.md
   records `body.theme-kid h1` beating a `.x--list h1` for exactly the same reason,
   and says in as many words: match the specificity. */
.lnd .hro__title {
  margin: 0;
  font-family: var(--lnd-display);
  /* ⚑ SESSION 44: THE HEADLINE IS THE BRAND AND IT IS AT HEADLINE SCALE.
     56px at 390, 69 at 768, 92 at 1024, 128 at 1440 and above. The brief asks
     for 96-128 desktop / 56-72 mobile; 9vw lands inside both bands and the 8rem
     cap stops 1920 growing a third line's worth of height for no gain.
     ⚑ "Korva Academy" WRAPS TO TWO LINES ON A NARROW PHONE AND THAT IS INTENDED,
     not an overflow: 13 characters at 56px is about 400px of ink against a 351px
     container. `text-wrap: balance` splits it evenly. Shrinking the type to force
     one line is the "cram" the brief rules out. */
  font-size: clamp(3.5rem, 9.4vw, 8rem);
  letter-spacing: -.022em;   /* measured off Linear at every width */
  line-height: 1.02;         /* theirs is 1.00 at 1440 */
  /* 600, not 700: Linear's is 510, and the confidence comes from the tightness
     rather than the mass */
  font-weight: 600;
  color: var(--pub-ink);
  text-wrap: balance;
}
/* also a <p>, and also outranked by `.lnd p` - it happens to declare the same
   14px bottom margin, so nothing moved, but the rule was equally inert and the
   next person to change this number would have found it did nothing. */
.lnd .hro__eyebrow {
  margin: 0 0 14px;
  text-transform: uppercase;
  letter-spacing: .16em;
  font-size: .72rem;
  font-weight: 700;
  color: var(--pub-ink-2);
}
/* ⚑ `.lnd .hro__sub`, NOT `.hro__sub`, AND THE ONE-CLASS VERSION LOST.
   `.lnd p { margin: 0 0 14px }` is (0,1,1) and `.hro__sub` is (0,1,0), so the
   22px declared here never applied and /about rendered its sub-line hard against
   the baseline of its headline - measured as a 0px gap at all three widths. The
   landing hero did not show it only because `.hro--home .hro__sub` (0,2,0) had
   already been written for other reasons. Sixth instance of this exact trap in
   this sheet family; CLAUDE.md's instruction is "match the specificity". */
.lnd .hro__sub {
  margin: 24px 0 0;
  font-size: clamp(1.02rem, 1.5vw, 1.24rem);
  line-height: 1.5;
  color: var(--pub-ink);
  max-width: 40ch;
}
.hro__act { margin-top: 40px; }

.hro__cta {
  display: inline-flex;
  align-items: center;
  gap: 10px;
  min-height: 60px;
  padding: 0 34px;
  /* Linear's nav control is a pill; its body buttons are ~8px. 10px reads as
     deliberate at this size. */
  border-radius: 10px;
  font-size: 1.06rem;
  font-weight: 650;
  text-decoration: none;
  color: #FFFFFF;
  /* ⚑ CORAL, DARKENED UNTIL IT PASSES, AND THE MEASUREMENT IS WHY. The brief names
     #E85A4F; white on it is **3.50:1**, and this label is 17px semibold — not large
     text — so it needs 4.5. #C93F2A is the same hue with the light taken down and
     measures **4.97**, the darker stop **5.72**. Shipping the named value would have
     put the page's only call to action below AA. (style.css already records coral
     #ff7183 failing against white outright; this is a different coral, measured
     rather than assumed either way.) */
  background: linear-gradient(180deg, #C93F2A 0%, #B83A26 100%);
  box-shadow: 0 14px 32px -14px rgba(201, 63, 42, .85),
              0 1px 0 rgba(255, 255, 255, .18) inset;
  transition: transform .18s cubic-bezier(.2, .7, .3, 1),
              filter .18s ease, box-shadow .18s ease, gap .18s ease;
}
/* ⚑ THE LABEL HAD TO BE RE-DECLARED HERE, AND IT WAS WRONG BEFORE SESSION 44.
   `style.css:182` carries a bare `a:hover { color: var(--blue-700) }` at
   (0,1,1); `.hro__cta` declares its white at (0,1,0) and loses on hover. So the
   page's one call to action turned its label DARK TEAL on the coral fill the
   moment a pointer touched it — measured rgb(0,65,90) on #C93F2A, **2.22:1**
   against a 4.5 floor, where the resting state is 4.97.
   ⚑ NO PAGE SWEEP COULD SEE IT, which is why it survived from Session 40:
   `assert_landing_hero.py` samples the resting page, and CLAUDE.md already says
   ":hover/.active colours are unreachable by a page sweep". Found by hovering
   every control on every public page (`scratch_s44/audit_states.py`). */
.hro__cta:hover,
.hro__cta:focus-visible {
  color: #FFFFFF;
  transform: translateY(-2px) scale(1.015);
  filter: brightness(1.09);
  gap: 14px;
  box-shadow: 0 20px 40px -16px rgba(201, 63, 42, .95),
              0 1px 0 rgba(255, 255, 255, .24) inset;
}
.hro__cta:focus-visible { outline: 3px solid #F0B3A6; outline-offset: 3px; }
.hro__cta:active { transform: translateY(0) scale(.996); }

/* ⚑ `.lnd .hro__micro`, NOT `.hro__micro`, AND THE ONE-CLASS VERSION HAD BEEN
   INERT SINCE SESSION 44. `.lnd p { margin: 0 0 14px }` is (0,1,1) and this is a
   <p>, so at (0,1,0) the 16px top margin declared here never applied: measured
   **0px between the CTA's bottom edge and the microtext at every width**, which
   is why the line sat against the button.
   ⚑ THIRD INSTANCE OF THIS EXACT TRAP IN THIS FILE. Session 44 wrote
   `.lnd .hro__title` for it, 44b found `.lnd p` beating `.hro__sub` and fixed
   that one - and did not check the sibling paragraph two rules below. When a
   `.hro__*` rule targets a <p>, it needs the `.lnd` prefix; there are exactly
   three such rules and all three carry it now.
   ⚑ 24px, THE TOP OF THE BRIEF'S 16-24 BAND, because the gap it replaces is 0
   rather than the 16 the old declaration claimed. */
.lnd .hro__micro {
  margin: 24px 0 0;
  /* 15px at 390, 16px at 1024 and up — the brief's 14-16 band. */
  font-size: clamp(.9375rem, .9vw, 1rem);
  /* the brief asks for 65-75% of the headline colour; this is about 70% of #F4F7F8
     against the hero ground, and the gate measures it rather than trusting it.
     ⚑ #93AAB4 -> #A9C0CB in Session 44 (a 15px string is BODY text under WCAG,
     so its floor is 4.5), and -> --pub-ink-2 in 44c: 6.41 cleared the floor and
     still read as dim grey, which is the whole reason 44c exists. */
  color: var(--pub-ink-2);
}

/* ── 1b · the home hero (Session 44) ────────────────────────────────
   ⚑ SCOPED TO `.hro--home`, NOT TO `.hro`. /about reuses this shell through
   `.hro--page`, and the prose beneath it is left-aligned; centring the shell
   itself would give that page a centred heading over a left-aligned article,
   which is two alignments on one page. The landing hero opts in.

   ⚑ THE WHOLE THING FITS ONE VIEWPORT AND THE UNIT IS THE REASON.
   `100svh` is the SMALL viewport height — the height with a mobile browser's
   address bar SHOWING, which is what you get on first paint. `100vh` on iOS
   Safari resolves to the LARGE viewport, so a hero built to it is 60-90px taller
   than the screen on arrival and the CTA is the thing that falls off the bottom:
   precisely the failure this hero is required not to have. `dvh` would fit too,
   but it resizes as the bar hides, which moves the CTA under a reader's thumb
   mid-tap. */
/* ⚑ `.lnd .hro--home`, NOT `.hro--home`, AND THE ONE-CLASS VERSION SILENTLY LOST.
   The narrow block at the foot of this file declares `.hro { min-height: 82vh }`
   inside `@media (max-width: 860px)`. Media queries contribute NO specificity, so
   `.hro` (0,1,0) arriving later beat `.hro--home` (0,1,0) arriving earlier, and
   the hero measured 692px against an 844px phone and 840 against a 1024px tablet
   — 82vh in both cases, exactly. Nothing errored and the declaration in this file
   looked correct. `.lnd .hro--home` is (0,2,0) and outranks it wherever it sits.
   This is the same trap `.lnd .hro__title` above was already written to dodge, and
   CLAUDE.md records two more instances (`body.theme-kid h1`, `.adm-dim`) with the
   same instruction: match the specificity. */
.lnd .hro--home {
  min-height: 100svh;
  text-align: center;
}
/* ⚑ THE PADDING CLEARS THE NAV RATHER THAN CENTRING UNDER IT. `.knav--public`
   is transparent and sits OVER this section, so a purely centred block puts the
   headline behind the brand mark at short viewport heights. The top padding is
   the nav's height plus air; the flex centring then works on what is left. */
.hro--home .hro__in {
  padding: 104px 0 56px;
  display: flex;
  flex-direction: column;
  align-items: center;
}
.hro--home .hro__title { text-wrap: balance; }

/* ⚑ GENEROUS, AND ON THE 8px SCALE. 32 / 24 / 48 / 16 between the four blocks,
   clamped against viewport height so a short phone does not spend its whole
   screen on gaps. */
.hro--home .hro__sub {
  margin: clamp(24px, 3vh, 32px) 0 0;
  /* 18px at 390, 22 at 768, 27 at 1440, 32 capped — the brief's 24-32 desktop
     and 18-22 mobile. */
  font-size: clamp(1.125rem, 2.35vw, 2rem);
  /* ⚑ THE 40ch MEASURE IS RELEASED HERE. It is a sensible cap on a sentence and
     wrong on a five-word label: at 1440 it left the line visibly off-centre
     inside a centred column, because the BOX was narrower than the text looked.
     Centred text needs the box centred too. */
  max-width: none;
  /* ⚑ #A8BFC8 -> #B6CCD6 in Session 44 (at 18px this is body text, floor 4.5),
     -> --pub-ink primary in 44c: it is the line that says what the product is
     for, which is not small print. */
  color: var(--pub-ink);
}

/* ⚑ A LIST, DRAWN WITHOUT ITS MARKERS, AND IT IS A <ul> ON PURPOSE. Four
   sections is a list of four things and a screen reader should say so. The
   markers are suppressed with `list-style-type: ""` rather than
   `list-style: none`, because `none` on a <ul> with no other role drops it out
   of the accessibility tree in Safari — the semantics are the reason the
   element was chosen, so they must survive the styling. */
.hro--home .hro__subjects {
  list-style-type: "";
  margin: clamp(20px, 2.6vh, 24px) 0 0;
  padding: 0;
  display: flex;
  flex-direction: column;
  /* ⚑ 8px IS THE BOTTOM RUNG OF THE BRIEF'S SCALE AND THE LIST MAY NOT GO
     UNDER IT. The first version clamped from 4px, which measured 5-6px at 390
     and 768 - inside a hero that has room to spare (the CTA sits at 75% of the
     fold at 390). "Tighter line-height" is asked for on the TYPE, and it has it
     at 1.22; it is not a licence to close the gaps below the scale. */
  gap: clamp(8px, .8vh, 12px);
}
.hro--home .hro__subjects li {
  /* 21.6px at 390, 26.6 at 1024, 37.4 at 1440, 40 capped — the brief's 28-40
     desktop and 20-26 mobile, and the one size the brief says to REDUCE rather
     than crowd if the hero will not fit. It is the only type on this page
     allowed to give ground. */
  font-size: clamp(1.35rem, 2.8vw, 2.5rem);
  line-height: 1.22;          /* tighter than body, as briefed */
  letter-spacing: -.012em;
  font-weight: 600;
  /* the headline's own value: this is the substance of the offer and reading as
     secondary would be wrong */
  color: var(--pub-ink);
}
.hro--home .hro__act {
  margin-top: clamp(32px, 4.5vh, 48px);
  display: flex;
  flex-direction: column;
  align-items: center;
}

@media (max-width: 560px) {
  /* ⚑ THE PHONE GIVES GROUND ON THE SUBJECT LIST FIRST, WHICH IS THE BRIEF'S
     OWN INSTRUCTION. Everything else keeps its size; only the four names step
     down, and only below 560px. */
  .hro--home .hro__in { padding: 88px 0 40px; }
  .hro--home .hro__subjects li { font-size: clamp(1.25rem, 5.6vw, 1.5rem); }
}

/* ⚑ A SHORT LANDSCAPE PHONE IS THE ONE VIEWPORT THIS HERO CANNOT SIMPLY FIT.
   At 800x420 the content is about 430px against 420 of screen. Rather than
   shrink four separate things toward illegibility, the hero stops insisting on a
   full viewport and becomes an ordinary scrolling block — which is the honest
   outcome: the CTA is one short scroll away instead of being cut in half. */
@media (max-height: 520px) and (orientation: landscape) {
  .lnd .hro--home { min-height: auto; }   /* (0,2,0) — see the note above */
  .hro--home .hro__in { padding: 88px 0 48px; }
  .hro--home .hro__title { font-size: clamp(2.5rem, 6vw, 4rem); }
  .hro--home .hro__subjects li { font-size: 1.25rem; }
}

/* the About page reuses the hero shell at a smaller size */
.hro--page { min-height: 46vh; }
/* ⚑ THE SAME CONTAINER AS THE BODY BENEATH IT. The hero's own width is
   min(1180px, 90vw) and `.pge__in` is min(760px, 92vw), so on /about the heading
   started at x=130 and the prose under it at x=340 — two left edges on one page,
   visible in the render before anything measured it. */
.hro--page .hro__in { width: min(760px, 92vw); padding: 14vh 0 7vh; }
.lnd .hro__title--sm { font-size: clamp(2.1rem, 4.4vw, 3.4rem); }

/* ── 2 — the carousel (`car-`) ──*/
/* ⚑ NOTHING HERE MAY CROP A PICTURE, AND THE BUG WAS NEVER IN THIS FILE.
   The top-cropping fault chased through Sessions 34b-41 was a FILE fault:
   scratch_agent_p1/recrop.py cut 720x480 windows out of the 1600x1000 captures for
   the old `.fea` thumbnails, and for writing it opened the window at y=120, so the
   top 120px of that board — its whole chrome bar — was missing from the file
   itself. Session 41's `.fea__shot` was `width: 100%; height: auto`, which cannot
   crop anything; every session that went looking in the stylesheet was looking in
   the wrong place. The `<name>_card` crops are referenced nowhere now.

   ⚑ THREE INDEPENDENT THINGS KEEP IT SHUT, AND THE THIRD IS THE ONE THAT
   SURVIVES A FUTURE MISTAKE:
     1. `aspect-ratio: 16 / 10` on the IMAGE is exactly 1600x1000, so the box the
        browser draws and the file it draws agree and there is nothing to resolve;
     2. `object-fit: contain` — if a future picture is NOT 16:10 it letterboxes
        inside that box instead of losing an edge. `cover` is the declaration that
        crops and it must never appear in this block;
     3. nothing clips. There is no `overflow: hidden` on the picture and no
        `border-radius` on the <img>. ⚑ THAT IS DELIBERATE AND IT COST THE
        ROUNDED CORNER: a radius on the image itself hides a few pixels of each
        corner, which is a small crop and still a crop. The rounding is on the FRAME
        around it, which has padding, so the picture inside is a plain rectangle and
        every pixel of it is on screen.
   carousel_problems() in app.py checks (1) against the real files at boot;
   tools/assert_no_image_crop.py proves it in a browser at five widths. */

.car {
  background: #22283A;   /* a step up from the hero's #1A1F2E, so the band reads as
                            its own section without introducing a second hue */
  border-top: 1px solid rgba(150, 175, 210, .10);
  padding: var(--lnd-band) 0 calc(var(--lnd-band) - 12px);
  /* ⚑ THE SECTION IS WIDER THAN ITS CONTAINER ON PURPOSE. The track bleeds to
     the viewport edges so a card can sit half-off-screen and read as continuing;
     `.car__in` keeps the heading on the same left edge as every other page. */
  overflow: hidden;
}
.car__in { width: min(1180px, 92vw); margin-inline: auto; }
.car__h {
  font-family: var(--lnd-display);
  font-size: clamp(1.6rem, 3vw, 2.4rem);
  letter-spacing: -.022em;
  line-height: 1.1;
  font-weight: 600;
  color: var(--pub-ink);   /* 14.66 on #22283A */
  margin: 0;
}

.car__wrap { position: relative; margin-top: clamp(24px, 3vw, 40px); }

/* ⚑ A NATIVE SCROLLER, NOT AN ANIMATION. Sessions 37-39 shipped a marquee, a
   peacock, a snap carousel and a ten-card drift here and every one of them failed on
   animation arithmetic — a `flex: none` that collapsed a `max-content` track, a
   circular custom property, a 50% translate measured against the element's own box.
   This has none: the browser scrolls, snap decides where it stops. It works with a
   finger, a trackpad, a keyboard and a screen reader, and it lets a reader STOP on
   one picture, which a marquee by definition does not. */
.car__track {
  --car-w: min(900px, 78vw);
  --car-gap: 22px;
  list-style: none;
  margin: 0;
  display: flex;
  gap: var(--car-gap);
  overflow-x: auto;
  overscroll-behavior-x: contain;
  scroll-snap-type: x mandatory;
  scroll-behavior: smooth;
  /* centres the first and last card instead of jamming them against the edge */
  padding: 4px max(4vw, calc((100% - var(--car-w)) / 2));
  /* the scrollbar is hidden, NOT the scrolling — the track is still a scroll
     container for a finger, a wheel and the arrow keys, and the arrows landing.js
     adds are an addition to that, never the only way through */
  scrollbar-width: none;
}
.car__track::-webkit-scrollbar { display: none; }
.car__track:focus-visible {
  outline: 3px solid #8FC4DE;
  outline-offset: 4px;
  border-radius: 6px;
}

.car__item {
  flex: 0 0 var(--car-w);
  scroll-snap-align: center;
  margin: 0;
}
.car__fig { margin: 0; }

/* the FRAME is what is rounded; the picture inside it is not */
.car__box {
  background: #0E1119;
  border: 1px solid rgba(150, 175, 210, .16);
  border-radius: 14px;
  padding: 8px;
  box-shadow: 0 30px 70px -34px rgba(0, 0, 0, .85);
}
.car__img {
  display: block;
  width: 100%;
  height: auto;
  aspect-ratio: 16 / 10;   /* == 1600x1000, the real size of every file */
  object-fit: contain;     /* never `cover` */
}

.car__cap {
  margin: 14px 4px 0;
  font-size: .88rem;
  line-height: 1.45;
  color: var(--pub-ink-2); /* 10.61 on #22283A - was 7.72 measured */
}

/* ⚑ THE ARROWS ARE ADDED BY landing.js AND STYLED HERE. They are a convenience
   over a track that already scrolls; with the script blocked they simply do not
   exist and nothing is lost. 44px is the WCAG 2.5.5 floor and is the deliberate
   value, not a rounded-down 48. */
.car__arw {
  position: absolute;
  top: calc(50% - 22px);
  z-index: 2;
  width: 44px;
  height: 44px;
  border-radius: 50%;
  border: 1px solid rgba(190, 212, 235, .34);
  background: rgba(18, 21, 31, .82);
  color: #E6EDF5;
  cursor: pointer;
  display: grid;
  place-items: center;
  font-size: 1.1rem;
  line-height: 1;
  transition: background .18s ease, border-color .18s ease;
}
.car__arw:hover { background: rgba(18, 21, 31, .95); border-color: rgba(190, 212, 235, .6); }
.car__arw:focus-visible { outline: 3px solid #8FC4DE; outline-offset: 3px; }
.car__arw[disabled] { opacity: .32; cursor: default; }
.car__arw--prev { left: max(10px, 2vw); }
.car__arw--next { right: max(10px, 2vw); }

@media (prefers-reduced-motion: reduce) {
  /* a snap jump is still a jump; with reduce set it happens at once rather than
     gliding, and the scroll is still a scroll */
  .car__track { scroll-behavior: auto; }
  .car__arw { transition: none; }
}

@media (max-width: 860px) {
  .car__track { --car-w: 84vw; --car-gap: 14px; }
  .car__box { padding: 6px; border-radius: 12px; }
  .car__cap { font-size: .82rem; margin-top: 10px; }
  /* ⚑ NO ARROWS ON A TOUCH-WIDTH SCREEN. A finger already does this, and two
     44px discs over a 84vw card cover the picture they are meant to reveal. */
  .car__arw { display: none; }
}

/* ── the footer, on the dark pages only ───────────────────*/
/* ⚑ THE FOOTER IS WARM NEAR-BLACK AND THIS PAGE IS COOL NEAR-BLACK, AND THE SEAM
   SHOWED. Measured: `.korva-footer` is #211E1B (composited #1F1A14) while `.cls`
   bottoms out at #12151F — luminance 0.0108 against 0.0076, so the step is almost
   invisible, but the HUE step is not: a brown band under a blue-black page reads as
   a different site's footer. It is the shared footer, so /pricing, /how-it-works and
   /faq keep their warm one; this is scoped with the same `:has()` the fixed nav uses.
   ⚑ AND THE INK MOVED WITH IT, MEASURED, NOT ASSUMED. Warm greys on a cool ground is
   the same mismatch one layer in. Contrast on #12151F, old -> new:
   body 6.90 -> 7.73 · meta 4.87 -> 5.79 · links 10.13 -> 11.17. The separator dot is
   decoration (2.28 -> 2.44) and carries no information; it is `aria-hidden` in the
   markup. Every value here is measured in scratch_agent_p1/contrast.py's sweep. */
/* ⚑ MOVED TO polish.css IN SESSION 44 - see the note above. The footer is on
   every page and four public pages do not load this sheet. */

/* ── the About page's body ─────────────────────────────────────────────────*/
/* ⚑ SESSION 44b: THE TOP PADDING WAS ZERO AND THE FIRST HEADING TOUCHED THE
   HERO. Measured at 390, 768 and 1440: "What it is" sat **0px** below the hero
   section's bottom edge at every width, while the three headings after it had
   34px (390/768) and 56px (1440) above them. So the page opened with its one
   crowded heading and then read correctly for the rest of its length.
   `--lnd-band-tight` is the rhythm token this sheet already uses for a
   half-height band — clamp(40px, 5vw, 72px) — so the gap is now 40 / 40 / 72,
   on the 8px scale and never smaller than the gaps between the blocks below. */
.pge { background: #1A1F2E; padding: var(--lnd-band-tight) 0 var(--lnd-band); }
.pge__in { width: min(760px, 92vw); margin-inline: auto; }
/* ⚑ 34px IS NOT ON THE 8px SCALE AND 32 IS. The other end (56) already was.
   This is the only value the brief's "8px multiples" rule was missing on this
   page; it changes the 390 and 768 gaps from 34 to 32 and leaves 1440 at 56. */
.pge__block { margin-bottom: clamp(32px, 4vw, 56px); }
.pge__h {
  margin: 0 0 14px;
  font-family: var(--lnd-display);
  font-size: clamp(1.3rem, 2.2vw, 1.7rem);
  letter-spacing: -.014em;
  color: var(--pub-ink);
}
.pge__block p { margin: 0 0 14px; color: var(--pub-ink); line-height: 1.68; }
.pge__block strong { color: var(--pub-ink); }
/* ⚑ THE DIMMEST STRING ON ANY PUBLIC PAGE, AT 5.15:1. It is the
   non-affiliation disclosure, so it was also the last thing that should have
   been hard to read. Small print by size, never by legibility. */
.pge__na { margin: 18px 0 0; font-size: .84rem; line-height: 1.6; color: var(--pub-ink-2); }
.pge__cta { display: grid; justify-items: start; gap: 4px; margin-top: 8px; }

/* ── the navigation over the hero ──────────────────────────────────────────
   ⚑ THREE CLASSES, AND THE THIRD IS LOAD-BEARING. theme.css declares
   `body.theme-parent .nav { background: rgba(246,241,231,.88) }` at (0,2,1), which
   BEATS a two-class rule — Session 36 measured the bar keeping its cream background
   while its links went white, at 1.27:1. A third class is (0,3,0) and outranks a
   class pair plus an element. This is the fourth time this family of sheets has lost
   a declaration that way; see `.adm-dim` and `body.theme-kid h1`. */
/* ⚑ THE BAR HAS TO OVERLAY THE HERO, AND MAKING IT TRANSPARENT WAS NOT ENOUGH.
   `.nav` is `position: sticky` in style.css, so it occupies flow space and the hero
   begins BELOW it — a transparent bar then shows the body's cream, which is exactly
   what the first render did: a 65px cream strip above a dark hero with near-white
   links on it, measured at rgba(0,0,0,0) background and rgb(234,241,244) links.
   Both were correct; the bar was simply not over anything.
   ⚑ SCOPED WITH `:has()` SO ONLY THE DARK PAGES ARE AFFECTED. `_public_nav.html` is
   shared with /pricing, /how-it-works and /faq, which are light and want the sticky
   bar they have. `desktop.css` already uses this pattern in this codebase. */
/* ⚑ SESSION 44 RE-KEYED THIS OFF THE HERO, AND IT HAD TO. The rule below makes
   the bar `position: fixed` and then removes the page's top padding, which is
   correct ONLY when there is a full-bleed hero for the bar to float over. Session
   44 puts the dark ground on /pricing, /how-it-works and /faq as well, and those
   three have no hero - keyed on `.lnd--dark` they would have had a fixed bar with
   their first band sliding underneath it. Keyed on `.hro`, the behaviour follows
   the thing it was always about. /(.hro--home) and /about (.hro--page) are
   unchanged; measured, not assumed. */
body:has(.hro) .knav--public {
  position: fixed;
  inset: 0 0 auto;
  z-index: 120;
}
/* and the page beneath it starts at the top, since the bar no longer holds a place */
body:has(.hro) .lnd--dark { padding-top: 0; }

/* ⚑ AND THE SCROLLED BAR HAS TO BE OPAQUE ON THIS PAGE, WHICH IS A CONTRAST FIX AND
   NOT A TASTE ONE. The shared bar is `rgba(246,241,231,.88)`. Over a light page that
   composites to something very near #F6F1E7 and every link passes; over THIS page it
   composites to #DAD7D2, and `.knav-link--quiet` (#6E6659, 12.5px) lands at
   **3.95:1** against a 4.5 floor. Measured from the rendered pixels once the bar had
   solidified — a sweep at scrollY=0 never sees this state at all, because over the
   hero the bar is transparent and its links are near-white.
   Opaque here fixes it at 4.94 and changes nothing on /pricing, /how-it-works or
   /faq, which keep the translucent bar over their own cream ground. */
/* ⚑ THE SOLID BAR IS DARK NOW, AND THAT IS A CONSISTENCY FIX THE BRIEF ASKED
   FOR. It was #F6F1E7 - cream - because until Session 44 only two public pages
   were dark and a cream bar over them scrolled past quickly. With all eight
   public pages on the charcoal ground, a cream bar is the one light object on
   the site and it reads as a different product.
   ⚑ AND THE INKS HAD TO MOVE WITH IT, WHICH IS THE HALF THAT IS EASY TO MISS.
   `.knav-link--quiet` is `var(--ink-mute)` (#6E6659) and `.knav-link` is
   `var(--ink-soft)`; both are near-black and were chosen against cream. Left
   alone they would have landed at about 1.3:1 on this bar - the exact failure
   Session 36 measured here when the background won and the links did not.
   Measured on #1A1F2E: #EAF1F4 is 14.4, #B8C6D4 is 9.0. */
body:has(.lnd--dark) .knav--public:not(.is-over-hero) {
  background: #1A1F2E;
  border-bottom: 1px solid #2E3548;
  box-shadow: none;
}
body:has(.lnd--dark) .knav--public:not(.is-over-hero) .knav-brand,
body:has(.lnd--dark) .knav--public:not(.is-over-hero) .knav-link {
  color: var(--pub-ink);
}
body:has(.lnd--dark) .knav--public:not(.is-over-hero) .knav-link--quiet {
  color: var(--pub-ink-2);
}
body:has(.lnd--dark) .knav--public:not(.is-over-hero) .knav-link:hover {
  background: rgba(255, 255, 255, .08);
  color: #FFFFFF;
}
/* ⚑ THE ACTIVE ITEM PAINTS A PILL, AND ONLY ITS INK HAD BEEN MOVED. style.css
   gives `.knav-link.is-active` a `background: var(--surface)` - a light fill -
   so setting the text white produced white on near-white: measured 1.04:1 on
   /how-it-works, /pricing and /about at 1440, and invisible at exactly the width
   where the menu is expanded and the item is on screen. Below 860px the menu is
   behind the burger, which is why three widths reported it and six did not.
   The pill needs a fill of its own on this bar, not just an ink. */
body:has(.lnd--dark) .knav--public:not(.is-over-hero) .knav-link.is-active {
  color: #FFFFFF;
  background: #2C3448;
  border-color: #3E4761;
}
/* the burger is three painted bars, not text, so it needs the same treatment */
body:has(.lnd--dark) .knav--public:not(.is-over-hero) .knav-burger span {
  background: #EAF1F4;
}

/* ⚑ AND ONE PRIMARY COLOUR PER PAGE. The shared nav pill is the brand cerulean; on
   this page the call to action is coral, so a scrolled visitor saw a coral "Start
   free trial" in the band and a teal "Start free" in the bar at the same moment —
   two primaries, which reads as two products. Measured: white on #C93F2A is
   **4.97:1** at 12.5px, clear of the 4.5 floor (it is lower than the 7.10 the teal
   pill measured, and that is the price of the coherence — both pass).
   ⚑ SESSION 44: /pricing, /how-it-works and /faq are on the dark ground too, so
   they now get the coral pill as well - which is the point. One primary colour
   per SITE, not per page. */
body:has(.lnd--dark) .knav--public:not(.is-over-hero) .knav-link--cta {
  background: linear-gradient(180deg, #C93F2A 0%, #B83A26 100%);
  border-color: #B83A26;
  color: #FFFFFF;
}

.knav.knav--public.is-over-hero {
  background: transparent;
  box-shadow: none;
  border-bottom-color: transparent;
}
/* ⚑ #EAF1F4 WAS THE LOWEST-CONTRAST STRING IN THE WHOLE INVENTORY - 4.57:1,
   over the brightest part of the hero glow. It cleared 4.5 by 0.07. */
.knav.knav--public.is-over-hero .knav-brand,
.knav.knav--public.is-over-hero .knav-link { color: var(--pub-ink); }
.knav.knav--public.is-over-hero .knav-link--quiet { color: var(--pub-ink-2); }
/* ⚑ THE ACTIVE CHIP IS A LIGHT PILL, AND ON A DARK BAR IT SWALLOWED ITS OWN LABEL.
   `.knav-link.is-active` carries a pale background for the light nav; over the hero
   that put near-white "Home" on near-white. It becomes an underline here — the same
   signal, in a colour the bar can carry. */
.knav.knav--public.is-over-hero .knav-link.is-active {
  background: transparent;
  color: #FFFFFF;
  box-shadow: inset 0 -2px 0 rgba(255, 255, 255, .75);
  border-radius: 0;
}
.knav.knav--public.is-over-hero .brand-mark {
  background: rgba(255, 255, 255, .14);
  color: #FFFFFF;
}
/* a ghost button on a dark hero is the control people miss */
.knav.knav--public.is-over-hero .knav-link--cta {
  background: #FFFFFF;
  color: #23283A;
}
.knav.knav--public.is-over-hero .knav-burger {
  border-color: rgba(255, 255, 255, .4);
  background: rgba(255, 255, 255, .08);
}
.knav.knav--public.is-over-hero .knav-burger span { background: #FFFFFF; }
.knav.knav--public.is-over-hero .knav-menu.is-open .knav-link { color: inherit; }

/* ═══════════════════════════════════════════════════════════════════════
   SESSION 44b · THE PHONE MENU WAS A CREAM PANEL WITH NEAR-WHITE LINKS
   ═══════════════════════════════════════════════════════════════════════
   ⚑ MEASURED, NOT DESCRIBED: below 860px `style.css` gives `.knav .knav-menu`
   a `background: var(--surface)` — rgb(253,250,244), cream — and Session 44 set
   the public nav's links to #EAF1F4 for the dark bar. Tapping the burger
   therefore produced near-white text on cream: **"How it works", "Pricing",
   "About" and "Log in" were effectively invisible**, and only the active pill
   and the coral CTA could be read. On / and /about that was true before Session
   44 as well, because `.is-over-hero` has painted those links #EAF1F4 since
   Session 40; Session 44 extended it to /how-it-works, /pricing and /faq by
   making their bars dark. It is the whole navigation on a phone.

   ⚑ WHY NO GATE SAW IT. `audit_public.py` skips a text node whose element is
   `display: none`, and `#knav-menu` is `display: none` until the burger opens
   it — so the entire phone menu was outside every sweep this repo has. Session
   44's own STATES pass hovers and focuses, but it never OPENS anything.
   `scratch_s44b/audit_dark_text.py` opens the menu and every <details> first.

   ⚑ THE PANEL GOES DARK RATHER THAN THE LINKS GOING BACK TO INK. Making the
   links dark again would fix the panel and re-break the bar, which is the same
   one-value-two-meanings trap CLAUDE.md records for `is-tucked`/`is-parked` and
   `is-quiet`/`is-hushed`. One surface, one colour: the menu is a panel of the
   dark site, so it is #22283A — the exact value the carousel band and the
   marketing cards already use. Measured on it: link #EAF1F4 **12.83**, quiet
   link #B8C6D4 **8.42**, active #FFFFFF **14.66**. */
body:has(.lnd--dark) .knav--public .knav-menu {
  background: #22283A;
  border-color: #39415A;
  box-shadow: 0 24px 60px -24px rgba(0, 0, 0, .75);
}
/* ⚑ AND THE `color: inherit` ABOVE HAD TO BE UNDONE FOR THESE PAGES. That rule
   exists because over the hero the links are white and the panel WAS cream, so
   opening the menu had to hand them back their dark ink. With a dark panel it
   does the opposite: `inherit` resolves to the body's near-black and paints
   rgb(31,26,20) on rgb(26,31,46) — **1.05:1**, which is the literal
   "black text on dark blue" this session was called to remove. Reachable at
   ≤860px, and also at 1440 if a reader opens the menu narrow and then widens
   the window, because `.is-open` survives the resize. */
body:has(.lnd--dark) .knav--public.is-over-hero .knav-menu.is-open .knav-link,
body:has(.lnd--dark) .knav--public .knav-menu .knav-link { color: var(--pub-ink); }
body:has(.lnd--dark) .knav--public .knav-menu .knav-link--quiet { color: var(--pub-ink-2); }
body:has(.lnd--dark) .knav--public .knav-menu .knav-link.is-active {
  color: #FFFFFF;
  background: #2C3448;
  border-color: #3E4761;
}
body:has(.lnd--dark) .knav--public .knav-menu .knav-link:hover {
  background: rgba(255, 255, 255, .08);
  color: #FFFFFF;
}
/* the coral CTA keeps its own fill and white label in the panel too */
body:has(.lnd--dark) .knav--public .knav-menu .knav-link--cta {
  background: linear-gradient(180deg, #C93F2A 0%, #B83A26 100%);
  border-color: #B83A26;
  color: #FFFFFF;
}

/* ⚑ AND THE BURGER ITSELF DISAPPEARED ON THE WAY IN. `.nav-burger:hover` is
   `background: var(--blue-50)`, a light-theme token that resolves warm —
   measured rgb(242,236,223) — while Session 44 set its bars to #EAF1F4. So the
   moment a finger touched it the control became a blank cream square with
   near-white bars on it. Same family as the `a:hover` that turned the hero CTA
   dark teal in Session 44: a hover state inherited from the light theme. */
body:has(.lnd--dark) .knav--public .knav-burger:hover,
body:has(.lnd--dark) .knav--public .knav-burger[aria-expanded="true"] {
  background: rgba(255, 255, 255, .12);
  border-color: rgba(255, 255, 255, .40);
}
body:has(.lnd--dark) .knav--public .knav-burger:hover span,
body:has(.lnd--dark) .knav--public .knav-burger[aria-expanded="true"] span {
  background: #EAF1F4;              /* 14.37 on the bar */
}

/* ⚑ THE ONE LIGHT PANEL LEFT ON A DARK PAGE, AND IT IS NOT A CONTRAST FAILURE
   — WHICH IS EXACTLY WHY NO SWEEP FLAGGED IT. `.cm-empty` (community.css) is
   the /faq empty state; it paints its own cream fill, so its dark text reads
   perfectly against it and every contrast measurement passed. It is a Fix-5
   miss: community.css was never converted, so /faq carried one bright card in
   the middle of the dark page. Measured on #22283A: body #A9C0CB **7.74**,
   strong #EAF1F5 **12.84**, link #8FC4DE **7.76**. */
.lnd--dark .cm-empty {
  background: #22283A;
  border-color: #39415A;
  color: #A9C0CB;
}
/* ⚑ THE <p> HAD TO BE NAMED, AND THIS IS THE SECOND TIME IN TWO SESSIONS.
   `style.css:179` declares `p { color: var(--ink-soft) }` — a bare ELEMENT
   rule — and an inherited value is only used when no rule matches the element
   at all. So setting the colour on `.cm-empty` did nothing to the paragraph
   inside it: measured rgb(76,69,59) on the new rgb(34,40,58) fill, **1.55:1**.
   Session 44 hit the identical rule on the legal documents and it cost 247
   invisible strings there. Anything given a colour on a container in this
   product needs its <p> and <li> named too. */
.lnd--dark .cm-empty p,
.lnd--dark .cm-empty li { color: var(--pub-ink); }
.lnd--dark .cm-empty strong { color: var(--pub-ink); }

@media (prefers-reduced-motion: reduce) {
  .hro__cta { transition: none; }
  .hro__cta:hover, .hro__cta:focus-visible { transform: none; filter: none; }
}

/* ── narrow ────────────────────────────────────────────────────────────────*/
@media (max-width: 860px) {
  .hro { min-height: 82vh; }
  .hro__in { padding: 13vh 0 9vh; }
  .hro__sub { margin-top: 18px; }
  .hro__act { margin-top: 30px; }
  .hro__cta { min-height: 56px; padding: 0 26px; font-size: 1rem; }
  .hro--page { min-height: 38vh; }
}
