/* ============================================================
   LANTERN â€” marketing site v2
   Two skins on one sheet: the teacher page wears the Teach app's
   cream/terracotta tokens; body.tutor flips the same variables to
   the tutor's marigold/forest. Display type is Fraunces â€” warm,
   editorial, and native to the tutor app's serif voice.
   ============================================================ */

:root {
  color-scheme: light;

  --paper: #F3EFE5;
  --paper-2: #f0e8d5;
  --card: #f9f7f0;
  --cream: #f2e9d2;
  --cream-deep: #e8d9b5;

  --ink: #14140f;
  --ink-2: #34322a;
  --muted: #5c5749;
  --line: #e5dabf;
  --line-2: #d5c6a2;
  --white: #ffffff;
  --band-alt: #fcfaf5;

  /* The nav bar's own ground and the type that sits on it. A separate set
     rather than reused page tokens, because every colour in the bar is now
     solving the opposite problem to the ones above: light on dark, not dark
     on light. --ink at 82% would have been the obvious shortcut and it is
     the wrong one â€” the bar is translucent over a cream page, so a mix that
     dark still lifts several shades toward the paper behind it.

     Same gate as the accent tokens: measured, not guessed. Against
     --bar #17160f, --bar-ink is 14.15:1, --bar-muted 8.01, --bar-green 9.10
     and --bar-accent 6.49. The link colours are deliberately not pure white
     â€” #e8e3d6 carries the page's warmth, and at 14:1 there is contrast to
     spare for it. */
  --bar: #17160f;
  --bar-ink: #e8e3d6;
  --bar-muted: #a49d8a;
  --bar-hover: rgba(255, 255, 255, 0.09);
  --bar-green: #8fd3a8;
  --bar-accent: #ff8578;

  /* Three colours carry this page: warm cream ground, near-black text, and one
     red for every highlight â€” the marker trail, the buttons, the links.

     A mid-lightness red is the hard case, and it is worth knowing why this is
     not the vermilion it looks like. #e6332a â€” the Marble red as it reads on
     screen â€” fails BOTH ways: white on it is 4.31:1 and near-black on it is
     4.29:1, so no label colour clears the 4.5 a button needs. It is
     simultaneously too dark for black text and too light for white.

     #d92d20 is one step deeper, still plainly vermilion, and white on it
     measures 4.83. That gate has now rejected three accents in a row: the
     original terracotta at 4.23 and the app's blue at 4.15 failed it too,
     which is why no button on this site was ever compliant.

     The three tokens have different jobs and each was checked against its own
     floor: --accent is fills and rules, --accent-deep is TEXT and the button
     hover fill, --accent-tint is pale grounds. Deep-on-tint was 4.46 under
     terracotta and is 6.26 now. */
  --accent: #E6352E;
  --accent-deep: #a01b12;
  --accent-tint: #f7e0dc;

  /* The affirmative stays its own colour: ticks, the yes list, the ok message.
     This matters more under a red accent than it did under blue, because red
     is also the conventional danger signal â€” so the "yes" marks have to stay
     unmistakably green rather than drifting toward the brand hue.

     --green-deep exists for one reason: green TEXT on the green tint measures
     4.11:1, under the 4.5 that 12â€“14px needs. It was already failing before
     any of this; --green-deep on the same tint is 6.20:1. Negatives use
     --muted and errors use --orange, neither of which depends on the accent. */
  --green: #3f7d54;
  --green-deep: #2f5f40;
  --green-tint: #e2eee2;
  --blue: #4f7fb8;
  /* --blue is a TEXT and tint colour only â€” white on it is 4.15:1, the same
     failure the palette note above records for the app's blue. --blue-deep is
     the fill version, for a solid pill carrying a white label: 5.01:1, which
     lands beside --green's 4.91 so a blue chip weighs the same as the green one
     it replaces rather than reading lighter. */
  --blue-deep: #3a72ad;
  --blue-tint: #e0e9f3;
  --navy: #33445f;
  --navy-tint: #e2e7ef;
  --orange: #d07a2e;
  --orange-tint: #f6e6d3;

  --primary: #23231f;
  --m-bg: #171712;
  --m-ink: #f3efe4;

  --r-xl: 30px;
  --r-lg: 22px;
  --r-md: 16px;
  --r-pill: 999px;

  --sh-1: 0 1px 2px rgba(26, 26, 24, 0.06), 0 8px 24px rgba(26, 26, 24, 0.07);
  --sh-2: 0 2px 8px rgba(26, 26, 24, 0.08), 0 18px 44px rgba(26, 26, 24, 0.13);
  --sh-3: 0 30px 80px rgba(26, 26, 24, 0.16);

  --ease: cubic-bezier(0.2, 0.8, 0.28, 1);
  /* overshoots and settles â€” for things that physically land, like a dropped
     file or a page falling into the fan. Never for fades. */
  --drop: cubic-bezier(0.34, 1.4, 0.44, 1);

  --display: "Fraunces", "Iowan Old Style", Georgia, "Times New Roman", serif;
  /* The hero headline only â€” section headings stay on --display. Deliberately
     the SAME face as body copy, just taken to 800: the headline reads thick
     and plain without introducing a third voice, and the extra weight is the
     only thing marking it as the loudest thing on the page. A display face
     here has to earn its keep against the footage, and the two we tried did
     not â€” Playfair's hairlines went thin over the sky, Fredoka's rounded
     terminals read as a children's app rather than a school's. */
  --display-hero: var(--font);
  --font: "Plus Jakarta Sans", ui-sans-serif, system-ui, -apple-system,
    "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
  --mono: ui-monospace, "SF Mono", "JetBrains Mono", Menlo, Consolas, monospace;

  --wrap: 1180px;
  --noise: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='160' height='160'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.9' numOctaves='2'/%3E%3CfeColorMatrix type='saturate' values='0'/%3E%3CfeComponentTransfer%3E%3CfeFuncA type='linear' slope='0.05'/%3E%3C/feComponentTransfer%3E%3C/filter%3E%3Crect width='160' height='160' filter='url(%23n)'/%3E%3C/svg%3E");
}

body.tutor {
  /* v2 â€” the whole paper family is WARM now. It used to be a cool sage
     (#eef1ec / #e6eae2), inherited from the sage in the mark, and it fought
     everything sitting on it: the app screenshots are warm bone, the ruled
     pages are cream, and the sage bands between them read grey-green by
     comparison â€” cold light on a page about a child's exercise book. These
     are the same values one step around the wheel toward the marigold, so
     the forest ink and green tints still land as they did. Warm ALL of them
     together or the page splits into two temperatures at every band edge. */
  --paper: #f2eee1;
  --paper-2: #ebe4d4;
  /* Paper, NOT #fff. The app this page sells lives on warm bone â€” canvas
     #faf8f2, panels #f9f4ec (sampled from shots/tutor-map.png) â€” and a
     clinical white card sitting next to those screenshots reads as a
     different product. --card is every paper surface here (the ruled
     exercise-book pages included), so it takes the app's register: warm,
     now a shade past the app's own panels so the exercise book reads as
     paper rather than as screen. */
  --card: #fbf6e8;
  --cream: #f0e8d7;
  --cream-deep: #e4d9c1;
  --ink: #14302c;
  --ink-2: #26403a;
  --muted: #56675e;
  --line: #e3dbc8;
  --line-2: #d0c4a9;
  --white: #ffffff;
  /* Was a minty near-white (#f7faf7) â€” same clash as --card, cool where
     the app is warm. A shade under --card so cards still lift off it. */
  --band-alt: #f8f3e6;
  --accent: #cf8517;
  --accent-deep: #935d0d;
  --accent-tint: #f8ecd6;
  --green: #2f6b4f;
  /* the tutor's forest is already dark enough to read as text (5.98:1 on its
     tint), so --green-deep is a small step down rather than the bigger drop
     the teach skin needed â€” it exists here so shared components that reach for
     it don't resolve to nothing on this page */
  --green-deep: #245740;
  --green-tint: #e0ede4;
  --blue: #3b7bd0;
  --blue-tint: #e1ebf7;
  --navy: #274b5e;
  --navy-tint: #e2ecf0;
  --primary: #14302c;
  --m-bg: #0f231d;
  --m-ink: #ece6d6;
  --sh-1: 0 1px 2px rgba(20, 48, 44, 0.05), 0 10px 30px rgba(20, 48, 44, 0.07);
  --sh-2: 0 2px 6px rgba(20, 48, 44, 0.07), 0 20px 46px rgba(20, 48, 44, 0.11);
  --sh-3: 0 30px 80px rgba(20, 48, 44, 0.18);

  /* â”€â”€ exercise-book layer (tutor only) â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€
     The teach page's instrument is the teacher's: a marker on a whiteboard.
     The tutor page's instrument is the child's: a ruled exercise book. These
     tokens are stationery, not brand â€” the brand stays sage/marigold/forest.
     Â· --rule-red   the margin line. Deliberately NOT the teach vermilion
       (#d92d20) and NOT the marigold accent: a softened brick that reads as
       a printed margin rule, quiet enough to sit behind content all day.
     Â· --rule-feint the horizontal feint rules. Printed-blue ruling (#ccd9de)
       is what a real exercise book has, but there is one rule every 28px
       across the whole band, so the ruling â€” not the paper â€” was setting the
       page's temperature, and it set it cold. Warm taupe instead, weighted to
       stay the same whisper: ~1.29:1 on --card, where the blue was ~1.25:1.
       Decorative, so contrast rules don't apply â€” it must whisper.
     Â· --rule-gap   the ruling rhythm. Sections that sit ON the rules set
       their line-height to a multiple of this so text rides the lines.
     Â· --pencil     graphite, for everything the CHILD wrote (Caveat face).
       The app speaks in Fraunces/Jakarta; the child speaks in pencil.
     Â· --write      easing for strokes that are DRAWN (ticks, underlines,
       the rule filling in). Teach's --drop overshoots because its objects
       physically land; nothing bounces on a page, so --write only ever
       decelerates. */
  --rule-red: #cf7361;
  --rule-red-deep: #b25545;
  /* the walkthrough's ribbon bookmark: deeper and browner than the ink reds
     above, because it is dyed cloth lying on the page rather than ink in it â€”
     at --rule-red-deep it read as a very fat pen stroke, not a bookmark */
  --ribbon: #9d3b28;
  --rule-feint: #e0d6c1;
  --rule-gap: 28px;
  --pencil: #44514c;
  --hand: "Caveat", "Segoe Script", "Bradley Hand", cursive;
  --write: cubic-bezier(0.45, 0.05, 0.25, 1);
}

/* ------------------------------------------------------------- base */

* { box-sizing: border-box; }

html {
  scroll-behavior: smooth;
  scroll-padding-top: 96px;
  -webkit-text-size-adjust: 100%;
}

body {
  margin: 0;
  background: var(--paper);
  color: var(--ink);
  font-family: var(--font);
  font-size: 16.5px;
  line-height: 1.62;
  -webkit-font-smoothing: antialiased;
  text-rendering: optimizeLegibility;
  overflow-x: hidden;
}

h1, h2, h3 {
  font-family: var(--display);
  font-weight: 600;
  line-height: 1.06;
  letter-spacing: -0.015em;
  text-wrap: balance;
  margin: 0;
  color: var(--ink);
}
h4 { font-family: var(--font); font-weight: 700; letter-spacing: -0.01em; margin: 0; color: var(--ink); }

p { margin: 0; }
a { color: inherit; }
img { max-width: 100%; display: block; }
::selection { background: var(--accent-tint); color: var(--ink); }
:focus-visible { outline: 2.5px solid var(--accent); outline-offset: 3px; border-radius: 6px; }

.kw { color: var(--accent); font-style: normal; }
.kw.green { color: var(--green); }
.kw.blue { color: var(--blue); }

.wrap { width: 100%; max-width: var(--wrap); margin: 0 auto; padding: 0 28px; }

section { position: relative; }
.sec { padding: 110px 0; }
.sec-tight { padding: 72px 0; }

.eyebrow {
  font-family: var(--mono);
  font-size: 11px;
  font-weight: 700;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: var(--accent-deep);
  margin: 0 0 18px;
  display: flex;
  align-items: center;
  gap: 10px;
}
.eyebrow::before {
  content: "";
  width: 22px; height: 1.5px;
  background: var(--accent);
  display: inline-block;
}
.sec-head.center .eyebrow { justify-content: center; }

.sec-head { max-width: 820px; margin: 0 0 60px; }
.sec-head.center { margin-inline: auto; text-align: center; }
.sec-head h2 { font-size: clamp(32px, 4.6vw, 52px); margin-bottom: 20px; }
.sec-head p { font-size: 18px; color: var(--ink-2); line-height: 1.62; }

/* ---------------------------------------------------------- reveals */

.reveal { opacity: 0; transform: translateY(22px); transition: opacity 0.8s var(--ease), transform 0.8s var(--ease); }
.reveal.in { opacity: 1; transform: none; }
[data-stagger] > * { transition-delay: calc(var(--i, 0) * 90ms); }

/* word-stagger headlines */
[data-split].split-ready .w { display: inline-block; overflow: hidden; vertical-align: bottom; }
[data-split].split-ready .w > span {
  display: inline-block;
  transform: translateY(112%);
  transition: transform 0.75s var(--ease);
  transition-delay: inherit;
}
[data-split].in .w > span { transform: none; }
[data-split].reveal { opacity: 1; transform: none; } /* the words animate, not the block */

/* squiggle underline */
.squig { position: relative; display: inline-block; }
.squig-svg {
  position: absolute; left: -1%; bottom: -0.14em;
  width: 102%; height: 0.26em;
  overflow: visible;
}
.squig-svg path {
  stroke: currentColor;
  stroke-width: 3px;
  stroke-linecap: round;
  vector-effect: non-scaling-stroke;
  stroke-dasharray: 1;
  stroke-dashoffset: 1;
  transition: stroke-dashoffset 1s var(--ease) 0.55s;
}
.in .squig-svg path { stroke-dashoffset: 0; }

/* grain + glow blobs */
.grain::after {
  content: "";
  position: absolute; inset: 0;
  background-image: var(--noise);
  pointer-events: none;
}
.blob {
  position: absolute;
  border-radius: 50%;
  filter: blur(70px);
  pointer-events: none;
  opacity: 0.65;
  animation: drift 18s ease-in-out infinite alternate;
}
@keyframes drift {
  from { transform: translate(0, 0) scale(1); }
  to { transform: translate(40px, -30px) scale(1.12); }
}

/* ------------------------------------------------------------- nav */

/* A solid dark strip, not a tinted pane over the page. The translucent version
   this replaces leaned on backdrop-filter to stay legible, and that filter goes
   with it: it cost a repaint of everything behind the bar on every scroll
   frame, and buys nothing once the surface is opaque.

   Opaque also means the bar stops borrowing the colour of whatever it is
   passing over, which is the point of the look â€” one constant dark rule at the
   top of the page with the red pill as the only bright thing in it. */
.nav {
  position: sticky; top: 0; z-index: 80;
  background: var(--bar);
  border-bottom: 1px solid transparent;
  transition: border-color 0.25s var(--ease);
}
/* The seam only has to show against the PAGE, and against a dark bar the old
   cream --line was the brighter half of the join â€” it read as a stripe under
   the bar rather than an edge to it. Shadow, not highlight. */
.nav.stuck { border-bottom-color: rgba(0, 0, 0, 0.55); }

/* Hidden over the hero, dropped in past it. The hero is one picture and one
   sentence, and a chrome strip across the top of it is what makes that read as
   a website rather than a view; past the hero the reader is travelling and
   wants the way out in reach.

   Gated on html.nav-hides, which motion.js Â§9 adds only once it is actually in
   a position to reveal the bar again. That direction matters: the bar's default
   is visible, and hiding is the enhancement. With JS off, GSAP unreachable, or
   reduced motion asked for, Â§9 never runs, the class is never added, and the
   navigation simply stays where it is â€” instead of being stranded off-screen
   with nothing left to bring it back.

   Lifted by its own height, so it clears itself whatever the row measures.
   pointer-events guards the links while parked; visibility keeps them out of
   the tab order and off the screen reader, delayed on the way out so the bar
   does not blink away before the slide finishes. */
html.nav-hides .nav {
  transform: translateY(-100%);
  visibility: hidden;
  pointer-events: none;
  transition: border-color 0.25s var(--ease),
              transform 0.34s var(--ease),
              visibility 0s linear 0.34s;
}
/* A sticky header still occupies its row while hidden, which would leave a
   band of empty paper above the hero and give the trick away. Taking it out of
   flow lets the picture run to the top edge of the window.

   But only once the bar has finished leaving. `absolute` re-anchors it to the
   top of the DOCUMENT, so applying it the instant .revealed comes off teleports
   the bar a whole scroll-height upward â€” it blinks out on the spot and the
   slide is never drawn. So the swap waits for .parked, which motion.js sets on
   transitionend: sticky for the length of the pull-up, absolute once it is gone
   and only the hero's top edge still cares.

   One exception, and motion.js Â§9 marks it: reaching scroll 0 while the bar is
   still sliding. There the wait is what does the damage â€” a sticky bar at the
   document top holds its own 74px row above the hero, so the picture sits low
   under a band of bar colour until transitionend snaps it back. So the top
   parks on the spot, and .cut drops the leftover slide with it: the bar is
   already off-screen and out of flow, and finishing a 0.34s travel nobody can
   see only leaves it free to peek back over the hero's top edge. */
html.nav-hides .nav.parked { position: absolute; left: 0; right: 0; }
html.nav-hides .nav.parked.cut { transition: none; }
html.nav-hides .nav.revealed {
  transform: none;
  visibility: visible;
  pointer-events: auto;
  transition: border-color 0.25s var(--ease),
              transform 0.34s var(--ease),
              visibility 0s linear 0s;
}
.nav-in { display: flex; align-items: center; gap: 22px; height: 74px; }

.brand { display: flex; align-items: center; gap: 11px; text-decoration: none; flex: none; margin-right: auto; }
.brand svg { width: 30px; height: 30px; flex: none; }
.brand-txt { display: flex; flex-direction: column; line-height: 1.1; }
/* The wordmark inherits --ink from the page by default, which is invisible on
   the dark bar â€” it has to be told the bar's own ink. */
.brand-txt b {
  font-family: var(--display); font-size: 18px; font-weight: 600;
  letter-spacing: -0.01em; color: var(--bar-ink);
}
.brand-txt span {
  font-family: var(--mono); font-size: 9.5px; font-weight: 700;
  letter-spacing: 0.15em; text-transform: uppercase; color: var(--bar-muted);
}

.nav-links { display: flex; align-items: center; gap: 4px; }
.nav-links a {
  text-decoration: none; font-size: 14.5px; font-weight: 500; color: var(--bar-ink);
  padding: 8px 13px; border-radius: var(--r-pill);
  transition: background 0.2s var(--ease), color 0.2s var(--ease);
  white-space: nowrap;
}
/* Hover lifts OFF the bar rather than tinting toward the page. A white wash at
   9% works on any bar colour; the old --paper-2 was a cream chip that would now
   flash light behind the pointer. */
.nav-links a:hover { background: var(--bar-hover); color: var(--white); }
/* --accent-deep is the darkened red picked for text on cream, and on the bar it
   is nearly invisible. This is the same link, lightened past the ground instead
   of darkened past the paper. */
.nav-links a.cross { color: var(--bar-accent); }
.nav-links a.cross:hover { background: rgba(255, 133, 120, 0.15); color: var(--bar-accent); }
/* The way back in for an existing teacher. A text link, not a second pill: two
   filled buttons side by side make the reader choose between them, and signing
   in is not competing with booking a demo â€” it is the door for people who have
   already decided. Built on .cross's shape so it sits in the bar like the other
   links, in green because that is the colour the rest of this page uses for the
   affirmative. On the dark bar that inverts: --green-deep was chosen to be dark
   enough for cream, so it goes the wrong way here â€” --bar-green is the same
   idea taken light instead (9.10:1 on the bar). */
.nav-links a.auth { color: var(--bar-green); font-weight: 600; }
.nav-links a.auth:hover { background: rgba(143, 211, 168, 0.14); color: var(--bar-green); }

/* ----------------------------------------------------------- buttons */

.btn {
  display: inline-flex; align-items: center; justify-content: center; gap: 9px;
  font-family: inherit; font-size: 15px; font-weight: 600; letter-spacing: -0.005em;
  text-decoration: none; padding: 13px 24px;
  border-radius: var(--r-pill); border: 1px solid transparent;
  cursor: pointer; white-space: nowrap;
  transition: transform 0.2s var(--ease), box-shadow 0.2s var(--ease),
    background 0.2s var(--ease), border-color 0.2s var(--ease);
}
.btn:active { transform: translateY(1px) !important; }
.btn-primary { background: var(--primary); color: #fdfcf8; box-shadow: var(--sh-1); }
.btn-primary:hover { box-shadow: var(--sh-2); }
.btn-ghost { background: transparent; color: var(--ink); border-color: var(--line-2); }
.btn-ghost:hover { background: var(--card); border-color: var(--ink-2); }
.btn-accent { background: var(--accent); color: #fff; box-shadow: var(--sh-1); }
.btn-accent:hover { background: var(--accent-deep); box-shadow: var(--sh-2); }
/* The way back in for a teacher who already has an account â€” it sits directly
   beside "Book a demo", so it is defined against that button, not on its own.

   It was a solid green pill of exactly the same weight. Two problems, and the
   label contrast (4.91:1 white on --green) was never one of them:

   - Filled green vs filled red measure 1.02:1 AGAINST EACH OTHER â€” all but
     identical in lightness, separated by hue alone. That is the red/green
     pair specifically, so under deuteranopia the two pills fall to 2.82:1 and
     read as one row of matching buttons: a returning teacher and a new visitor
     are shown the same thing twice.
   - Equal weight IS competing for the click. A secondary action should be
     quieter than the primary, not the same size in a different colour.

   Outlined instead: green keeps the identity in the label and the border, the
   red pill is unambiguously the one filled thing, and the difference survives
   as weight rather than hue. --green-deep on paper is 6.46:1 (--green itself
   is 4.28, under the floor for a 14px label), and hover fills to the solid
   green it used to be so the affordance still lands. */
.btn-green {
  background: color-mix(in srgb, var(--paper) 82%, transparent);
  color: var(--green-deep);
  border-color: color-mix(in srgb, var(--green) 42%, transparent);
}
.btn-green:hover {
  background: var(--green); color: #fff;
  border-color: transparent; box-shadow: var(--sh-1);
}
.btn-sm { padding: 9px 17px; font-size: 14px; }
.btn .arw { transition: transform 0.22s var(--ease); }
.btn:hover .arw { transform: translateX(3px); }

/* -------------------------------------------------------------- hero */

.hero { padding: 84px 0 0; overflow: hidden; }
.hero .blob.b1 {
  width: 720px; height: 720px; top: -320px; right: -200px;
  background: radial-gradient(circle, var(--accent-tint) 0%, transparent 65%);
}
.hero .blob.b2 {
  width: 560px; height: 560px; top: 120px; left: -260px;
  background: radial-gradient(circle, var(--blue-tint) 0%, transparent 65%);
  animation-delay: -8s;
}
.hero-in { position: relative; max-width: 920px; margin: 0 auto; text-align: center; }

.pill {
  display: inline-flex; align-items: center; gap: 9px;
  font-size: 13px; font-weight: 600; color: var(--ink-2);
  background: var(--card); border: 1px solid var(--line);
  padding: 7px 16px 7px 10px; border-radius: var(--r-pill);
  box-shadow: var(--sh-1); margin-bottom: 30px;
}
.pill .dot { width: 7px; height: 7px; border-radius: 50%; background: var(--green); flex: none; margin-left: 2px; }

.hero h1 {
  font-size: clamp(42px, 6.6vw, 80px);
  line-height: 1.02;
  letter-spacing: -0.022em;
  margin-bottom: 26px;
}
.hero-lede {
  font-size: clamp(17px, 2vw, 20px);
  color: var(--ink-2);
  max-width: 680px;
  margin: 0 auto;
  line-height: 1.6;
}
.hero-cta { display: flex; flex-wrap: wrap; gap: 13px; margin-top: 36px; justify-content: center; }
.hero-tags { display: flex; flex-wrap: wrap; gap: 10px 26px; margin-top: 32px; justify-content: center; }
.hero-tags span {
  display: inline-flex; align-items: center; gap: 8px;
  font-size: 14px; color: var(--muted); font-weight: 500;
}
.hero-tags svg { width: 16px; height: 16px; flex: none; color: var(--green); }

.hero-vis { position: relative; margin-top: 64px; padding-bottom: 34px; }

/* -------------------------------------------------------- screenshots */

.tiltwrap { perspective: 1400px; }
.shot {
  position: relative;
  border-radius: var(--r-lg);
  background: var(--card);
  border: 1px solid var(--line-2);
  box-shadow: var(--sh-2);
  overflow: hidden;
  transition: transform 0.35s var(--ease), box-shadow 0.35s var(--ease);
  transform-style: preserve-3d;
  will-change: transform;
}
.shot.raise { box-shadow: var(--sh-3); }
.shot .chrome {
  display: flex; align-items: center; gap: 8px;
  height: 38px; padding: 0 14px;
  background: var(--cream); border-bottom: 1px solid var(--line);
}
.shot .chrome i { width: 10px; height: 10px; border-radius: 50%; background: var(--line-2); flex: none; }
.shot .chrome .url {
  flex: 1; height: 21px; margin-left: 8px;
  border-radius: var(--r-pill);
  background: var(--paper); border: 1px solid var(--line);
  display: flex; align-items: center; padding: 0 11px;
  font-family: var(--mono); font-size: 10.5px; color: var(--muted);
  overflow: hidden; white-space: nowrap;
}
.shot .frame { position: relative; aspect-ratio: var(--ar, 16 / 10); background: var(--paper-2); }
.shot .frame img {
  position: absolute; inset: 0;
  width: 100%; height: 100%;
  object-fit: cover; object-position: top center;
  background: var(--paper);
}
.shot-cap { margin: 14px 2px 0; font-size: 13.5px; color: var(--muted); line-height: 1.5; }
.shot-cap b { color: var(--ink-2); font-weight: 600; }

/* floating UI chips */
.chip {
  position: absolute; z-index: 6;
  display: inline-flex; align-items: center; gap: 8px;
  padding: 10px 15px; border-radius: var(--r-pill);
  background: color-mix(in srgb, var(--card) 90%, transparent);
  backdrop-filter: blur(10px);
  -webkit-backdrop-filter: blur(10px);
  border: 1px solid var(--line);
  box-shadow: var(--sh-2);
  font-size: 13.5px; font-weight: 600; color: var(--ink);
  white-space: nowrap;
  animation: bob var(--d, 6s) ease-in-out var(--dd, 0s) infinite;
}
.chip .code {
  font-family: var(--mono); font-size: 11.5px; font-weight: 700;
  color: var(--green); background: var(--green-tint);
  padding: 3px 8px; border-radius: 6px;
}
.chip svg { width: 16px; height: 16px; flex: none; color: var(--accent); }
.chip small { font-weight: 500; color: var(--muted); font-size: 12.5px; }
@keyframes bob {
  0%, 100% { transform: translateY(0); }
  50% { transform: translateY(-9px); }
}

/* ------------------------------------------------------------ marquee */

.marquee {
  overflow: hidden;
  border-block: 1px solid var(--line);
  background: var(--card);
  padding: 15px 0;
}
.mq-track { display: flex; width: max-content; animation: mq 46s linear infinite; }
.marquee:hover .mq-track { animation-play-state: paused; }
.mq-half { display: flex; align-items: center; gap: 44px; padding-right: 44px; }
.mq-half span {
  display: inline-flex; align-items: center; gap: 10px;
  font-size: 14px; font-weight: 600; color: var(--ink-2); white-space: nowrap;
}
.mq-half span i {
  font-style: normal; font-family: var(--mono); font-size: 11px; font-weight: 700;
  color: var(--accent-deep); background: var(--accent-tint);
  padding: 3px 8px; border-radius: 6px;
}
.mq-half span::after { content: "âœ¦"; color: var(--line-2); margin-left: 34px; font-size: 11px; }
@keyframes mq { to { transform: translateX(-50%); } }

/* -------------------------------------------------------------- stats */

.stats { display: grid; grid-template-columns: repeat(4, 1fr); gap: 0; }
.stat { padding: 10px 34px; border-left: 1px solid var(--line-2); }
.stat:first-child { border-left: 0; padding-left: 0; }
.stat b {
  display: block;
  font-family: var(--display); font-weight: 600;
  font-size: clamp(44px, 5vw, 72px);
  letter-spacing: -0.03em; line-height: 1;
  color: var(--ink); font-variant-numeric: tabular-nums;
  margin-bottom: 10px;
}
.stat b em { font-style: normal; color: var(--accent); }
.stat > span { font-size: 14px; color: var(--muted); line-height: 1.5; display: block; max-width: 24ch; }

/* ------------------------------------------------------------ journey */

.j-grid {
  display: grid;
  grid-template-columns: minmax(0, 0.92fr) minmax(0, 1.08fr);
  gap: 64px;
  align-items: start;
}
.j-list { padding: 2vh 0 16vh; }
.j-item {
  padding: 7.5vh 0;
  border-top: 1px dashed var(--line-2);
  opacity: 0.5;
  transition: opacity 0.45s var(--ease);
}
.j-item:first-child { border-top: 0; }
.j-item.on { opacity: 1; }
.j-tag {
  display: inline-flex; align-items: center; gap: 10px;
  font-family: var(--mono); font-size: 11px; font-weight: 700;
  letter-spacing: 0.14em; text-transform: uppercase;
  color: var(--accent-deep); margin-bottom: 14px;
}
.j-tag b {
  display: grid; place-items: center;
  width: 26px; height: 26px; border-radius: 50%;
  background: var(--accent); color: #fff;
  font-family: var(--mono); font-size: 12px;
}
.j-item h3 { font-size: clamp(24px, 2.6vw, 32px); margin-bottom: 12px; }
.j-item > p { font-size: 16px; color: var(--ink-2); line-height: 1.6; max-width: 46ch; }
.j-extra { margin-top: 18px; }

.typebox {
  display: flex; align-items: center; gap: 10px;
  background: var(--card); border: 1px solid var(--line-2);
  border-radius: var(--r-md); padding: 12px 16px;
  font-size: 14px; color: var(--ink-2);
  box-shadow: var(--sh-1); max-width: 420px;
}
.typebox svg { width: 16px; height: 16px; color: var(--accent); flex: none; }
.typebox .type::after {
  content: ""; display: inline-block;
  width: 2px; height: 1em; background: var(--accent);
  margin-left: 2px; vertical-align: -0.15em;
  animation: caret 1s steps(1) infinite;
}
@keyframes caret { 50% { opacity: 0; } }

.j-visual { position: sticky; top: calc(50vh - 232px); }
.j-shots { position: relative; aspect-ratio: 16 / 10; background: var(--paper-2); }
.j-shots img {
  position: absolute; inset: 0; width: 100%; height: 100%;
  object-fit: cover; object-position: top center;
  opacity: 0; transform: scale(1.03);
  transition: opacity 0.5s var(--ease), transform 0.7s var(--ease);
}
.j-shots img.on { opacity: 1; transform: none; }
.j-mshot { display: none; }

/* -------------------------------------------------------------- bento */

.bento { display: grid; grid-template-columns: repeat(3, 1fr); gap: 18px; }
.tile {
  position: relative; display: flex; flex-direction: column;
  border-radius: var(--r-lg); border: 1px solid var(--line-2);
  background: var(--card); overflow: hidden;
  box-shadow: var(--sh-1);
  /* `translate`, not `transform`: this element carries data-fade, so GSAP
     writes `transform` on reveal â€” a CSS transition on the same property
     chases GSAP frame by frame and strands the element partway. */
  transition: translate 0.3s var(--ease), box-shadow 0.3s var(--ease);
}
.tile:hover { translate: 0 -4px; box-shadow: var(--sh-2); }
.tile.wide { grid-column: span 2; }
.tile.tall { grid-row: span 2; }
.t-img { position: relative; overflow: hidden; background: var(--cream); border-bottom: 1px solid var(--line); }
.t-img img { width: 100%; height: 100%; object-fit: cover; object-position: top center; transition: transform 0.55s var(--ease); }
.tile:hover .t-img img { transform: scale(1.03); }
.tile.tall .t-img { flex: 1; min-height: 0; }
.t-txt { padding: 18px 20px 20px; }
.t-txt h4 { font-size: 16.5px; margin-bottom: 6px; }
.t-txt p { font-size: 13.5px; color: var(--muted); line-height: 1.55; }

/* hand-built micro-UI inside tiles */
.t-ui { flex: 1; padding: 20px; display: flex; flex-direction: column; gap: 9px; justify-content: center; background: var(--cream); border-bottom: 1px solid var(--line); }
.ui-chip {
  display: inline-flex; align-items: center; gap: 9px;
  align-self: flex-start;
  background: var(--card); border: 1px solid var(--line-2);
  border-radius: var(--r-pill); padding: 8px 14px;
  font-size: 13px; font-weight: 600; color: var(--ink-2);
  box-shadow: var(--sh-1);
}
.ui-chip svg { width: 14px; height: 14px; color: var(--accent); flex: none; }
.ui-row {
  display: flex; align-items: center; justify-content: space-between; gap: 12px;
  background: var(--card); border: 1px solid var(--line-2);
  border-radius: var(--r-md); padding: 10px 14px;
  font-size: 13px; color: var(--ink-2);
}
.ui-row b { font-weight: 600; color: var(--ink); }
/* Two different things wore the same red here: "current" is a STATE and
   "restore" is a safe action, and red is also the page's warning colour. Split
   them â€” green chip for the live state (the sheet already reserves green for
   "yes"), plain ink link for the action. Keeps red scarce enough to mean
   something when a CTA uses it.

   The base .act stays neutral because the tutor page uses the same class for
   outcome labels ("full credit", "tracked gently") â€” those are descriptions,
   not actions, and an underline there would promise a link that isn't. Only
   .act.do, which is opt-in, reads as clickable. */
.ui-row .act { font-size: 12px; font-weight: 700; color: var(--muted); }
.ui-row .act.do {
  color: var(--ink);
  text-decoration: underline; text-underline-offset: 2px;
  text-decoration-color: var(--line-2);
}
.ui-row .act.now {
  color: var(--green-deep); background: var(--green-tint);
  border-radius: var(--r-pill); padding: 3px 9px;
}
.ui-code {
  align-self: center; display: flex; align-items: center; gap: 14px;
}
.ui-code b {
  font-family: var(--mono); font-size: 22px; letter-spacing: 0.3em;
  background: var(--card); border: 1px solid var(--line-2);
  padding: 12px 18px 12px 24px; border-radius: var(--r-md);
}
.ui-swatch { display: flex; align-items: center; gap: 12px; background: var(--card); border: 1px solid var(--line-2); border-radius: var(--r-md); padding: 10px 14px; font-size: 13px; font-weight: 600; }
.ui-swatch i { width: 26px; height: 26px; border-radius: 8px; flex: none; border: 1px solid rgba(0,0,0,.08); }

/* ------------------------------------------------------- link section */

/* The base band is shared with the tutor page's #grownups section, which
   wears it on that skin's light sage. Everything the teach page's night
   version changes hangs off `.night`, so the two can't drift into each
   other â€” the tutor page carries no such class. */
.link-sec {
  background: linear-gradient(180deg, var(--cream) 0%, var(--cream-deep) 100%);
  border-block: 1px solid var(--line-2);
}

/* â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•
   .night â€” the band goes dark. Two reasons, neither of them mood: this is
   the only section on the page that happens in the evening at home, and it
   is the only one whose subject is a lantern. The cream version put four
   plates of identical weight on a cream ground and nothing was the hero;
   here exactly one object is lit â€” the report opened in the morning.

   The palette split inside this band is load-bearing and must not be
   crossed. MARIGOLD is the tutor / home side. GREEN is the school's own band
   ladder (Secure, Developing) and the affirmative marks. A green wash on the
   home half, or a gold tick in the crosses list, would spend a signal the
   report legend depends on three sections later.
   â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â• */

.link-sec.night {
  /* the lift at the top is where the section head sits, so the headline gets
     the lightest ground and the corners fall away under the panels */
  background: radial-gradient(125% 92% at 50% 4%, #1B2A33 0%, #101A20 44%, #080D11 100%);
  border-block: 0;
  color: #E9EFEA;
  overflow: hidden;
  isolation: isolate;
}
.link-sky {
  position: absolute; inset: 0; z-index: 0;
  width: 100%; height: 100%;
  pointer-events: none;
}
.link-sec.night > .wrap { position: relative; z-index: 1; }

/* head â€” the piece the cream version never had. Every other section on the
   page opens with an eyebrow, a headline and a lede; this one opened
   mid-column and read as a continuation of the gallery above it. */
.link-sec.night .eyebrow { color: #E0A73C; }
.link-sec.night .eyebrow::before { background: #E0A73C; }
.link-sec.night .tag {
  background: none; color: rgba(233,239,234,.72);
  border: 1px solid rgba(233,239,234,.26);
}
.link-sec.night .sec-head { margin-bottom: 46px; }
.link-sec.night .sec-head h2 { color: #F6F1E5; font-size: clamp(31px, 4.2vw, 48px); }
/* sage, not the dusk band's warm gold: gold already means "home" in here */
.link-sec.night .sec-head h2 em { font-style: italic; font-weight: 500; color: #A8CABE; }
.link-sec.night .sec-head p { color: rgba(243,239,228,.8); font-size: 17px; }

/* â”€â”€ the crossing â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€
   Not one plate with three cells: the two sides are lit differently on
   purpose. Home is glass at 19:42, school is paper at 08:05. */
.bridge {
  display: grid;
  grid-template-columns: 1fr 200px 1fr;
  /* start, not centre: the two timestamps are the section's whole argument, so
     they have to sit on one line. Centring floated them apart by the height
     difference between the conversation and the report. */
  align-items: start;
  gap: 0;
}
.bridge-mid { align-self: center; }
.bridge-side { min-width: 0; }
.bridge-side .who {
  display: flex; align-items: center; gap: 9px;
  font-family: var(--mono); font-size: 10.5px; font-weight: 700;
  letter-spacing: 0.14em; text-transform: uppercase;
  margin: 0 0 13px;
}
.bridge-side .who svg { width: 15px; height: 15px; flex: none; }
.bs-home .who { color: #E0A73C; }
.bs-class .who { color: #A8CABE; }

.chat-panel {
  background: rgba(10, 19, 24, .58);
  border: 1px solid rgba(224, 167, 60, .22);
  border-radius: var(--r-lg);
  padding: 20px 22px 18px;
}
.chat-panel h4 {
  font-family: var(--display); font-weight: 600; font-size: 19px;
  letter-spacing: -.012em; margin: 0 0 3px; color: #F3EFE4;
}
.chat-panel .bs-sub { margin: 0 0 14px; font-size: 12.5px; color: rgba(233,239,234,.6); }

.chat-panel .chat { display: grid; gap: 8px; }
.chat-panel .chat span {
  font-size: 13.5px; line-height: 1.5;
  border-radius: 14px; padding: 9px 13px;
}
.chat-panel .c-bot { background: rgba(255,255,255,.07); color: rgba(238,244,237,.9); max-width: 92%; }
/* the child's turn carries the lantern's hue â€” it is the tutor's side of the
   page, and it keeps the two voices apart without a name label */
.chat-panel .c-kid {
  background: rgba(224,167,60,.15); color: #F3DFB4;
  justify-self: end; text-align: right; max-width: 86%;
}
.chat-done {
  display: flex; align-items: center; gap: 8px;
  margin: 13px 0 0;
  font-family: var(--mono); font-size: 9.5px; font-weight: 700;
  letter-spacing: .13em; text-transform: uppercase; color: #9DC9AE;
}
.chat-done svg { width: 14px; height: 14px; flex: none; }

/* the lit object. Rimmed rather than shadowed â€” a plain drop shadow on a
   near-black ground is invisible, so the card has to carry its own light.
   The colour reset is load-bearing: the card is a light-ground component
   dropped onto a dark band, and anything inside it without an explicit
   colour (the learner's name, for one) otherwise inherits the band's cream
   and disappears into the paper. */
.bs-class .xfer { color: var(--ink); }
.bs-class .xfer {
  box-shadow:
    0 0 0 1px rgba(224,167,60,.26),
    0 26px 64px rgba(0,0,0,.6),
    0 0 120px rgba(224,167,60,.13);
  border-color: transparent;
}

.bridge-mid {
  position: relative;
  display: flex; flex-direction: column; align-items: center; justify-content: center;
  gap: 12px; padding: 26px 18px;
  text-align: center;
}
.bridge-mid::before, .bridge-mid::after {
  content: ""; position: absolute; left: 14px; right: 14px; height: 2px;
  background: repeating-linear-gradient(90deg, rgba(224,167,60,.62) 0 8px, transparent 8px 17px);
  animation: flow 1.1s linear infinite;
}
.bridge-mid::before { top: 22px; }
.bridge-mid::after { bottom: 22px; }
@keyframes flow { to { background-position-x: 17px; } }
.bridge-mid .arrow {
  /* flex: none â€” stacked, the spine is a flex ROW and the disc was being
     squeezed into an oval by the caption beside it */
  width: 46px; height: 46px; flex: none; border-radius: 50%;
  display: grid; place-items: center;
  background: rgba(255,255,255,.06);
  border: 1px solid rgba(224,167,60,.34);
  color: #E0A73C;
}
.bridge-mid .arrow svg { width: 19px; height: 19px; }
.bridge-mid b {
  display: block; font-family: var(--mono); font-size: 10px; font-weight: 700;
  letter-spacing: 0.12em; text-transform: uppercase; color: #EFEFE6;
  line-height: 1.5;
}
.bridge-mid span { display: block; font-size: 11.5px; color: rgba(233,239,234,.6); line-height: 1.45; text-wrap: balance; }

/* â”€â”€ the schedule strip â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€
   The base pair of cards is shared with the tutor page, which wears them on
   a light ground â€” so the night treatment below is scoped, not a rewrite. */
.crosses { display: grid; grid-template-columns: 1fr 1fr; gap: 14px; margin-top: 18px; }
.cross-card { background: var(--card); border: 1px solid var(--line-2); border-radius: var(--r-lg); padding: 20px 22px 18px; }
.cross-card h4 { font-size: 15.5px; margin: 0 0 13px; display: flex; align-items: center; gap: 9px; }
.cross-card h4 svg { width: 17px; height: 17px; flex: none; }
.cross-card.yes h4 { color: var(--green); }
.cross-card.no h4 { color: var(--muted); }
.cross-card ul { list-style: none; margin: 0; padding: 0; display: flex; flex-wrap: wrap; gap: 8px; }
.cross-card li { font-size: 13.5px; padding: 6px 13px; border-radius: var(--r-pill); line-height: 1.4; }
.cross-card.yes li { background: var(--green-tint); color: var(--green); font-weight: 600; }
.cross-card.no li {
  background: var(--paper-2); color: var(--muted);
  text-decoration: line-through;
  text-decoration-color: color-mix(in srgb, var(--muted) 45%, transparent);
}

/* On the night band it becomes ONE plate ruled down the middle rather than
   two competing cards, and the withheld column is a plain dimmed list â€”
   struck-through chips read as broken UI rather than as a promise, and half
   the section's area was going to them. */
.link-sec.night .crosses {
  gap: 0; margin-top: 26px;
  background: rgba(255,255,255,.032);
  border: 1px solid rgba(233,239,234,.13);
  border-radius: var(--r-lg);
  overflow: hidden;
}
.link-sec.night .cross-card {
  background: none; border: 0; border-radius: 0;
  padding: 19px 24px 21px;
}
.link-sec.night .cross-card + .cross-card { border-left: 1px solid rgba(233,239,234,.13); }
.link-sec.night .cross-card h4 {
  font-family: var(--mono); font-size: 11px; font-weight: 700;
  letter-spacing: .13em; text-transform: uppercase;
}
.link-sec.night .cross-card h4 svg { width: 15px; height: 15px; }
.link-sec.night .cross-card.yes h4 { color: #9DC9AE; }
.link-sec.night .cross-card.no h4 { color: rgba(233,239,234,.55); }
.link-sec.night .cross-card ul { display: grid; gap: 7px; }
.link-sec.night .cross-card li {
  padding: 0; background: none; border-radius: 0;
  font-size: 13.5px; font-weight: 400; line-height: 1.45;
  text-decoration: none;
}
.link-sec.night .cross-card.yes li { color: rgba(240,244,238,.9); }
.link-sec.night .cross-card.no li { color: rgba(233,239,234,.55); }
/* nbsp: a trailing space inside `content` collapses and welds the marker to
   the first word */
.link-sec.night .cross-card.yes li::before { content: "+\00a0"; font-family: var(--mono); font-weight: 700; color: #7FB894; }
.link-sec.night .cross-card.no li::before { content: "\2014\00a0"; font-family: var(--mono); font-weight: 700; color: rgba(233,239,234,.32); }

/* the exit is a line of text, not a slab. The cream version ended on a
   near-black CTA bar, which made the section's visual peak its own exit â€”
   and on this ground a dark bar would simply vanish. */
.link-out {
  display: flex; align-items: center; justify-content: center; gap: 11px;
  margin: 26px 0 0;
}
.link-out .lamp { width: 19px; height: 19px; color: #E0A73C; flex: none; }
.link-out a {
  display: inline-flex; align-items: center; gap: 9px;
  font-size: 15px; font-weight: 600; color: #F6F1E5;
  text-decoration: none;
  border-bottom: 1.5px solid #E0A73C;
  padding-bottom: 3px;
}
.link-out a svg { width: 15px; height: 15px; transition: transform .22s var(--ease); }
.link-out a:hover svg { transform: translateX(3px); }

.cross-link {
  display: flex; align-items: center; gap: 26px;
  margin-top: 22px; padding: 30px 34px;
  border-radius: var(--r-xl);
  background: var(--m-bg); color: var(--m-ink);
  box-shadow: var(--sh-2); text-decoration: none;
  position: relative; overflow: hidden;
  /* `translate`, not `transform`: this element carries data-fade, so GSAP
     writes `transform` on reveal â€” a CSS transition on the same property
     chases GSAP frame by frame and strands the element partway. */
  transition: translate 0.28s var(--ease), box-shadow 0.28s var(--ease);
}
.cross-link::before {
  content: ""; position: absolute; inset: 0;
  background: radial-gradient(420px circle at 85% 20%, color-mix(in srgb, var(--accent) 26%, transparent), transparent 70%);
  opacity: 0.7; transition: opacity 0.3s;
}
.cross-link:hover { translate: 0 -3px; box-shadow: var(--sh-3); }
.cross-link:hover::before { opacity: 1; }
.cross-link > * { position: relative; }
.cross-link .cl-ico {
  width: 54px; height: 54px; border-radius: 16px; flex: none;
  display: grid; place-items: center;
  background: rgba(255, 255, 255, 0.08);
  border: 1px solid rgba(255, 255, 255, 0.14);
}
.cross-link .cl-ico svg { width: 30px; height: 30px; }
.cross-link .cl-txt { flex: 1; min-width: 0; }
.cross-link .cl-txt b { display: block; font-family: var(--display); font-size: 22px; font-weight: 600; letter-spacing: -0.01em; margin-bottom: 5px; }
.cross-link .cl-txt span { font-size: 14.5px; color: color-mix(in srgb, var(--m-ink) 66%, transparent); line-height: 1.5; }
.cross-link .cl-go {
  flex: none; display: inline-flex; align-items: center; gap: 8px;
  font-size: 14.5px; font-weight: 600; color: var(--m-ink);
  border: 1px solid rgba(255, 255, 255, 0.22);
  padding: 11px 20px; border-radius: var(--r-pill);
  transition: background 0.25s;
}
.cross-link:hover .cl-go { background: rgba(255, 255, 255, 0.1); }
.cross-link .cl-go svg { width: 15px; height: 15px; transition: transform 0.22s var(--ease); }
.cross-link:hover .cl-go svg { transform: translateX(3px); }

/* ---------------------------------------------------------- manifesto */

.manifesto {
  background: var(--m-bg); color: var(--m-ink);
  padding: 92px 0; overflow: hidden;
}
.manifesto::before {
  content: ""; position: absolute; inset: 0;
  background: radial-gradient(640px circle at var(--mx, 72%) var(--my, 24%),
    color-mix(in srgb, var(--accent) 17%, transparent), transparent 62%);
  pointer-events: none;
}
/* The ledger is 860 inside an 1180 wrap, so left-flush it sat a full column
   left of centre while the bands either side of it â€” the coverage cards
   above, the Link band below â€” are centred. Both the eyebrow and the ledger
   ride the same centred 860 column so the label still sits over the rule it
   names; the type inside the cells stays flush left, because a ledger is
   read down its two edges. */
.manifesto .eyebrow {
  color: color-mix(in srgb, var(--accent) 78%, #fff);
  max-width: 860px; margin-inline: auto;
}
.manifesto .eyebrow::before { background: currentColor; }
/* v2 â€” THE LEDGER. The three trade-offs were three full-measure headlines
   at 64px, and the pairing between the thing we refused and the thing we
   built was carried entirely by a strike-through. Two problems came out of
   that. The break point was a wrap decision: with no max-width and the h2's
   inherited `text-wrap: balance`, line two landed as "Reward every tap.
   Reward / time in real books", so the refusal and its answer sat on
   different lines with nothing lining them up â€” the parallel structure the
   whole device runs on was invisible. And the four things we did NOT build
   were set at the same 64px as the three we did, which is the loudest way
   to say the quiet half.

   Two columns fix both structurally: no wrap can break a grid row, and the
   axis gets NAMED once in the header instead of being inferred three times
   from a decoration. The type steps down deliberately â€” this band is an
   argument being laid out, not a statement being shouted â€” and the whole
   section now costs about a third of the height it did, which is what let
   the padding come down to 92px as well. */
.mledger {
  display: grid;
  grid-template-columns: minmax(0, 0.78fr) minmax(0, 1.22fr);
  /* The measure has to come down WITH the type. At the full 1180 the rules
     ran a third of their length past the last word in both columns and the
     block read as two stranded columns rather than one ledger â€” the ink,
     not the wrap, sets how long a rule is allowed to be. */
  max-width: 860px;
  margin-inline: auto;
}
.mledger > * { padding: 10px 0; }
.mledger > .l { padding-right: clamp(14px, 2.4vw, 32px); }
.mledger > .r {
  padding-left: clamp(16px, 2.6vw, 36px);
  border-left: 1px solid color-mix(in srgb, var(--m-ink) 15%, transparent);
}
.mledger > .row { border-top: 1px solid color-mix(in srgb, var(--m-ink) 12%, transparent); }
/* The two columns are set in different faces at different sizes, so equal
   padding staggers their first baselines. The left cell is lifted to meet
   the serif's, and the lift tracks the same vw the serif scales on. */
.mledger > .l.row { padding-top: calc(10px + min(0.6vw, 8px)); }

.ml-hd {
  padding-top: 0; padding-bottom: 9px;
  font-family: var(--mono); font-size: 10px; font-weight: 700;
  letter-spacing: 0.15em; text-transform: uppercase;
}
.ml-hd.l { color: color-mix(in srgb, var(--m-ink) 62%, transparent); }
.ml-hd.r { color: color-mix(in srgb, var(--accent) 78%, #fff); }

/* Struck in the exercise book's correction red, NOT the marigold. The accent
   is the eyebrow directly above this and every link on the page; it cannot
   also mean "this is the thing we refused" six lines later. The cream at 58%
   over --m-bg measures 5.3:1, so the struck half is still readable text
   rather than a grey ghost â€” it is content, not decoration. */
.ml-no {
  font-size: clamp(13px, 1.15vw, 15px);
  line-height: 1.35;
  color: color-mix(in srgb, var(--m-ink) 58%, transparent);
  text-decoration-line: line-through;
  text-decoration-thickness: 2px;
  text-decoration-color: color-mix(in srgb, var(--rule-red) 85%, transparent);
}
.mledger h2.ml-yes {
  color: var(--m-ink);
  font-size: clamp(18px, 1.9vw, 24px);
  line-height: 1.14;
  margin: 0;
}
.m-kick {
  grid-column: 2;
  padding-top: 16px; padding-left: clamp(16px, 2.6vw, 36px);
  border-left: 1px solid color-mix(in srgb, var(--m-ink) 15%, transparent);
  font-size: 15px;
  color: color-mix(in srgb, var(--m-ink) 72%, transparent);
  max-width: 52ch;
}
.m-kick b { color: var(--m-ink); font-weight: 600; }

/* ------------------------------------------------------ cards / trust */

.grid { display: grid; gap: 18px; }
.g2 { grid-template-columns: repeat(2, 1fr); }
.g3 { grid-template-columns: repeat(3, 1fr); }
.g4 { grid-template-columns: repeat(4, 1fr); }

.card {
  background: var(--card); border: 1px solid var(--line-2);
  border-radius: var(--r-lg); padding: 30px 28px;
  box-shadow: var(--sh-1);
  /* `translate`, not `transform`: this element carries data-fade, so GSAP
     writes `transform` on reveal â€” a CSS transition on the same property
     chases GSAP frame by frame and strands the element partway. */
  transition: translate 0.28s var(--ease), box-shadow 0.28s var(--ease);
}
.card:hover { translate: 0 -4px; box-shadow: var(--sh-2); }
.card h4 { font-size: 18px; margin: 0 0 10px; }
.card p { font-size: 14.5px; color: var(--muted); line-height: 1.6; }
.card .ico {
  width: 42px; height: 42px; border-radius: 13px;
  display: grid; place-items: center; margin-bottom: 18px;
  background: var(--accent-tint); color: var(--accent-deep);
  transition: transform 0.3s var(--ease);
}
.card:hover .ico { transform: rotate(-6deg) scale(1.06); }
.card .ico svg { width: 21px; height: 21px; }
/* Two hue families, not five. Blue and navy appeared on this site ONLY here,
   and a cool pair reads as imported next to cream, red and green â€” nothing
   else on the page answers them.

   The letters are kept because the markup uses them, but they now sort by
   meaning rather than by hue: .b is the red family for "the teacher is in
   control", .n is the green family for "safety and consent". .g stays green
   and .o keeps its amber for the one genuinely different note â€” a fallback,
   which is neither a control nor a guarantee. Green icons use --green-deep:
   --green on its own tint is 4.11:1, which is why that token exists. */
.card.g .ico { background: var(--green-tint); color: var(--green-deep); }
.card.b .ico { background: var(--accent-tint); color: var(--accent-deep); }
.card.n .ico { background: var(--green-tint); color: var(--green-deep); }
.card.o .ico { background: var(--orange-tint); color: var(--orange); }
/* Four across is ~250px a column at full width â€” the 30/28 padding leaves too
   little line for the body copy, so the four-up grid runs a tighter box. */
.g4 .card { padding: 26px 22px; }
.g4 .card h4 { font-size: 17px; }
.g4 .card p { font-size: 14px; }

/* Safety grid: six tiles read as a dense reference list, not a hero grid â€”
   shrink heading and tile by >25% so it sits compact under the section head. */
#safety .sec-head h2 { font-size: clamp(24px, 3.4vw, 39px); }
#safety .grid.g3 { gap: 13px; }
#safety .card { padding: 20px 18px; }
#safety .card h4 { font-size: 13px; margin-bottom: 6px; }
#safety .card p { font-size: 12.5px; line-height: 1.5; }
#safety .card .ico {
  width: 30px; height: 30px; border-radius: 10px; margin-bottom: 12px;
}
#safety .card .ico svg { width: 15px; height: 15px; }

/* ------------------------------------------------- constellation band */

.stars-band {
  position: relative;
  background: linear-gradient(180deg, var(--paper) 0%, var(--cream) 100%);
  border-block: 1px solid var(--line);
  overflow: hidden;
}
.stars-canvas { position: absolute; inset: 0; }
.stars-canvas canvas { display: block; }
.stars-band .wrap { position: relative; z-index: 2; }
.stars-head { max-width: 640px; padding: 110px 0 40px; }
.stars-head h2 { font-size: clamp(32px, 4.6vw, 52px); margin-bottom: 18px; }
.stars-head p { font-size: 17.5px; color: var(--ink-2); }
.stars-stats { display: flex; gap: 46px; margin: 26px 0 0; flex-wrap: wrap; }
.stars-stats div b {
  display: block; font-family: var(--display); font-weight: 600;
  font-size: clamp(38px, 4vw, 56px); letter-spacing: -0.03em;
  color: var(--accent); line-height: 1; font-variant-numeric: tabular-nums;
}
.stars-stats div span { font-size: 13.5px; color: var(--muted); }
.stars-shot { position: relative; padding: 30px 0 110px; }

/* -------------------------------------------------- split feature row */

.split-row { display: grid; grid-template-columns: minmax(0, 0.9fr) minmax(0, 1.1fr); gap: 64px; align-items: center; }
.split-row.flip .sr-txt { order: 2; }
.sr-txt h3 { font-size: clamp(26px, 3.4vw, 38px); margin-bottom: 16px; }
.sr-txt > p { font-size: 16.5px; color: var(--ink-2); line-height: 1.62; }
.feat-list { list-style: none; margin: 24px 0 0; padding: 0; display: flex; flex-direction: column; gap: 12px; }
.feat-list li { display: flex; gap: 12px; align-items: flex-start; font-size: 15px; color: var(--ink-2); line-height: 1.5; }
.feat-list svg { width: 18px; height: 18px; flex: none; margin-top: 3px; color: var(--accent); }
.feat-list b { color: var(--ink); font-weight: 600; }
.sr-vis { position: relative; }
.float-card {
  position: absolute; z-index: 5;
  border-radius: var(--r-md);
  border: 1px solid var(--line);
  box-shadow: var(--sh-2);
  overflow: hidden; background: var(--card);
  animation: bob 7s ease-in-out infinite;
}
.float-card img { display: block; }

/* ---------------------------------------------------------------- cta */

.cta { text-align: center; padding: 130px 0; overflow: hidden; }
.cta .blob.b1 {
  width: 860px; height: 640px; bottom: -420px; left: 50%; margin-left: -430px;
  background: radial-gradient(circle, var(--accent-tint) 0%, transparent 62%);
}
.cta-in { position: relative; max-width: 780px; margin: 0 auto; }
.cta h2 { font-size: clamp(34px, 5.4vw, 62px); margin-bottom: 20px; }
.cta p { font-size: 18px; color: var(--ink-2); margin-bottom: 34px; }
.cta-btns { display: flex; gap: 13px; justify-content: center; flex-wrap: wrap; }

/* ------------------------------------------------------------- footer */

.foot { border-top: 1px solid var(--line); padding: 46px 0 56px; background: var(--paper-2); }
.foot-in { display: flex; align-items: center; justify-content: space-between; gap: 26px; flex-wrap: wrap; }
.foot-links { display: flex; gap: 22px; flex-wrap: wrap; }
.foot-links a { font-size: 14px; color: var(--muted); text-decoration: none; }
.foot-links a:hover { color: var(--ink); text-decoration: underline; text-underline-offset: 3px; }
.foot-note {
  font-family: var(--mono); font-size: 11px;
  letter-spacing: 0.09em; text-transform: uppercase; color: var(--muted);
}

/* ---------------------------------------------------------- misc bits */

.tag {
  display: inline-flex; align-items: center; gap: 7px;
  font-family: var(--mono); font-size: 10.5px; font-weight: 700;
  letter-spacing: 0.1em; text-transform: uppercase;
  padding: 5px 11px; border-radius: var(--r-pill);
  background: var(--orange-tint); color: #8a5a1f;
  vertical-align: middle;
}

.steps { counter-reset: s; display: grid; grid-template-columns: repeat(3, 1fr); gap: 20px; }
.step { position: relative; padding: 28px 26px 26px; background: var(--card); border: 1px solid var(--line-2); border-radius: var(--r-lg); box-shadow: var(--sh-1); }
.step::before {
  counter-increment: s; content: counter(s);
  display: grid; place-items: center;
  width: 30px; height: 30px; border-radius: 50%;
  background: var(--accent); color: #fff;
  font-family: var(--mono); font-size: 13px; font-weight: 700;
  margin-bottom: 16px;
}
.step h4 { font-size: 17px; margin: 0 0 8px; }
.step p { font-size: 14px; color: var(--muted); line-height: 1.55; }

/* --------------------------------------------------------- responsive */

@media (max-width: 1000px) {
  .g3, .g4, .steps, .bento { grid-template-columns: repeat(2, 1fr); }
  .stats { grid-template-columns: repeat(2, 1fr); row-gap: 40px; }
  .stat:nth-child(3) { border-left: 0; padding-left: 0; }
  .j-grid { grid-template-columns: 1fr; gap: 0; }
  .j-visual { display: none; }
  .j-list { padding: 0; }
  .j-item { opacity: 1; padding: 44px 0; }
  .j-mshot { display: block; margin-top: 22px; }
  .split-row { grid-template-columns: 1fr; gap: 36px; }
  .split-row.flip .sr-txt { order: 0; }
  /* the spine stops being a column and becomes a rule between the two sides.
     The dashes ran left-to-right; stacked, the crossing runs downward, so the
     arrow turns and the flow lines go with it rather than being hidden. */
  .bridge { grid-template-columns: 1fr; }
  .bridge-mid { flex-direction: row; gap: 16px; padding: 22px 26px; text-align: left; }
  .bridge-mid::before, .bridge-mid::after { left: 24%; right: 24%; }
  .bridge-mid .arrow svg { transform: rotate(90deg); }
  .bridge-mid b { flex: none; }
  .chip { display: none; }
  .chip.keep { display: inline-flex; }
}

@media (max-width: 720px) {
  body { font-size: 15.5px; }
  .wrap { padding: 0 20px; }
  .sec { padding: 72px 0; }
  .hero { padding-top: 54px; }
  .nav-links { display: none; }
  .nav-links.keep { display: flex; }
  .g2, .g3, .g4, .steps, .crosses, .bento { grid-template-columns: 1fr; }
  .tile.wide { grid-column: auto; }
  .tile.tall { grid-row: auto; }
  .tile.tall .t-img { min-height: 260px; }
  .stats { grid-template-columns: 1fr 1fr; gap: 28px 20px; }
  .stat { border-left: 0; padding: 0; }
  .cross-link { flex-direction: column; align-items: flex-start; gap: 18px; padding: 26px 24px; }
  .hero-vis { margin-top: 44px; }
  .manifesto { padding: 64px 0; }
  /* Stacked, the vertical rule and the left header have nothing to divide,
     and the strike-through is doing the pairing again â€” which is fine here,
     because a refusal and its answer are now adjacent lines with no wrap
     between them. */
  .mledger { grid-template-columns: 1fr; }
  .mledger > .r { border-left: 0; padding-left: 0; }
  .mledger > .ml-hd.l { display: none; }
  .mledger > .l.row { border-top: 1px solid color-mix(in srgb, var(--m-ink) 12%, transparent); padding: 14px 0 0; }
  .mledger > .r.row { border-top: 0; padding-top: 2px; }
  .m-kick { grid-column: 1; border-left: 0; padding-left: 0; }
  .float-card { display: none; }
  .stars-head { padding-top: 80px; }
  .stars-shot { padding-bottom: 80px; }
}

/* ----------------------------------------------------- reduced motion */

@media (prefers-reduced-motion: reduce) {
  html { scroll-behavior: auto; }
  *, *::before, *::after {
    animation-duration: 0.001ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.001ms !important;
  }
  .reveal { opacity: 1; transform: none; }
  .mq-track { animation: none; }
}

/* ============================================================
   v3 â€” section rhythm, credibility & conversion components
   ============================================================ */

/* ------------------------------------------------- section grounds */

.band-white { background: var(--band-alt); border-block: 1px solid var(--line); }
.band-cream { background: var(--cream); border-block: 1px solid var(--line); }
.band-white + .band-white { border-top: 0; }

/* ------------------------------------------------- stats, redesigned
   Leads with the positive claim. The two "we don't do X" points render
   as struck words rather than proud zeros â€” a row that opened "0 0" read
   as "nothing here" before anyone got to the labels. */

.stat b.neg {
  font-size: clamp(26px, 3vw, 38px);
  color: var(--muted);
  text-decoration: line-through;
  text-decoration-thickness: 2px;
  text-decoration-color: color-mix(in srgb, var(--accent) 70%, transparent);
  letter-spacing: -0.02em;
}
.stat b em { font-style: normal; color: var(--accent); }
.stat b small {
  font-family: var(--font); font-size: 0.36em; font-weight: 600;
  letter-spacing: 0; color: var(--muted); margin-left: 6px;
}

/* --------------------------------------------------- coverage strip */

.coverage {
  display: grid; grid-template-columns: minmax(0, 1fr) auto; gap: 40px;
  align-items: center;
  background: var(--card); border: 1px solid var(--line-2);
  border-radius: var(--r-xl); padding: 34px 38px; box-shadow: var(--sh-1);
}
.coverage h3 { font-size: 25px; margin-bottom: 10px; }
.coverage p { font-size: 15.5px; color: var(--ink-2); max-width: 62ch; }
.cov-grid { display: flex; gap: 10px; flex-wrap: wrap; margin-top: 18px; }
.cov-grid span {
  display: inline-flex; align-items: center; gap: 8px;
  font-size: 13.5px; font-weight: 600; color: var(--ink-2);
  background: var(--paper-2); border: 1px solid var(--line-2);
  padding: 7px 14px; border-radius: var(--r-pill);
}
.cov-grid span i { font-style: normal; color: var(--green); font-weight: 700; }
.cov-badge {
  flex: none; text-align: center;
  border-left: 1px solid var(--line); padding-left: 40px;
}
.cov-badge b {
  display: block; font-family: var(--display); font-weight: 600;
  font-size: 46px; line-height: 1; color: var(--accent); letter-spacing: -0.03em;
}
.cov-badge span { font-size: 13px; color: var(--muted); }

/* ------------------------------------------------------ mid-page CTA */

.midcta {
  display: flex; align-items: center; justify-content: space-between;
  gap: 32px; flex-wrap: wrap;
  background: var(--cream-deep); border: 1px solid var(--line-2);
  border-radius: var(--r-xl); padding: 30px 36px;
}
.midcta h3 { font-size: clamp(21px, 2.4vw, 27px); margin-bottom: 6px; }
.midcta p { font-size: 15px; color: var(--ink-2); }
.midcta .mc-btns { display: flex; gap: 11px; flex-wrap: wrap; }

/* ---------------------------------------------------------- samples */

.samples { display: grid; grid-template-columns: repeat(2, 1fr); gap: 18px; }
.sample {
  display: flex; align-items: center; gap: 20px;
  background: var(--card); border: 1px solid var(--line-2);
  border-radius: var(--r-lg); padding: 22px 24px;
  text-decoration: none; box-shadow: var(--sh-1);
  transition: transform 0.28s var(--ease), box-shadow 0.28s var(--ease), border-color 0.28s var(--ease);
}
.sample:hover { transform: translateY(-3px); box-shadow: var(--sh-2); border-color: var(--accent); }
.sample .sp-thumb {
  flex: none; width: 78px; height: 100px; border-radius: 8px;
  border: 1px solid var(--line-2); background: var(--paper-2);
  overflow: hidden; position: relative; box-shadow: var(--sh-1);
}
.sample .sp-thumb img { width: 100%; height: 100%; object-fit: cover; object-position: top center; }
.sample .sp-txt { flex: 1; min-width: 0; }
.sample .sp-txt b { display: block; font-size: 16.5px; font-weight: 700; margin-bottom: 4px; }
.sample .sp-txt span { display: block; font-size: 13.5px; color: var(--muted); line-height: 1.5; }
.sample .sp-meta {
  display: inline-flex; align-items: center; gap: 7px; margin-top: 10px;
  font-family: var(--mono); font-size: 10.5px; font-weight: 700;
  letter-spacing: 0.08em; text-transform: uppercase;
  color: var(--accent-deep); background: var(--accent-tint);
  padding: 4px 10px; border-radius: var(--r-pill);
}
.sample .sp-go { flex: none; color: var(--accent); transition: transform 0.22s var(--ease); }
.sample:hover .sp-go { transform: translateY(2px); }
.sample .sp-go svg { width: 22px; height: 22px; }

/* -------------------------------------------------------- team strip */

.team { display: grid; grid-template-columns: repeat(3, 1fr); gap: 18px; }
.member {
  background: var(--card); border: 1px solid var(--line-2);
  border-radius: var(--r-lg); padding: 26px 24px; box-shadow: var(--sh-1);
}
.member .av {
  width: 54px; height: 54px; border-radius: 50%; margin-bottom: 16px;
  display: grid; place-items: center;
  font-family: var(--display); font-size: 21px; font-weight: 600; color: #fff;
  background: linear-gradient(140deg, var(--accent), var(--accent-deep));
}
.member.g .av { background: linear-gradient(140deg, var(--green), color-mix(in srgb, var(--green) 60%, #000)); }
.member.b .av { background: linear-gradient(140deg, var(--blue), color-mix(in srgb, var(--blue) 60%, #000)); }
.member h4 { font-size: 17px; margin-bottom: 3px; }
.member .role {
  font-family: var(--mono); font-size: 10.5px; font-weight: 700;
  letter-spacing: 0.1em; text-transform: uppercase; color: var(--accent-deep);
  display: block; margin-bottom: 12px;
}
.member p { font-size: 14px; color: var(--muted); line-height: 1.58; }
.member .prev { display: flex; gap: 7px; flex-wrap: wrap; margin-top: 14px; }
.member .prev span {
  font-size: 11.5px; font-weight: 600; color: var(--ink-2);
  background: var(--paper-2); border: 1px solid var(--line);
  padding: 3px 9px; border-radius: var(--r-pill);
}
/* editorial marker so unfilled placeholder copy is obvious in-page */
.tbd { border-bottom: 1.5px dashed var(--accent); color: var(--accent-deep); }

/* -------------------------------------------------------------- FAQ */

.faq { max-width: 860px; margin: 0 auto; }
.faq details {
  border-bottom: 1px solid var(--line-2);
  padding: 4px 0;
}
.faq details:first-of-type { border-top: 1px solid var(--line-2); }
.faq summary {
  list-style: none; cursor: pointer;
  display: flex; align-items: center; justify-content: space-between; gap: 20px;
  padding: 24px 4px;
  font-family: var(--display); font-size: clamp(17px, 2vw, 21px); font-weight: 600;
  letter-spacing: -0.01em; color: var(--ink);
  transition: color 0.2s var(--ease);
}
.faq summary::-webkit-details-marker { display: none; }
.faq summary:hover { color: var(--accent-deep); }
.faq summary .q-ico {
  flex: none; width: 26px; height: 26px; border-radius: 50%;
  border: 1px solid var(--line-2); display: grid; place-items: center;
  position: relative; transition: transform 0.32s var(--ease), background 0.25s, border-color 0.25s;
}
.faq summary .q-ico::before,
.faq summary .q-ico::after {
  content: ""; position: absolute; background: var(--ink-2);
  transition: opacity 0.25s var(--ease), background 0.25s;
}
.faq summary .q-ico::before { width: 11px; height: 1.8px; }
.faq summary .q-ico::after { width: 1.8px; height: 11px; }
.faq details[open] summary .q-ico { transform: rotate(135deg); background: var(--accent); border-color: var(--accent); }
.faq details[open] summary .q-ico::before,
.faq details[open] summary .q-ico::after { background: #fff; }
.faq details[open] summary .q-ico::after { opacity: 0; }
.faq .ans { padding: 0 46px 26px 4px; }
.faq .ans p { font-size: 15.5px; color: var(--ink-2); line-height: 1.65; }
.faq .ans p + p { margin-top: 12px; }
.faq .ans a { color: var(--accent-deep); text-underline-offset: 3px; }

/* --------------------------------------------------- contact / form */

.contact-grid {
  display: grid; grid-template-columns: minmax(0, 0.85fr) minmax(0, 1.15fr);
  gap: 56px; align-items: start;
}
.contact-copy h2 { font-size: clamp(30px, 4.2vw, 46px); margin-bottom: 18px; }
.contact-copy p { font-size: 17px; color: var(--ink-2); }
.contact-points { list-style: none; margin: 26px 0 0; padding: 0; display: flex; flex-direction: column; gap: 13px; }
.contact-points li { display: flex; gap: 12px; font-size: 15px; color: var(--ink-2); align-items: flex-start; }
.contact-points svg { width: 18px; height: 18px; flex: none; margin-top: 3px; color: var(--green); }

.form {
  background: var(--card); border: 1px solid var(--line-2);
  border-radius: var(--r-xl); padding: 32px; box-shadow: var(--sh-2);
}
.form-row { display: grid; grid-template-columns: 1fr 1fr; gap: 14px; }
.field { display: flex; flex-direction: column; gap: 7px; margin-bottom: 14px; }
.field label {
  font-family: var(--mono); font-size: 10.5px; font-weight: 700;
  letter-spacing: 0.1em; text-transform: uppercase; color: var(--muted);
}
.field input, .field select, .field textarea {
  font-family: inherit; font-size: 15px; color: var(--ink);
  background: var(--paper); border: 1px solid var(--line-2);
  border-radius: var(--r-md); padding: 12px 14px;
  transition: border-color 0.2s var(--ease), box-shadow 0.2s var(--ease), background 0.2s;
  width: 100%;
}
.field textarea { min-height: 104px; resize: vertical; }
.field input:focus, .field select:focus, .field textarea:focus {
  outline: none; border-color: var(--accent); background: var(--card);
  box-shadow: 0 0 0 3px color-mix(in srgb, var(--accent) 16%, transparent);
}
.field input::placeholder, .field textarea::placeholder { color: color-mix(in srgb, var(--muted) 70%, transparent); }
.form .btn { width: 100%; margin-top: 6px; }
.form-note { font-size: 12.5px; color: var(--muted); margin-top: 14px; text-align: center; line-height: 1.5; }
.form-note a { color: var(--accent-deep); }
.form-msg {
  display: none; margin-top: 16px; padding: 14px 16px; border-radius: var(--r-md);
  font-size: 14.5px; line-height: 1.5;
}
.form-msg.ok { display: block; background: var(--green-tint); color: var(--green); border: 1px solid color-mix(in srgb, var(--green) 30%, transparent); }
.form-msg.err { display: block; background: var(--orange-tint); color: #8a5a1f; border: 1px solid color-mix(in srgb, var(--orange) 34%, transparent); }
.hp { position: absolute; left: -9999px; width: 1px; height: 1px; overflow: hidden; }

/* ------------------------------------------------ join / demo (teacher)
   The closing form, stripped to the mark, one line, three fields and a
   button. The two-column .contact-grid above is still what the tutor page
   uses; this is the teacher page's version and it does not share a rule
   with it, so tuning one never moves the other.

   Fields carry their labels only as placeholders, which is the trade this
   layout is making â€” so each one keeps an aria-label in the markup, and
   the placeholder text stays at --muted (5.9:1 on the tinted pill) rather
   than the faded 70% mix .field inputs use, since here it is the label. */

.join { position: relative; max-width: 430px; margin: 0 auto; }
.join-mark { display: block; width: 46px; height: 46px; margin: 0 auto 20px; color: var(--accent); }
.join h2 { font-size: clamp(38px, 6vw, 60px); margin: 0 0 32px; }

.join-form { display: flex; flex-direction: column; gap: 11px; }
.join-form input,
.join-form select,
.join-form textarea {
  font-family: inherit; font-size: 16px; color: var(--ink); text-align: left;
  background: color-mix(in srgb, var(--ink) 6%, transparent);
  border: 1px solid transparent; border-radius: var(--r-pill);
  padding: 17px 24px; width: 100%;
  transition: background 0.2s var(--ease), border-color 0.2s var(--ease),
    box-shadow 0.2s var(--ease);
}
/* Native select chrome ignores the pill's own padding rhythm, so the stock
   arrow lands off-balance against the rounded corner. Swapped for a drawn
   chevron set at the same 24px inset the text uses, sized to its box. */
.join-form select {
  appearance: none; -webkit-appearance: none;
  padding-right: 44px;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%2356675e' stroke-width='2.2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M5 9l7 7 7-7'/%3E%3C/svg%3E");
  background-repeat: no-repeat;
  background-position: right 22px center;
  background-size: 14px 14px;
}
/* The one field that can't be a pill â€” a capsule around three lines of text
   reads as a speech bubble. Squared to --r-lg, which is the pill's radius
   held back just far enough to still belong to the same set. */
.join-form textarea {
  display: block; border-radius: var(--r-lg); min-height: 112px;
  line-height: 1.5; resize: vertical;
}
.join-form input::placeholder,
.join-form textarea::placeholder { color: var(--muted); }
.join-form input:hover,
.join-form select:hover,
.join-form textarea:hover { background: color-mix(in srgb, var(--ink) 9%, transparent); }
.join-form input:focus,
.join-form select:focus,
.join-form textarea:focus {
  outline: none; background: var(--card); border-color: var(--accent);
  box-shadow: 0 0 0 3px color-mix(in srgb, var(--accent) 16%, transparent);
}
.join-form .btn { width: 100%; margin-top: 5px; padding: 17px 24px; font-size: 16px; }
.join .form-msg { text-align: left; }

/* The one thing on this page that isn't work. It waves for about a second,
   then rests for two â€” a loop that never stops moving reads as a spinner,
   not a greeting. The bob is 3px so the two cycles stay in phase. */
.join-fig {
  display: block; width: 192px; height: auto; margin: 46px auto 0;
  color: var(--ink); animation: join-bob 3.4s ease-in-out infinite;
}
.join-arm {
  /* the firefly's shoulder, where the raised arm leaves the head */
  transform-box: view-box; transform-origin: 136px 94px;
  animation: join-wave 3.4s var(--ease) infinite;
}
@keyframes join-wave {
  0%, 6%    { transform: rotate(0deg); }
  15%       { transform: rotate(-17deg); }
  23%       { transform: rotate(9deg); }
  31%       { transform: rotate(-14deg); }
  39%       { transform: rotate(6deg); }
  47%, 100% { transform: rotate(0deg); }
}
@keyframes join-bob {
  0%, 100% { transform: translateY(0); }
  50%      { transform: translateY(-3px); }
}
@media (prefers-reduced-motion: reduce) {
  .join-fig, .join-arm { animation: none; }
}
@media (max-width: 720px) {
  .join-fig { width: 156px; margin-top: 38px; }
}

/* ------------------------------------------------------- prose page */

.prose { max-width: 760px; margin: 0 auto; }
.prose h1 { font-size: clamp(34px, 5vw, 52px); margin-bottom: 18px; }
.prose .updated { font-family: var(--mono); font-size: 11.5px; letter-spacing: 0.08em; text-transform: uppercase; color: var(--muted); margin-bottom: 40px; }
.prose h2 { font-size: clamp(22px, 2.6vw, 28px); margin: 48px 0 14px; }
.prose h3 { font-size: 18px; margin: 28px 0 10px; font-family: var(--font); font-weight: 700; }
.prose p { font-size: 16.5px; color: var(--ink-2); line-height: 1.72; margin-bottom: 14px; }
.prose ul { margin: 0 0 18px; padding-left: 22px; }
.prose li { font-size: 16px; color: var(--ink-2); line-height: 1.7; margin-bottom: 8px; }
.prose strong { color: var(--ink); font-weight: 700; }
.prose a { color: var(--accent-deep); text-underline-offset: 3px; }
.prose .callout {
  background: var(--card); border: 1px solid var(--line-2); border-left: 3px solid var(--accent);
  border-radius: var(--r-md); padding: 20px 24px; margin: 26px 0;
}
.prose .callout p:last-child { margin-bottom: 0; }

/* ------------------------------------------------------ footer, big */

.foot-big { border-top: 1px solid var(--line); padding: 44px 0 32px; background: var(--paper-2); }
.foot-top {
  display: flex; align-items: center; justify-content: space-between;
  gap: 28px; flex-wrap: wrap; padding-bottom: 26px; border-bottom: 1px solid var(--line-2);
}
.foot-nav { display: flex; align-items: center; gap: 26px; flex-wrap: wrap; }
.foot-nav a { font-size: 14.5px; color: var(--ink-2); text-decoration: none; }
.foot-nav a:hover { color: var(--accent-deep); text-decoration: underline; text-underline-offset: 3px; }
.foot-cta { font-size: 14.5px; font-weight: 600; color: var(--ink-2); text-decoration: none; }
.foot-cta:hover { color: var(--accent-deep); text-decoration: underline; text-underline-offset: 3px; }
.foot-bottom { padding-top: 20px; }
.foot-bottom p { font-size: 13px; color: var(--muted); max-width: 78ch; }

/* --------------------------------------------------- v3 responsive */

@media (max-width: 1000px) {
  .contact-grid { grid-template-columns: 1fr; gap: 36px; }
  .team { grid-template-columns: 1fr; }
  .coverage { grid-template-columns: 1fr; gap: 24px; }
  .cov-badge { border-left: 0; border-top: 1px solid var(--line); padding: 20px 0 0; text-align: left; }
}
@media (max-width: 720px) {
  .samples { grid-template-columns: 1fr; }
  .form-row { grid-template-columns: 1fr; }
  .form { padding: 24px 20px; }
  .midcta { padding: 26px 22px; }
  .coverage { padding: 26px 22px; }
  .foot-top { flex-direction: column; align-items: flex-start; gap: 20px; }
  .foot-nav { gap: 8px 20px; }
  .faq .ans { padding-right: 4px; }
  .sample { flex-direction: row; }
}

/* ============================================================
   v3 â€” the document showcase
   Teachers judge this product by the paper it produces, not by
   the app chrome. Each feature block puts the real generated
   document on the page as paper: A4 sheets, fanned, spreading
   apart on hover, linking to the actual PDF.
   ============================================================ */

.docfeat {
  display: grid;
  grid-template-columns: minmax(0, 0.86fr) minmax(0, 1.14fr);
  gap: 70px;
  align-items: center;
  padding: 84px 0;
  border-top: 1px dashed var(--line-2);
}
.docfeat:first-of-type { border-top: 0; padding-top: 24px; }
.docfeat.flip .df-txt { order: 2; }
.df-num {
  display: inline-flex; align-items: center; gap: 11px;
  font-family: var(--mono); font-size: 11px; font-weight: 700;
  letter-spacing: 0.14em; text-transform: uppercase;
  color: var(--accent-deep); margin-bottom: 16px;
}
.df-num b {
  display: grid; place-items: center;
  width: 27px; height: 27px; border-radius: 50%;
  background: var(--accent); color: #fff; font-size: 12px;
}
.df-txt h3 { font-size: clamp(25px, 3.1vw, 35px); margin-bottom: 14px; }
.df-txt > p { font-size: 16.5px; color: var(--ink-2); line-height: 1.62; }
.df-out {
  display: inline-flex; align-items: center; gap: 9px;
  margin-top: 22px; padding: 9px 15px;
  border-radius: var(--r-pill);
  background: var(--paper-2); border: 1px solid var(--line-2);
  font-size: 13px; font-weight: 600; color: var(--ink-2);
}
.df-out svg { width: 15px; height: 15px; color: var(--accent); flex: none; }
.df-link {
  display: inline-flex; align-items: center; gap: 8px;
  margin-top: 22px; font-size: 15px; font-weight: 600;
  color: var(--accent-deep); text-decoration: none;
}
.df-link svg { width: 16px; height: 16px; transition: transform 0.22s var(--ease); }
.df-link:hover { text-decoration: underline; text-underline-offset: 4px; }
.df-link:hover svg { transform: translateY(2px); }

/* ---- the fanned paper stack ---- */

.paper-stack {
  position: relative;
  display: block;
  padding: 14px 0 78px 44px;
  text-decoration: none;
  perspective: 1600px;
}
.paper {
  position: relative;
  display: block;
  width: 58%;
  border-radius: 5px;
  overflow: hidden;
  background: #fff;
  border: 1px solid var(--line-2);
  box-shadow: 0 2px 6px rgba(26, 26, 24, 0.07), 0 22px 50px rgba(26, 26, 24, 0.16);
  transition: transform 0.55s var(--ease), box-shadow 0.55s var(--ease);
  transform-origin: bottom left;
}
.paper img { width: 100%; display: block; background: #fff; }
/* pages 2 and 3 tuck behind, rotated, and fan out on hover */
.paper.p2, .paper.p3 { position: absolute; top: 18px; }
.paper.p1 { z-index: 3; }
.paper.p2 { z-index: 2; left: 44px; transform: rotate(4deg) translate(10%, 1.5%); }
.paper.p3 { z-index: 1; left: 44px; transform: rotate(7.5deg) translate(20%, 3%); }
.paper-stack:hover .paper.p1 { transform: rotate(-2.2deg) translateY(-6px); }
.paper-stack:hover .paper.p2 { transform: rotate(6.5deg) translate(26%, 0%); }
.paper-stack:hover .paper.p3 { transform: rotate(12deg) translate(48%, 2%); }
.paper-stack:hover .paper { box-shadow: 0 3px 8px rgba(26, 26, 24, 0.09), 0 34px 70px rgba(26, 26, 24, 0.2); }

/* the "open the PDF" affordance that fades in over the stack */
.paper-cta {
  position: absolute; z-index: 8; left: 6%; top: 44%;
  transform: translateY(12px) scale(.96);
  display: inline-flex; align-items: center; gap: 9px;
  padding: 11px 20px; border-radius: var(--r-pill);
  background: var(--m-bg); color: var(--m-ink);
  font-size: 14px; font-weight: 600; white-space: nowrap;
  box-shadow: var(--sh-2);
  opacity: 0; pointer-events: none;
  transition: opacity 0.35s var(--ease), transform 0.35s var(--ease);
}
.paper-cta svg { width: 15px; height: 15px; }
.paper-stack:hover .paper-cta { opacity: 1; transform: translateY(0) scale(1); }

/* a marked-up sheet gets a red-pen tick badge */
.paper .mark-badge {
  position: absolute; top: 8%; right: 7%; z-index: 4;
  width: 54px; height: 54px; border-radius: 50%;
  display: grid; place-items: center;
  background: color-mix(in srgb, var(--bad, #c56048) 12%, #fff);
  border: 2px solid var(--bad, #c56048);
  color: var(--bad, #c56048);
  font-family: var(--display); font-size: 17px; font-weight: 600;
  transform: rotate(-11deg);
  box-shadow: var(--sh-1);
}

/* page-count caption under a stack */
.stack-cap {
  position: relative; z-index: 7;
  margin-top: 6px; margin-left: 44px;
  font-size: 13px; color: var(--muted);
  display: flex; align-items: center; gap: 9px;
}
.stack-cap i {
  font-style: normal; font-family: var(--mono); font-size: 10.5px; font-weight: 700;
  letter-spacing: 0.08em; text-transform: uppercase;
  color: var(--accent-deep); background: var(--accent-tint);
  padding: 3px 9px; border-radius: var(--r-pill);
}

@media (max-width: 1000px) {
  .docfeat { grid-template-columns: 1fr; gap: 40px; padding: 56px 0; }
  .docfeat.flip .df-txt { order: 0; }
  .paper-stack { padding-left: 0; }
  .paper { width: 66%; }
  .paper.p2, .paper.p3 { left: 0; }
}
@media (max-width: 720px) {
  .paper { width: 76%; }
  .paper.p3 { display: none; }
  .paper-cta { opacity: 1; transform: none; position: static; margin: 20px 0 0; display: inline-flex; }
  .stack-cap { margin-left: 0; }
}

/* ============================================================
   v4 â€” photo-led editorial layout
   Hero splits into copy + photograph; photo slots degrade to a
   designed gradient panel so the page never looks broken while
   real photography is being sourced.
   ============================================================ */

/* --------------------------------------------------- photo slots */

.photo {
  position: relative;
  overflow: hidden;
  border-radius: var(--r-xl);
  background:
    radial-gradient(120% 90% at 20% 10%, color-mix(in srgb, var(--accent) 26%, transparent), transparent 60%),
    radial-gradient(110% 80% at 90% 90%, color-mix(in srgb, var(--green) 22%, transparent), transparent 62%),
    linear-gradient(160deg, var(--cream) 0%, var(--cream-deep) 100%);
  aspect-ratio: var(--par, 4 / 5);
  box-shadow: var(--sh-2);
  isolation: isolate;
}
/* the fallback mark â€” a big soft lantern, only visible when no photo loaded */
.photo::before {
  content: "";
  position: absolute; inset: 0; z-index: 0;
  background-image: var(--noise);
  opacity: 0.5;
}
.photo::after {
  content: "ðŸ®";
  position: absolute; z-index: 0;
  right: 6%; bottom: 4%;
  font-size: clamp(80px, 16vw, 190px);
  line-height: 1;
  opacity: 0.13;
  filter: grayscale(0.3);
  pointer-events: none;
}
.photo > img {
  position: absolute; inset: 0; z-index: 1;
  width: 100%; height: 100%;
  object-fit: cover; object-position: var(--pos, center);
  /* wipe-in + slow drift, both settle once revealed */
  clip-path: inset(0 0 100% 0);
  transform: scale(1.1);
  transition: clip-path 1.1s var(--ease), transform 1.6s var(--ease);
}
.reveal.in .photo > img, .photo.in > img { clip-path: inset(0 0 0 0); transform: scale(1.02); }
.photo.kb > img { animation: kenburns 22s ease-in-out infinite alternate; }
@keyframes kenburns {
  from { transform: scale(1.02) translate(0, 0); }
  to   { transform: scale(1.12) translate(-1.5%, -2%); }
}

/* card that sits over the bottom of a photo */
.photo-card {
  position: absolute; z-index: 4; left: 5%; right: 5%; bottom: 5%;
  background: color-mix(in srgb, var(--card) 94%, transparent);
  backdrop-filter: blur(14px); -webkit-backdrop-filter: blur(14px);
  border: 1px solid color-mix(in srgb, var(--line-2) 70%, transparent);
  border-radius: var(--r-lg);
  padding: 20px 24px;
  box-shadow: var(--sh-2);
}
.photo-card b { display: block; font-family: var(--display); font-size: 21px; font-weight: 600; letter-spacing: -0.01em; margin-bottom: 5px; }
.photo-card p { font-size: 14px; color: var(--muted); line-height: 1.5; }
.photo-card .pc-row { display: flex; align-items: center; gap: 14px; margin-top: 15px; flex-wrap: wrap; }
.photo-card .pc-cta {
  display: inline-flex; align-items: center; gap: 9px;
  background: var(--primary); color: #fdfcf8;
  padding: 9px 17px; border-radius: var(--r-pill);
  font-size: 13.5px; font-weight: 600; text-decoration: none;
  transition: transform .2s var(--ease);
}
.photo-card .pc-cta:hover { transform: translateY(-2px); }
.photo-card .pc-cta svg { width: 14px; height: 14px; }
.photo-card .pc-note { font-size: 12.5px; color: var(--muted); }

/* pills floating on top of a photo */
.photo-pills {
  position: absolute; z-index: 4; top: 5%; left: 5%;
  display: flex; gap: 9px; flex-wrap: wrap;
}
.photo-pills span {
  display: inline-flex; align-items: center; gap: 7px;
  background: color-mix(in srgb, var(--card) 92%, transparent);
  backdrop-filter: blur(10px); -webkit-backdrop-filter: blur(10px);
  border: 1px solid color-mix(in srgb, var(--line-2) 60%, transparent);
  padding: 8px 15px; border-radius: var(--r-pill);
  font-size: 13px; font-weight: 600; color: var(--ink);
  box-shadow: var(--sh-1);
  opacity: 0; transform: translateY(-8px);
  transition: opacity .6s var(--ease), transform .6s var(--ease);
  transition-delay: calc(var(--i, 0) * 130ms + .5s);
}
.reveal.in .photo-pills span { opacity: 1; transform: none; }
.photo-pills span.on { background: var(--primary); color: #fdfcf8; border-color: transparent; }
.photo-pills svg { width: 14px; height: 14px; }

/* --------------------------------------------------- split hero */

.hero-split {
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(0, 1.02fr);
  gap: 72px;
  align-items: center;
  padding: 26px 0 40px;
}
.hero-split .hs-copy { position: relative; z-index: 2; }
.hero-split h1 {
  font-size: clamp(52px, 8vw, 104px);
  line-height: 0.94;
  letter-spacing: -0.035em;
  margin-bottom: 30px;
}
.hero-split h1 em { font-style: italic; color: var(--green); font-weight: 500; }
.hero-split .hs-lede { font-size: clamp(17px, 1.9vw, 20px); color: var(--ink-2); max-width: 46ch; line-height: 1.58; }
.hero-split .hero-cta { justify-content: flex-start; margin-top: 34px; }
.hero-split .hero-tags { justify-content: flex-start; }

/* thin breadcrumb rail above the hero */
.hero-rail {
  display: flex; align-items: center; justify-content: space-between;
  gap: 22px; flex-wrap: wrap;
  padding: 22px 0 6px;
  font-size: 13.5px; color: var(--muted);
}
.hero-rail .hr-l { display: inline-flex; align-items: center; gap: 10px; }
.hero-rail .hr-l i {
  width: 7px; height: 7px; border-radius: 50%; background: var(--green);
  display: inline-block; animation: pulse 2.4s ease-in-out infinite;
}
@keyframes pulse { 0%,100% { opacity: 1; transform: scale(1);} 50% { opacity:.45; transform: scale(.8);} }
.hero-rail .hr-r { display: inline-flex; align-items: center; gap: 14px; }
.hero-rail .hr-r b {
  font-family: var(--mono); font-size: 10.5px; font-weight: 700;
  letter-spacing: .1em; text-transform: uppercase;
  color: var(--accent-deep); background: var(--accent-tint);
  padding: 4px 10px; border-radius: var(--r-pill);
}

/* ------------------------------------------------ dark statement */

.statement {
  background: var(--m-bg); color: var(--m-ink);
  padding: 46px 0; overflow: hidden;
}
.statement-in {
  display: flex; align-items: center; justify-content: space-between;
  gap: 44px; flex-wrap: wrap;
}
.statement .st-l { display: flex; align-items: center; gap: 18px; }
.statement .st-l .dots { display: flex; }
.statement .st-l .dots i {
  width: 34px; height: 34px; border-radius: 50%;
  display: grid; place-items: center;
  font-family: var(--mono); font-size: 12px; font-weight: 700; font-style: normal;
  background: color-mix(in srgb, var(--accent) 30%, var(--m-bg));
  border: 1px solid color-mix(in srgb, var(--m-ink) 24%, transparent);
  color: var(--m-ink);
  margin-left: -10px;
}
.statement .st-l .dots i:first-child { margin-left: 0; }
.statement .st-l b { display: block; font-family: var(--display); font-size: 25px; font-weight: 600; }
.statement .st-l span { font-size: 13.5px; color: color-mix(in srgb, var(--m-ink) 60%, transparent); }
.statement .st-r {
  font-family: var(--display); font-size: clamp(19px, 2.3vw, 29px);
  font-weight: 500; line-height: 1.25; text-align: right; max-width: 20ch;
}
.statement .st-r em { font-style: italic; color: color-mix(in srgb, var(--accent) 75%, #fff); }

/* -------------------------------------------- fanned app windows */

.fan {
  position: relative;
  display: flex; align-items: flex-end; justify-content: center;
  gap: 0;
  padding: 4px 0 0;
}
.fan figure {
  position: relative;
  flex: 0 0 27%;
  margin: 0 -5.5%;
  border-radius: 14px; overflow: hidden;
  background: var(--card);
  border: 1px solid var(--line-2);
  box-shadow: 0 3px 10px rgba(26,26,24,.09), 0 26px 60px rgba(26,26,24,.19);
  transition: transform .6s var(--ease), box-shadow .5s var(--ease), z-index 0s;
  will-change: transform;
}
.fan figure img { width: 100%; display: block; }
.fan figure:nth-child(1) { z-index: 1; transform: translateY(30px) rotate(-4deg); }
.fan figure:nth-child(2) { z-index: 2; transform: translateY(6px) rotate(-1.5deg); }
.fan figure:nth-child(3) { z-index: 3; transform: translateY(0) rotate(1deg); }
.fan figure:nth-child(4) { z-index: 2; transform: translateY(18px) rotate(3.5deg); }
.fan:hover figure { filter: saturate(.92); }
.fan figure:hover { z-index: 9 !important; transform: translateY(-22px) rotate(0deg) scale(1.06); filter: none !important; box-shadow: 0 6px 16px rgba(26,26,24,.12), 0 40px 90px rgba(26,26,24,.26); }

/* ------------------------------------------- numbered 01â€“04 strip */

.numstrip {
  display: grid; grid-template-columns: repeat(4, 1fr);
  gap: 34px;
  padding: 44px 0 0;
  border-top: 1px solid var(--line-2);
  position: relative;
}
.numstrip::before {
  content: ""; position: absolute; top: -1px; left: 0; height: 2px;
  width: var(--prog, 0%); background: var(--accent);
  transition: width .5s var(--ease);
}
.numitem b {
  display: block; font-family: var(--display);
  font-size: 34px; font-weight: 500; line-height: 1;
  color: var(--line-2); letter-spacing: -0.02em;
  margin-bottom: 14px;
  transition: color .4s var(--ease);
}
.numitem.on b { color: var(--accent); }
.numitem h4 { font-family: var(--display); font-weight: 600; font-size: 21px; margin-bottom: 8px; }
.numitem p { font-size: 14.5px; color: var(--muted); line-height: 1.55; }

/* ------------------------------------------------- mission band */

.mission {
  background: var(--m-bg); color: var(--m-ink);
  padding: 118px 0; overflow: hidden;
}
.mission::before {
  content: ""; position: absolute; inset: 0;
  background: radial-gradient(680px circle at var(--mx, 78%) var(--my, 30%),
    color-mix(in srgb, var(--accent) 15%, transparent), transparent 62%);
  pointer-events: none;
}
.mission-grid {
  position: relative;
  display: grid; grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
  gap: 70px; align-items: start;
}
.mission .eyebrow { color: color-mix(in srgb, var(--accent) 80%, #fff); }
.mission .eyebrow::before { background: currentColor; }
.mission h2 {
  color: var(--m-ink); font-size: clamp(34px, 4.6vw, 56px);
  line-height: 1.05; margin-bottom: 24px;
}
.mission h2 em { font-style: italic; color: color-mix(in srgb, var(--green) 62%, #fff); font-weight: 500; }
.mission .m-body { font-size: 17px; color: color-mix(in srgb, var(--m-ink) 74%, transparent); line-height: 1.66; max-width: 48ch; }
.mission-points {
  display: grid; grid-template-columns: 1fr 1fr; gap: 22px 30px; margin-top: 40px;
}
.mission-points div { display: flex; gap: 11px; align-items: flex-start; }
.mission-points svg { width: 17px; height: 17px; flex: none; margin-top: 3px; color: color-mix(in srgb, var(--green) 60%, #fff); }
.mission-points b { display: block; font-size: 14.5px; font-weight: 700; margin-bottom: 2px; }
.mission-points span { font-size: 13.5px; color: color-mix(in srgb, var(--m-ink) 58%, transparent); line-height: 1.5; }

.metric-card {
  border: 1px solid color-mix(in srgb, var(--m-ink) 15%, transparent);
  border-radius: var(--r-xl);
  padding: 44px 44px 34px;
  background: color-mix(in srgb, #fff 4%, transparent);
}
.metric-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 46px 34px; }
.metric b {
  display: block; font-family: var(--display);
  font-size: clamp(40px, 4.6vw, 60px); font-weight: 500; line-height: 1;
  letter-spacing: -0.03em; color: color-mix(in srgb, var(--green) 60%, #fff);
  font-variant-numeric: tabular-nums; margin-bottom: 12px;
  white-space: nowrap;
}
.metric b small { font-family: var(--font); font-size: 0.36em; font-weight: 600; letter-spacing: 0; }
.metric > span { font-size: 14px; color: color-mix(in srgb, var(--m-ink) 62%, transparent); line-height: 1.5; display: block; }
.metric-foot {
  display: flex; align-items: center; justify-content: space-between;
  gap: 20px; flex-wrap: wrap;
  margin-top: 38px; padding-top: 26px;
  border-top: 1px solid color-mix(in srgb, var(--m-ink) 13%, transparent);
}
.metric-foot p {
  font-family: var(--mono); font-size: 10.5px; letter-spacing: .1em; text-transform: uppercase;
  color: color-mix(in srgb, var(--m-ink) 50%, transparent);
}
.metric-foot a {
  font-size: 14px; font-weight: 600; color: var(--m-ink);
  text-decoration: none; display: inline-flex; align-items: center; gap: 8px;
}
.metric-foot a svg { width: 14px; height: 14px; transition: transform .22s var(--ease); }
.metric-foot a:hover svg { transform: translateX(3px); }

/* -------------------------------------------- photo + copy split */

.photo-split {
  display: grid; grid-template-columns: minmax(0, 1fr) minmax(0, 1.06fr);
  gap: 70px; align-items: center;
}
.photo-split.flip .ps-copy { order: 2; }
.ps-copy .pill-tag {
  display: inline-flex; align-items: center; gap: 9px;
  font-family: var(--mono); font-size: 10.5px; font-weight: 700;
  letter-spacing: .14em; text-transform: uppercase;
  color: var(--ink-2); background: var(--card);
  border: 1px solid var(--line-2);
  padding: 7px 15px; border-radius: var(--r-pill);
  margin-bottom: 22px;
}
.ps-copy .pill-tag i { width: 6px; height: 6px; border-radius: 50%; background: var(--accent); }
.ps-copy h2 { font-size: clamp(32px, 4.4vw, 50px); line-height: 1.04; margin-bottom: 20px; }
.ps-copy h2 em { font-style: italic; color: var(--green); font-weight: 500; }
.ps-copy > p { font-size: 16.5px; color: var(--ink-2); line-height: 1.66; }
.ps-points { list-style: none; margin: 26px 0 0; padding: 0; display: flex; flex-direction: column; gap: 15px; }
.ps-points li { display: flex; gap: 12px; align-items: flex-start; font-size: 15px; color: var(--ink-2); line-height: 1.55; }
.ps-points svg { width: 18px; height: 18px; flex: none; margin-top: 3px; color: var(--green); }
.ps-link {
  display: inline-flex; align-items: center; gap: 9px; margin-top: 26px;
  font-size: 15.5px; font-weight: 600; color: var(--ink);
  text-decoration: none; border-bottom: 1.5px solid var(--ink); padding-bottom: 3px;
}
.ps-link svg { width: 15px; height: 15px; transition: transform .22s var(--ease); }
.ps-link:hover svg { transform: translateX(4px); }

/* photo caption tab */
.photo-tab {
  position: absolute; z-index: 4; top: 5%; right: -4%;
  background: var(--accent-tint); border-radius: var(--r-md);
  padding: 14px 18px; max-width: 210px;
  box-shadow: var(--sh-2);
}
.photo-tab b {
  display: block; font-family: var(--mono); font-size: 9.5px; font-weight: 700;
  letter-spacing: .12em; text-transform: uppercase; color: var(--accent-deep); margin-bottom: 5px;
}
.photo-tab span { font-size: 14px; color: var(--ink); line-height: 1.4; font-weight: 600; }

/* ------------------------------------------------ v4 responsive */

@media (max-width: 1000px) {
  .hero-split, .mission-grid, .photo-split { grid-template-columns: 1fr; gap: 40px; }
  .photo-split.flip .ps-copy { order: 0; }
  .numstrip { grid-template-columns: 1fr 1fr; gap: 30px; }
  .statement-in { justify-content: flex-start; }
  .statement .st-r { text-align: left; max-width: none; }
  .fan { min-height: 300px; }
  .metric-card { padding: 34px 28px 26px; }
  .photo-tab { right: 4%; }
}
@media (max-width: 720px) {
  .hero-split h1 { font-size: clamp(42px, 12vw, 62px); }
  .numstrip { grid-template-columns: 1fr; gap: 26px; }
  .metric-grid { grid-template-columns: 1fr 1fr; gap: 30px 20px; }
  .mission-points { grid-template-columns: 1fr; }
  .fan figure { flex: 0 0 46%; margin: 0 -8%; }
  .fan figure:nth-child(4) { display: none; }
  .photo-card { left: 4%; right: 4%; bottom: 4%; padding: 16px 18px; }
  .photo-tab { display: none; }
}

/* ============================================================================
   v5 â€” GSAP-driven layout
   A tighter narrative: full-bleed video hero, one pinned walkthrough that
   tells plan/prepare/mark/report ONCE, and a horizontal document gallery.
   Rules here come last in the cascade and deliberately win over v4.
   ========================================================================= */

/* Elements GSAP animates start visible; GSAP sets its own "from" state.
   That way a failed script load leaves a complete, readable page. */
.no-motion [data-fade],
.no-motion [data-lines] { opacity: 1 !important; transform: none !important; }

/* ------------------------------------------------ masked word reveal ----- */
/* The reveal mask clips whatever leaves its box, so it must be tall enough for
   descenders (g, p, y) â€” with too little room the tails get sheared off. The
   negative margin cancels the extra padding so line spacing is unaffected. */
.word-m {
  display: inline-block; overflow: hidden; vertical-align: bottom;
  padding-bottom: 0.24em; margin-bottom: -0.24em;
}
.word { display: inline-block; will-change: transform; }

/* ------------------------------------------------ hero ------------------ */
.hero-v5 {
  position: relative;
  min-height: min(94vh, 900px);
  display: flex;
  align-items: flex-start;
  padding: 60px 0 96px;
  overflow: hidden;
  isolation: isolate;
}

/* v22 â€” THE HERO SHOWS THE WHOLE FRAME. A framing decision, not a sizing one.
   .hero-media is object-fit:cover, so the hero's SHAPE decides how much of the
   clip you see. At min(94vh, 900px) any window wider than 16:9 got a vertical
   crop â€” on a 1629Ã—762 window that is a ~1.35x magnification, which is what
   pushed the schoolhouse up in scale and cut the sky and the foreground field
   off. Giving the box the clip's own ratio makes cover a no-op: the whole
   frame, at the zoom the file was drawn at, still full-bleed and still no
   letterbox bars.

   aspect-ratio rather than a 56.25vw min-height because vw counts the
   scrollbar, and that is enough to put a few pixels of crop back.

   min-height MUST go to 0 alongside it, and that is not tidying. A definite
   min-height on a box that has an aspect-ratio transfers through the ratio
   into a minimum WIDTH: min(94vh, 900px) became a 1504px floor on the hero's
   width at 1440, pushing the section past the viewport and giving the whole
   page a horizontal scrollbar. Measured â€” 1440 viewport, scrollWidth 1504.
   Nothing is lost: at 1181px the 16:9 box is 664px tall against 609px of copy,
   and the slack only grows with width.

   SCOPED TWICE, both deliberate.
   Â· body.has-dusk â€” .hero-v5 is shared with /tutor, whose hero is a different
     clip, a different composition and a night seam tuned to its own heights.
   Â· min-width 1181 â€” below that the figure returns to normal flow and stacks
     under the copy, so content, not the ratio, has to set the height; the
     â‰¤1180 rule further down already zeroes min-height for that. Below 860
     motion.js swaps the video for the poster anyway.

   The cost, and it is a real one: on a short wide window the hero is now taller
   than the viewport (916px on a 762px-tall window), so the scroll cue sits
   below the fold. That is what showing a 16:9 frame on a 2.14:1 window means â€”
   the alternatives are cropping it (what this replaces) or letterboxing it. */
@media (min-width: 1181px) {
  body.has-dusk .hero-v5 {
    aspect-ratio: 16 / 9;
    min-height: 0;
  }
}
.hero-media { position: absolute; inset: 0; z-index: 0; overflow: hidden; }
.hero-media video,
.hero-media .hero-poster,
.hero-media canvas {
  position: absolute; inset: 0;
  width: 100%; height: 100%;
  object-fit: cover;
  display: block;
}
/* The video fades up once it has actually decoded a frame â€” no black flash. */
.hero-media video { opacity: 0; transition: opacity 1.1s var(--ease); }
.hero-media video.on { opacity: 1; }

/* Structure only. The paint lives in the v10 consolidated block at the end of
   this file â€” that is the single authoritative place to change the hero wash. */
.hero-scrim {
  position: absolute; inset: 0; z-index: 1;
}
.hero-v5 .wrap { position: relative; z-index: 2; }
.hero-copy { max-width: 660px; }
.hero-v5 h1 {
  font-family: var(--display-hero);
  font-size: clamp(46px, 6.6vw, 88px);
  line-height: 0.99;
  /* Jakarta is drawn for paragraphs, so at 88px its default fitting reads
     loose and the words drift apart. Tracking pulls that in â€” but it applies
     to the space character too, and past about -0.03em "Plan less" closes up
     into one word. So: a moderate negative track for the letters, and the
     word gap handed back explicitly. Tune them as a pair, never alone. */
  letter-spacing: -0.02em;
  word-spacing: 0.06em;
  font-weight: 800;
  margin: 20px 0 0;
}
/* Upright, not italic â€” at 800 the italic is a slanted copy of the roman and
   the slant just muddies the edge over the footage. Colour alone is a cleaner
   split, and it is the only accent red in the copy block. */
.hero-v5 h1 em { font-style: normal; color: var(--accent); font-weight: 800; }
.hero-v5 .hs-lede {
  font-size: clamp(17px, 1.9vw, 20.5px);
  color: var(--ink-2);
  max-width: 47ch;
  line-height: 1.58;
  margin-top: 24px;
}
.hero-v5 .hero-cta { justify-content: flex-start; margin-top: 36px; }
.hero-v5 .hero-tags { justify-content: flex-start; margin-top: 30px; }

.hero-scroll {
  position: absolute; left: 50%; bottom: 26px; transform: translateX(-50%);
  z-index: 3; display: flex; flex-direction: column; align-items: center; gap: 9px;
  font-size: 11.5px; letter-spacing: 0.14em; text-transform: uppercase;
  color: var(--muted); font-weight: 600;
}
.hero-scroll i {
  width: 1px; height: 34px; background: linear-gradient(var(--line-2), transparent);
  position: relative; overflow: hidden;
}
.hero-scroll i::after {
  content: ""; position: absolute; inset: 0;
  background: var(--accent);
  animation: scrollcue 2.2s var(--ease) infinite;
}
@keyframes scrollcue {
  0% { transform: translateY(-100%); }
  60%, 100% { transform: translateY(100%); }
}

/* ------------------------------------------------ mission, in the band --- */
/* Two columns on the dusk footage, below the "Less planning" statement:
   mission copy (plain text, same readability pattern .dusk-in p already
   uses â€” cream + text-shadow, no card) beside a metrics panel (glass, like
   .pathc below it â€” this section sits on the same moving video, so it
   reuses that contrast solution rather than inventing a new one). */
/* One centred column, not two. The statement is the band's opening beat now,
   so it gets the axis the old display heading had; the pledges, the figures
   and the coverage strip stack beneath it as full-width rows. */
.mission-in {
  position: relative; z-index: 2;
  max-width: 1180px; margin: 0 auto;
  /* The top pad is not taste â€” it is the seam. .dusk-scrim's ramp finishes at
     188px, and this copy took over the slot the display statement used to
     hold, so it has to start clear of that ramp or the heading is measured
     against a plate the seam contributes to. Floor at 196px for the same
     reason the old statement sat where it did. */
  padding: clamp(196px, 24vh, 240px) 24px clamp(96px, 12vh, 132px);
  text-align: center;
}
/* Scoped to .mission-in, NOT .mission-copy â€” that wrapper div went when the
   block became one centred column, and with it this gold. The eyebrow then
   inherited the generic .eyebrow accent red, which measures 1.5:1 on the
   band's top plate (rgb 140,102,61). Caught by _src/verify-band.py; keep the
   scope on an element that actually exists.

   The rule before the label reads as a left-anchored tick â€” on a centred axis
   it just hangs off one side, so the letterspacing carries the label alone. */
.mission-in .eyebrow {
  font-family: var(--mono); font-size: 11.5px; font-weight: 700;
  letter-spacing: 0.18em; text-transform: uppercase;
  /* #ffe9c4 is the band's gold everywhere else, but everywhere else sits
     further down the gradient. Up here the plate is the lightest the band
     gets â€” the gold measured 4.34:1 on the v25 ground, and even the
     statement's cream (#fffaf0) rounds DOWN to 4.47 on the v26 ember ramp
     (plate rgb(142,111,71)). Pure white is the only ink with headroom on
     this row; at 12px mono uppercase the step from cream is invisible.
     verify-band.py reads 4.7 here â€” re-run it before darkening this. */
  color: #fff; margin: 0 auto 30px;
  /* fit-content, not a full-width centred flex row: the bedded fossils are
     placed against real content boxes, and a box that spans the wrap while its
     ink sits in the middle reserves 500px of empty margin the grit could
     legitimately use. Makes the box equal the ink. */
  width: fit-content; justify-content: center;
}
.mission-in .eyebrow::before { display: none; }
/* This is the band's statement now, not a sub-head under one, so it takes the
   display scale the old h2 had rather than sub-head sizing. 19ch keeps it to
   three short centred lines instead of running out as a wide paragraph. */
.mission-in h3 {
  font-family: var(--display); font-weight: 600;
  font-size: clamp(34px, 4.4vw, 64px); line-height: 1.06; letter-spacing: -0.028em;
  margin: 0 auto 34px; max-width: 19ch; color: #fffdf8;
  text-shadow: 0 2px 24px rgba(52, 36, 18, 0.5);
  text-wrap: balance;
}
/* Back to the first pass's warm gold-cream â€” the colour the band's original
   "More teaching." emphasis wore (.dusk h2 em, #ffe9c4) â€” restored together
   with the first-pass ground. The sage experiment tied this word to the four
   tiles below, but on the quiet tan it read as a colour from a different
   page; the gold is the band's own light, one step warmer than the statement's
   near-white, so the emphasis reads as tone rather than a second hue.
   Clears 4.5:1 on the plate behind it (verified in _src/verify-band.py), so
   it is not near any floor the way the sage was. */
.mission-in h3 .kw { color: #ffe9c4; font-style: italic; font-weight: 500; }
.mission-lede {
  font-size: clamp(15px, 1.15vw, 17px); line-height: 1.68;
  color: rgba(253, 249, 239, 0.9);
  text-shadow: 0 1px 14px rgba(52, 36, 18, 0.45);
  max-width: 58ch; margin: 0 auto;
}
/* Four across on the centred axis. The tick and its two lines stay
   left-aligned inside each column â€” centring them too turns four small blocks
   into a ragged zigzag with no shared edge to read down. */
.mission-pledges {
  display: grid; grid-template-columns: repeat(4, 1fr); gap: 26px 30px;
  margin: 62px auto 0; text-align: left;
}
.mission-pledge { display: flex; gap: 11px; align-items: flex-start; }
.mission-pledge svg {
  width: 19px; height: 19px; flex: none; margin-top: 2px;
  /* the same gold the heading's "consistent" wears (#ffe9c4) â€” the ticks
     used to be mint, a second hue this band otherwise never uses */
  color: #fbe3ba; stroke-width: 2.4;
  stroke-linecap: round; stroke-linejoin: round;
  /* the SVG carries stroke="currentColor" (matching every other icon on
     this page â€” see the #i-check-c symbols) â€” the verify script reads
     getComputedStyle().color for its plate sample, so a CSS `stroke:` with
     no matching `color:` measures the WRONG property and reports the
     inherited text colour instead of what's actually painted on screen. */
}
.mission-pledge b { display: block; font-size: 14px; font-weight: 700; color: #fffdf8; }
.mission-pledge span {
  display: block; font-size: 13px; line-height: 1.45; margin-top: 2px;
  color: rgba(253, 249, 239, 0.82);
}

/* ------------------------------------------------ the four figures ------ */
/* They used to share one glass panel in the right-hand column. On a centred
   axis there is no right-hand column, so they become four tiles â€” the same
   object as the coverage cards, built the same way.

   They wear an OPAQUE sage plate carrying the planner board's crossed
   hairlines. It replaced the warm dark veil the single panel wore: a veil made
   each tile a darker patch of the band rather than an object on it, and it
   composited over four different points of the band gradient, so the four
   never quite matched. Opaque green fixes both â€” one plate, four identical
   tiles, stated against the brown instead of sunk into it. The type stays
   CREAM. Ratios re-measured across all four with _src/verify-band.py against
   the new plate rather than inherited from the brown one. */
.mission-tiles-lbl {
  font-family: var(--mono); font-size: 10.5px; font-weight: 700;
  letter-spacing: 0.16em; text-transform: uppercase;
  color: #ffe9c4; margin: 74px 0 20px;
}
.mission-tiles {
  display: grid; grid-template-columns: repeat(4, 1fr); gap: 22px;
  text-align: left;
}
.mission-tile {
  border-radius: 18px; padding: 24px 22px 22px;
  /* the crossed hairlines the planner board uses, warmed to the plate so the
     grid reads as ruling ON the surface rather than chalk over it */
  background-image:
    repeating-linear-gradient(to right,
      rgba(255, 236, 210, 0.07) 0, rgba(255, 236, 210, 0.07) 1px,
      transparent 1px, transparent 44px),
    repeating-linear-gradient(to bottom,
      rgba(255, 236, 210, 0.07) 0, rgba(255, 236, 210, 0.07) 1px,
      transparent 1px, transparent 44px),
    /* SAGE, hue ~120 at low saturation. The tiles used to wear a translucent
       brown veil, which made them a darker patch OF the band rather than an
       object on it â€” four figures that read as a dent. This plate is opaque
       and green, so the row states itself against the brown instead of
       receding into it, and the hairlines above stay the ruling ON it.
       Opaque matters: a translucent green over the band's own gradient shifts
       hue tile to tile (the ground runs rgb 107,78,48 to 97,70,43 under this
       row), and a green that goes olive at one end reads as a mistake. */
    linear-gradient(160deg,
      hsl(122, 13%, 33%),
      hsl(122, 13%, 30%) 52%,
      hsl(124, 14%, 26%));
  background-color: hsl(122, 13%, 31%);
  border: 1px solid rgba(255, 232, 206, 0.13);
  box-shadow: 0 20px 50px rgba(24, 15, 6, 0.32);
  display: flex; flex-direction: column; gap: 11px;
}
.mission-tile b {
  display: block; font-family: var(--display);
  font-size: clamp(30px, 3.1vw, 44px);
  line-height: 1; font-weight: 600; color: #fffdf8;
  letter-spacing: -0.024em; white-space: nowrap;
  font-variant-numeric: tabular-nums;
}
.mission-tile b i {
  font-style: normal; font-size: 0.48em; font-weight: 600; margin-left: 3px;
  color: rgba(253, 249, 239, 0.86);
}
.mission-tile > span {
  display: block; font-size: 13px; line-height: 1.5;
  color: rgba(253, 249, 239, 0.86);
}

/* ------------------------------------------------ coverage, in one line -- */
/* What used to be a whole beat â€” its own eyebrow, its own display heading and
   a four-card grid â€” reduced to a strip on the mission's own bottom row. It
   sits on the same translucent cream over brown the coverage cards used, which
   is the plate _src/verify-band.py already had a reading for.

   margin-top is load-bearing, not rhythm: the tiles above cast
   `0 20px 50px` and the strip was sitting flush under them, so 50px of tile
   shadow painted straight across its top edge and the two read as overlapping
   rather than stacked. The gap has to clear the shadow, not just the box. */
.cover-strip {
  margin-top: 30px;
  display: flex; align-items: center; flex-wrap: wrap; gap: 8px 14px;
  padding: 15px 20px; border-radius: 14px;
  background: rgba(253, 249, 239, 0.09);
  border: 1px solid rgba(253, 249, 239, 0.24);
}
/* The heading's "consistent" gold (#ffe9c4), replacing the coverage cards'
   mint live tint â€” same reasoning as the pledge ticks above: one accent hue
   for the band. Lighter than the mint it replaces, so it sits comfortably
   above the 4.5:1 floor on the strip's plate (rgb 110,86,59). */
.cover-live {
  font-family: var(--mono); font-size: 9px; font-weight: 700;
  letter-spacing: 0.14em; text-transform: uppercase; color: #ffe9c4;
  border: 1px solid rgba(248, 232, 204, 0.5); border-radius: var(--r-pill);
  padding: 3px 10px; flex: none;
}
.cover-strip > b {
  font-family: var(--display); font-weight: 600; font-size: 17px;
  letter-spacing: -0.018em; color: #fff;
}
.cover-sub { font-size: 13px; color: rgba(253, 249, 239, 0.86); }
/* 0.72 alpha measured 4.4:1 on the strip's own plate (rgb 108,83,58) â€” under
   the 4.5 floor for text this size. Verified with _src/verify-band.py, not
   computed by hand: the strip is translucent, so the plate is the brown
   showing through it, not the strip colour. */
.cover-next {
  margin-left: auto; font-family: var(--mono); font-size: 11px;
  letter-spacing: 0.04em; color: rgba(253, 249, 239, 0.86);
}
.cover-next a { color: rgba(253, 249, 239, 0.95); text-underline-offset: 3px; }

@media (max-width: 860px) {
  /* once it wraps, a right-pushed tail just lands under the left edge with a
     ragged gap above it â€” put it on its own full row with a hairline instead */
  .cover-next {
    margin-left: 0; width: 100%; padding-top: 11px;
    border-top: 1px solid rgba(253, 249, 239, 0.16);
  }
}

/* Four across only holds while each column can carry a two-line pledge and a
   figure beside its label. Below that they go two-up, then one. */
@media (max-width: 1000px) {
  .mission-pledges { grid-template-columns: 1fr 1fr; gap: 24px 30px; }
  .mission-tiles { grid-template-columns: 1fr 1fr; }
}
@media (max-width: 560px) {
  .mission-pledges { grid-template-columns: 1fr; margin-top: 44px; }
  .mission-tiles { grid-template-columns: 1fr; }
  .mission-tiles-lbl { margin-top: 54px; }
  .mission-tile { padding: 18px 17px 16px; }
}

/* ------------------------------------------------ the marker trail ------ */
/* One curve down the left, filling behind a travelling bead, with a checkpoint
   node and a stamped time beside each step. The path is drawn in JS at pixel
   size (motion.js) so stroke width, dash length and getPointAtLength agree â€”
   a stretched viewBox silently breaks the dash-to-bead mapping. */
.trail { position: relative; background: var(--paper); overflow: hidden; }
.trail-head { padding: 96px 0 30px; }

/* â”Œâ”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”
   â”‚  TRAIL KNOBS â€” every number that shapes the rail lives here.          â”‚
   â”‚  Edit, save, reload. Nothing else needs touching; motion.js reads      â”‚
   â”‚  --trail-cx and --trail-swing back out of the computed style.          â”‚
   â””â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”˜ */
.trail-in {
  --trail-col:    700px;  /* width of the centred text + screenshot column   */
  --trail-offset: 360px;  /* rail lane's left edge, measured left of that
                             column â€” bigger pushes the rail further out     */
  --trail-lane:   160px;  /* horizontal room the curve is allowed to swing in */
  --trail-stroke:  60px;  /* thickness of the rail itself                    */
  --trail-cx:      0.5;   /* where the curve rides in the lane, 0 = left edge */
  --trail-swing:   0.36;  /* bend size, as a fraction of the lane; 0 = straight */
  --trail-wave:   700px;  /* height of one bend â€” smaller number = curvier    */
  --trail-bead:    56px;  /* diameter of the travelling dot â€” keep this at or
                             under --trail-stroke so it stays inside the rail
                             rather than bulging past it                     */
  --trail-stamp:   100px;  /* gap between the time stamp and the text column  */
  --trail-shift:    64px;  /* nudges the whole trail â€” rail AND text column â€”
                              right as one unit; the rail's left is derived
                              from the column's centred position, so both have
                              to read this or the gap between them changes    */

  position: relative;
  /* Bottom padding is only the room the rail's round end cap needs â€” it used
     to be 96px of empty drift the rail ran through and stopped dead in. */
  padding: 10px 28px 40px;
}

/* The rail is positioned off the centred column, not off the viewport, so the
   gap between rail and text stays put at every width. */
.trail-rail {
  position: absolute; top: 0; bottom: 0;
  left: calc(50% - (var(--trail-col) / 2) - var(--trail-offset) + var(--trail-shift));
  width: var(--trail-lane);
}
.trail-steps {
  max-width: var(--trail-col);
  margin-inline: auto;
  transform: translateX(var(--trail-shift));
}

.tr-svg { position: absolute; inset: 0; width: 100%; height: 100%; overflow: visible; }
.tr-pale { fill: none; stroke: var(--accent-tint); stroke-width: var(--trail-stroke); stroke-linecap: round; }
.tr-lit { fill: none; stroke: var(--accent); stroke-width: var(--trail-stroke); stroke-linecap: round; }
.tr-bead {
  position: absolute; top: 0; left: 0;
  width: var(--trail-bead); height: var(--trail-bead); border-radius: 50%;
  /* No ring â€” a solid white bead on the red stroke reads cleanly on its own,
     and the darker ring was banding the edge rather than defining it. */
  background: var(--white); border: none;
  transform: translate(-50%, -50%);
  /* Kept tight â€” the bead now sits inside the rail, so a wide spread would
     halo out onto the paper either side of the stroke. */
  box-shadow: 0 2px 6px rgba(120, 60, 25, 0.22);
  opacity: 0;
}
/* .ready only marks that the bead has been placed; motion.js writes the actual
   opacity inline so it can fade the bead out as the stroke runs out. */
.tr-bead.ready { opacity: 1; }
.tr-node {
  position: absolute; top: 0; left: 0; width: 12px; height: 12px; border-radius: 50%;
  background: var(--paper); opacity: 0.5;
  transform: translate(-50%, -50%) scale(0.55);
  transition: opacity 0.45s var(--ease), transform 0.45s var(--ease);
}
.tr-node.lit { opacity: 1; transform: translate(-50%, -50%) scale(1); }

.tstep { position: relative; padding-bottom: 116px; }
.tstep:last-child { padding-bottom: 20px; }
.tstep-when {
  position: absolute; left: calc(-100px - var(--trail-stamp)); top: 2px; width: 100px;
  text-align: right; line-height: 1.2;
  font-family: var(--mono); font-size: 11px; font-weight: 700;
  letter-spacing: 0.05em; color: var(--muted);
  opacity: 0; transform: translateY(7px);
  transition: opacity 0.5s var(--ease), transform 0.5s var(--ease);
}
.tstep.on .tstep-when, .tstep.done .tstep-when { opacity: 1; transform: none; }
.tstep-when svg {
  display: block; margin: 0 0 5px auto;
  width: 21px; height: 21px; fill: none;
  stroke: var(--green); stroke-width: 2.6;
  stroke-linecap: round; stroke-linejoin: round;
}
.tstep .ws-num {
  font-family: var(--display); font-size: 15px; font-weight: 600;
  color: var(--accent); letter-spacing: 0.08em; display: block; margin-bottom: 12px;
}
.tstep h3 {
  font-family: var(--display); font-weight: 600;
  font-size: clamp(20px, 3vw, 30px); line-height: 1.08;
  letter-spacing: -0.02em; margin: 0 0 15px; max-width: 19ch;
}
/* Direct child only: the step's own intro paragraph. The app scenes now live
   inside .tstep too, and a bare descendant selector was re-typesetting every
   <p> in them â€” 16.5px/44ch turned the worksheet rail's refine box into a
   one-word-per-line column. */
.tstep > p { font-size: 15.5px; line-height: 1.62; color: var(--ink-2); max-width: 44ch; margin: 0; }
/* The outcome chip carries the Marble tint, not the affirmative green: these
   line up under the numbered steps as the trail's own punctuation, so they
   should read as brand rather than as four separate "yes" marks. --accent-deep
   on --accent-tint is 6.26:1 â€” the green it replaces was 4.11:1 at this size,
   under the 4.5 floor, so the swap fixes a contrast miss as well. The icon
   inherits; only the tick's counterpart in .tstep-when stays green, because
   that one really is a completion mark. */
.tstep .ws-out {
  display: inline-flex; align-items: center; gap: 8px; margin-top: 20px;
  font-size: 12.5px; font-weight: 600; color: var(--accent-deep);
  background: var(--accent-tint); border-radius: var(--r-pill); padding: 8px 15px;
}
.tstep .ws-out svg { width: 15px; height: 15px; flex: none; }
/* Steps you haven't reached yet sit back; once passed they stay lit. */
.tstep .ws-num, .tstep h3, .tstep p, .tstep .ws-out { transition: opacity 0.5s var(--ease); }
.tstep:not(.on):not(.done) .ws-num,
.tstep:not(.on):not(.done) h3,
.tstep:not(.on):not(.done) p,
.tstep:not(.on):not(.done) .ws-out { opacity: 0.5; }

.tshot {
  margin: 26px 0 0; max-width: var(--trail-shot, 100%);
  border-radius: var(--r-lg); overflow: hidden;
  background: var(--card); border: 1px solid var(--line-2); box-shadow: var(--sh-2);
  opacity: 0.42; transform: translateY(16px);
  transition: opacity 0.6s var(--ease), transform 0.6s var(--ease);
}
.tstep.on .tshot, .tstep.done .tshot { opacity: 1; transform: none; }

/* â”€â”€ the depth toggle Â· Walkthrough | In the app â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€
   The four job scenes used to run again as four more pills in #features, so a
   visitor met plan / paper / mark / report twice, in two different mock
   systems, one screen apart. They now sit inside their own step, one click
   deeper than the wizard: the same object at a second depth.

   Both panes stay in the DOM and the hidden one is display:none â€” which is
   also what stops it running, since an element with no box never satisfies
   its IntersectionObserver. A parked scene costs nothing until it is asked
   for, so the toggle needs no teardown of its own.

   Everything that touches a scene is written .tdepth-first on purpose: the
   .fp-stage rules these override live ~4,000 lines further down, so equal
   specificity would hand the win to source order. */
.tdepth { margin-top: 26px; }

.tseg {
  display: inline-flex; gap: 3px; padding: 3px;
  background: var(--card); border: 1px solid var(--line-2);
  border-radius: var(--r-pill);
  opacity: 0.42; transform: translateY(16px);
  transition: opacity 0.6s var(--ease), transform 0.6s var(--ease);
}
.tstep.on .tseg, .tstep.done .tseg { opacity: 1; transform: none; }
.tseg-b {
  font: inherit; font-size: 13px; font-weight: 600; letter-spacing: -0.005em;
  color: var(--muted); background: transparent; border: 0;
  border-radius: var(--r-pill); padding: 8px 16px; cursor: pointer;
  transition: background 0.18s var(--ease), color 0.18s var(--ease);
}
.tseg-b:hover { color: var(--ink); }
.tseg-b[aria-pressed="true"] { background: var(--primary); color: var(--paper); }
.tseg-b:focus-visible { outline: 2.5px solid var(--accent-deep); outline-offset: 2px; }

.tdepth-pane { display: none; }
.tdepth-pane.on { display: block; }
.tdepth-pane .tshot { margin-top: 14px; }

/* The receipt under the plan and paper steps: the mock's output is a real
   export, and the proof is one scroll away in #samples. Sits outside both
   panes so it holds for the wizard view too â€” including on phones, where the
   app pane is withdrawn. */
.tdepth-out {
  margin: 12px 0 0; font-size: 13.5px; color: var(--muted);
  text-align: center;
  opacity: 0.42; transform: translateY(16px);
  transition: opacity 0.6s var(--ease), transform 0.6s var(--ease);
}
.tstep.on .tdepth-out, .tstep.done .tdepth-out { opacity: 1; transform: none; }
.tdepth-out a { color: var(--accent-deep); font-weight: 600; text-decoration: underline; text-underline-offset: 2px; }
.tdepth-out a:hover { color: var(--accent); }

/* The scenes are composed at 880px and clip rather than reflow, so the app
   pane breaks out of the 700px text column wherever there is room for it.

   The breakout is Â±90px, and the marker rail sits in the left gutter: below
   1360px viewport the two meet exactly (measured â€” the stage's left edge lands
   ON the rail at 1180 and 1280), so the breakout only starts once there is
   real clearance. Under that, the stage simply fills the column. */
/* No height overrides: the scenes keep the exact .fp-stage heights #features
   gives them (566, and the 592/628 viewport tiers below 940). Earlier this
   block forced taller stages â€” but those numbers were measured while a CSS
   scope bug (.tstep p reaching into the scenes) was inflating every caption to
   16.5px/44ch. With the fonts correct, the scenes fit the stock heights the
   same way they do in #features, and the extra height was surfacing as blank
   bands inside the cards that stretch to fill the stage. */
.tdepth .tstage {
  margin: 14px auto 0; max-width: 880px;
  opacity: 0.42; transform: translateY(16px);
  transition: opacity 0.6s var(--ease), transform 0.6s var(--ease);
}

/* Phones keep the walkthrough only. The app scenes carry fixed-width innards â€”
   the term/subject filter rows, the three-ring class summary â€” that need about
   620px before they wrap; in #features they get it, because that stage sits in
   the full-width wrap. In the trail's column at a 390 viewport they spill and
   cut "Summer", "Science" and the Science ring off the right edge, and no
   height fixes a horizontal squeeze.

   So the second depth is withdrawn rather than shown broken, which is what the
   page does elsewhere at this width (see .xr-bar). The wizard remains, and it
   is exactly what a phone visitor saw here before the scenes moved in. */
@media (max-width: 700px) {
  .tdepth .tseg,
  .tdepth-pane[data-depth-pane="app"] { display: none; }
  .tdepth-pane[data-depth-pane="wiz"] { display: block; }
}
.tstep.on .tdepth .tstage, .tstep.done .tdepth .tstage { opacity: 1; transform: none; }
@media (min-width: 1360px) {
  .tdepth-pane[data-depth-pane="app"] {
    margin-inline: calc((var(--trail-col) - 880px) / 2);
  }
}

/* Between 941 and 1359 the stage is the 700px column rather than the 880 it
   was composed against, and the parent-report evidence hover â€” left-anchored,
   fixed 208px â€” then overhangs the stage's right edge by ~82px. It anchors
   from the right instead here. #features solves the same problem at phone
   width by dropping the popover, but this beat is ABOUT tracing a sentence
   back to its objective, so it keeps the evidence and moves it. */
@media (min-width: 941px) and (max-width: 1359px) {
  .tdepth .tstage .fp-pw-text u .fp-evipop { left: auto; right: 0; }
}
.tshot .chrome {
  display: flex; align-items: center; gap: 6px;
  padding: 11px 14px; background: var(--cream);
  border-bottom: 1px solid var(--line);
}
.tshot .chrome i {
  width: 9px; height: 9px; border-radius: 50%; background: var(--line-2); flex: none;
}
.tshot .chrome .url {
  margin-left: 10px; font-family: var(--mono); font-size: 11.5px; color: var(--muted);
}
.tshot img {
  display: block; width: 100%; aspect-ratio: 16 / 10;
  object-fit: cover; object-position: top center;
}

/* ------------------------------------------------ document gallery ------ */
/* Used to be a pinned horizontal scroller; down to 3 cards it reads better
   static and centred, so the pin/scrub (motion.js) and scroll-only rules
   that supported it are gone too. */
/* The seam with .trail above used to be a hard edge: --paper (#F3EFE5) butting
   straight into --band-alt (#fcfaf5) is a ~5% lightness step running the full
   width, and the eye reads it as a partition line. The band still changes â€”
   it just dissolves. The gradient carries the trail's own --paper through the
   first 340px (the tail of .trail is empty drift space, so nothing sits in the
   transition) and settles into --band-alt before the eyebrow. */
.hscroll { position: relative; background: var(--band-alt); }
.hscroll::before {
  content: "";
  position: absolute; z-index: 0;
  top: 0; left: 0; right: 0; height: 340px;
  background: linear-gradient(to bottom, var(--paper) 0%, transparent 100%);
  pointer-events: none;
}
.hscroll > * { position: relative; z-index: 1; }
.hs-head { padding: 96px 0 40px; }
.hs-viewport { padding-bottom: 64px; }
.hs-track {
  display: flex; flex-wrap: wrap; justify-content: center;
  gap: 30px; align-items: stretch;
  max-width: var(--wrap); margin: 0 auto; padding: 0 24px;
}
.hs-card {
  flex: 0 0 auto; width: 400px;
  display: flex; flex-direction: column;
}
.hs-card.doc { width: 270px; }
.hs-paper {
  border-radius: 8px; overflow: hidden;
  background: #fff; border: 1px solid var(--line-2);
  box-shadow: var(--sh-2);
  aspect-ratio: 1 / 1.414;
}
.hs-paper img { display: block; width: 100%; height: 100%; object-fit: cover; object-position: top center; }
.hs-shot {
  border-radius: var(--r-md); overflow: hidden;
  background: var(--card); border: 1px solid var(--line-2);
  box-shadow: var(--sh-2); aspect-ratio: 16 / 10;
}
.hs-shot img { display: block; width: 100%; height: 100%; object-fit: cover; object-position: top center; }
.hs-cap { margin-top: 14px; }
.hs-cap b {
  display: block; font-family: var(--display); font-size: 17px;
  font-weight: 600; letter-spacing: -0.01em;
}
.hs-cap span { display: block; margin-top: 6px; font-size: 14px; color: var(--muted); line-height: 1.5; }
.hs-cap a {
  display: inline-flex; align-items: center; gap: 7px; margin-top: 12px;
  font-size: 13.5px; font-weight: 600; color: var(--accent-deep);
}
.hs-cap a svg { width: 14px; height: 14px; }

/* ------------------------------------------------ specular tilt --------- */
[data-tilt3d] { transform-style: preserve-3d; will-change: transform; position: relative; }
[data-tilt3d]::after {
  content: ""; position: absolute; inset: 0; border-radius: inherit;
  pointer-events: none; opacity: 0; transition: opacity 0.4s var(--ease);
  background: radial-gradient(420px circle at var(--mx, 50%) var(--my, 50%),
    rgba(255,255,255,0.34), transparent 62%);
  z-index: 3;
}
[data-tilt3d].lit::after { opacity: 1; }

/* ------------------------------------------------ responsive ------------ */

/* The rail sits (col/2 + offset + lane) left of centre, so below roughly
   1300px it would walk off the page. Pull it in rather than clip it. */
@media (max-width: 1300px) {
  .trail-in { --trail-offset: 190px; --trail-lane: 100px; --trail-stroke: 32px;
              --trail-bead: 30px; }
}

/* Too narrow to hold a centred column with a rail beside it â€” the rail hugs
   the left edge and the text sits next to it, stamp inline. */
@media (max-width: 1080px) {
  .trail-in {
    --trail-lane: 76px; --trail-stroke: 24px;
    --trail-bead: 22px;
  }
  /* Below this width the rail hugs the left edge and the text is placed off
     it directly, so the centred-column shift no longer applies â€” zeroing it
     keeps the narrow layout pinned where it was. */
  .trail-in { --trail-shift: 0px; }
  .trail-rail { left: 4px; }
  .trail-steps { margin-inline: 0; margin-left: 110px; max-width: none; }
}

@media (max-width: 1000px) {
  .proof-stats { grid-template-columns: 1fr 1fr; }
  .trail-in { --trail-lane: 56px; --trail-stroke: 16px; --trail-bead: 15px;
              padding-bottom: 72px; }
  .trail-steps { margin-left: 78px; }
  .trail-head { padding: 76px 0 22px; }
  .tstep { padding-bottom: 86px; }
  .tr-node { width: 8px; height: 8px; }
  .hero-v5 { min-height: 0; padding: 116px 0 76px; }
  .hero-scroll { display: none; }
}
@media (max-width: 720px) {
  .proof { padding: 56px 0; }
  .proof-stats { grid-template-columns: 1fr 1fr; gap: 14px; }
  .proof-item { padding: 22px 18px 20px; }
  .hs-track { gap: 20px; }
}

/* The teach page's hero CTA used to be the ONLY red button, patched in here
   while the identical mid-page and form CTAs stayed near-black â€” the same
   action wearing three coats down one page. Red is now the primary everywhere
   on this page (--accent is 4.83:1 against white, the gate it was chosen for),
   so those CTAs carry .btn-accent in the markup and this override is gone.

   .btn-primary keeps one honest job: a filled dark button where red would
   fight its ground â€” the tutor page's hero over a photograph, and the nav. */

/* --- fix: .nav-links a (0,1,1) was beating .btn-primary (0,1,0), leaving the
   nav CTA with ink-on-ink text at ~1.05:1 contrast. Pre-existing since v1. --- */
.nav-links a.btn-primary { color: #fdfcf8; }
.nav-links a.btn-primary:hover { background: var(--ink); color: #fff; }
/* On the dark bar this pill is the one bright object on the page's top rule,
   which makes its label contrast worth getting right rather than nearly right.
   --accent is #E6352E and white on it is 4.27:1 â€” the exact failure the accent
   comment at the top of this file documents and rejects elsewhere. #d92d20 is
   the deeper step that comment settles on, still plainly vermilion, and white
   on it measures 4.83. Scoped to the nav so the button keeps --accent
   everywhere else it appears on cream. */
.nav-links a.btn-accent { background: #d92d20; color: #fff; }
/* ...and the same trap on hover, which the btn-primary line above already had to
   patch: .nav-links a:hover (0,2,1) outranks .btn-accent:hover (0,1,1), so the
   pill would go cream while its label stayed white â€” white on --paper-2 is
   ~1.1:1. Restate it at (0,3,1) and let hover ride the accent ramp to the deep
   token, which is 7.9:1 against white. */
.nav-links a.btn-accent:hover { background: var(--accent-deep); color: #fff; }
/* No .btn-green in the nav â€” the bar uses the quieter a.auth link (above) for
   the same destination, which is already --green-deep text. The pair of
   override lines that used to sit here styled a button this markup never
   renders. */


.hs-head { padding: 48px 0 18px; }
.hs-head h2 { font-size: clamp(28px, 3.4vw, 42px); }
.hs-cap a { margin-top: 9px; }

@media (max-width: 720px) {
  .hs-card, .hs-card.doc { width: 76vw; }
  .hs-cap { width: 100%; }
}

/* ghost CTA sits over moving footage â€” give it a plate so the border reads */
.hero-v5 .btn-ghost {
  background: rgba(255, 255, 255, 0.6);
  -webkit-backdrop-filter: blur(7px);
  backdrop-filter: blur(7px);
  border-color: var(--line-2);
}
.hero-v5 .btn-ghost:hover { background: rgba(255, 255, 255, 0.86); border-color: var(--ink-2); }

/* ============================================================================
   Reduced motion â€” the pinned sections never run, so their JS-driven "current
   step" class never lands. Without this, steps 2-4 and every off-screen
   gallery card stay permanently hidden. Lay them out statically instead.
   ========================================================================= */
/* No scrub: the rail is still drawn (motion.js draws it before it bails), but
   nothing travels â€” every step reads as already reached. */
.no-motion .tr-bead { display: none; }
.no-motion .tr-node { opacity: 1; transform: translate(-50%, -50%); }
.no-motion .tstep-when { opacity: 1 !important; transform: none !important; }
.no-motion .tstep .ws-num,
.no-motion .tstep h3,
.no-motion .tstep p,
.no-motion .tstep .ws-out { opacity: 1 !important; }
.no-motion .tshot { opacity: 1 !important; transform: none !important; }

.no-motion .hs-viewport { overflow-x: auto; -webkit-overflow-scrolling: touch; }
.no-motion .hs-track { transform: none !important; padding-right: 24px; }
.no-motion .hero-scroll { display: none; }

/* the scroll cue pulses forever â€” stop it, and stop the marquees too */
@media (prefers-reduced-motion: reduce) {
  .hero-scroll i::after { animation: none; }
  .mq-track { animation: none !important; }
  * { scroll-behavior: auto !important; }
}

/* ============================================================================
   v6 â€” hero furniture + document lightbox
   ========================================================================= */

/* ------------------------------------------------ hero floating card ---- */
.hero-float {
  position: absolute;
  right: max(24px, calc((100vw - var(--wrap)) / 2));
  top: 50%; transform: translateY(-46%);
  width: min(430px, 38vw);
  z-index: 3;
  pointer-events: none;
}
.hero-float > * { pointer-events: auto; }

.hf-pills { display: flex; gap: 9px; margin-bottom: 18px; }
.hf-pills span {
  display: inline-flex; align-items: center; gap: 7px;
  padding: 9px 15px; border-radius: var(--r-pill);
  background: rgba(255, 255, 255, 0.82);
  -webkit-backdrop-filter: blur(10px); backdrop-filter: blur(10px);
  border: 1px solid rgba(255, 255, 255, 0.9);
  box-shadow: 0 2px 10px rgba(40, 30, 20, 0.09);
  font-size: 14px; font-weight: 600; color: var(--ink-2);
  transition: background 0.45s var(--ease), color 0.45s var(--ease),
              transform 0.45s var(--ease);
}
.hf-pills span svg { width: 15px; height: 15px; flex: none; }
.hf-pills span.on {
  background: var(--primary); color: #fdfcf8;
  border-color: var(--primary); transform: translateY(-1px);
}
.hf-pills span.on svg { color: #fdfcf8; }

.hf-card {
  background: rgba(253, 252, 248, 0.93);
  -webkit-backdrop-filter: blur(16px) saturate(1.15);
  backdrop-filter: blur(16px) saturate(1.15);
  border: 1px solid rgba(255, 255, 255, 0.95);
  border-radius: var(--r-lg);
  padding: 26px 26px 24px;
  box-shadow: 0 3px 12px rgba(40, 30, 20, 0.08), 0 24px 60px rgba(40, 30, 20, 0.15);
}
.hf-card h3 {
  font-family: var(--display); font-weight: 600;
  font-size: clamp(21px, 2.2vw, 27px); line-height: 1.14;
  letter-spacing: -0.018em; margin: 0 0 8px;
}
.hf-card p { font-size: 14.5px; color: var(--muted); line-height: 1.5; margin: 0 0 18px; }
.hf-row { display: flex; align-items: center; gap: 14px; flex-wrap: wrap; }
.hf-btn {
  display: inline-flex; align-items: center; gap: 10px;
  padding: 11px 20px 11px 12px; border-radius: var(--r-pill);
  background: var(--m-bg); color: #f3efe4;
  font-size: 14.5px; font-weight: 600; border: 0; cursor: pointer;
  transition: transform 0.3s var(--ease), box-shadow 0.3s var(--ease);
}
body.tutor .hf-btn { background: var(--ink); }
.hf-btn:hover { transform: translateY(-1px); box-shadow: var(--sh-2); }
.hf-btn i {
  width: 26px; height: 26px; border-radius: 50%; flex: none;
  background: rgba(255, 255, 255, 0.16);
  display: inline-flex; align-items: center; justify-content: center;
}
.hf-btn svg { width: 13px; height: 13px; color: #f3efe4; }
.hf-note { font-size: 13px; color: var(--muted); }

/* ------------------------------------------------ preview / zoom -------- */
.hs-paper { cursor: zoom-in; position: relative; }
.hs-paper::after {
  content: ""; position: absolute; inset: 0; border-radius: inherit;
  background: rgba(20, 20, 15, 0.26);
  opacity: 0; transition: opacity 0.35s var(--ease); z-index: 2;
}
.hs-paper:hover::after { opacity: 1; }
.hs-zoom {
  position: absolute; left: 50%; top: 50%;
  transform: translate(-50%, -50%) scale(0.9);
  z-index: 3; opacity: 0;
  display: inline-flex; align-items: center; gap: 8px;
  padding: 11px 18px; border-radius: var(--r-pill);
  background: rgba(253, 252, 248, 0.96);
  font-size: 13.5px; font-weight: 600; color: var(--ink);
  box-shadow: var(--sh-2); white-space: nowrap;
  transition: opacity 0.35s var(--ease), transform 0.35s var(--ease);
}
.hs-paper:hover .hs-zoom { opacity: 1; transform: translate(-50%, -50%) scale(1); }
.hs-zoom svg { width: 15px; height: 15px; }

.hs-actions { display: flex; align-items: center; gap: 14px; margin-top: 11px; flex-wrap: wrap; }
.hs-actions button,
.hs-actions a {
  display: inline-flex; align-items: center; gap: 7px;
  font-size: 13.5px; font-weight: 600; color: var(--accent-deep);
  background: none; border: 0; padding: 0; cursor: pointer; font-family: inherit;
}
.hs-actions svg { width: 14px; height: 14px; }
.hs-actions .sep { color: var(--line-2); font-weight: 400; }

/* ------------------------------------------------ lightbox -------------- */
.lb {
  position: fixed; inset: 0; z-index: 200;
  display: flex; align-items: center; justify-content: center;
  background: rgba(18, 16, 12, 0.86);
  -webkit-backdrop-filter: blur(6px); backdrop-filter: blur(6px);
  opacity: 0; visibility: hidden;
  transition: opacity 0.34s var(--ease), visibility 0.34s;
}
.lb.on { opacity: 1; visibility: visible; }
/* Every page of the document in one scrolling column. Each page keeps the
   paper's own portrait shape â€” the height is what's constrained, so an A4
   page is never blown up to the width of the viewport and cropped into a
   landscape letterbox â€” and snapping settles the scroll on one page at a
   time so it reads like turning paper rather than a long ribbon. */
.lb-stage {
  position: relative; width: 100%; height: 82vh;
  overflow-y: auto; overflow-x: hidden;
  scroll-snap-type: y mandatory; scroll-behavior: smooth;
  overscroll-behavior: contain; -webkit-overflow-scrolling: touch;
  transform: scale(0.97); transition: transform 0.34s var(--ease);
  scrollbar-width: thin; scrollbar-color: rgba(255,255,255,0.3) transparent;
}
.lb.on .lb-stage { transform: scale(1); }
.lb-stage::-webkit-scrollbar { width: 8px; }
.lb-stage::-webkit-scrollbar-thumb {
  background: rgba(255,255,255,0.26); border-radius: 4px;
}
.lb-doc {
  display: flex; flex-direction: column; align-items: center;
  gap: 20px; padding: calc((82vh - 78vh) / 2) 0;
  min-height: 100%;
}
.lb-page {
  flex: none; scroll-snap-align: center;
  /* A4 until the real ratio is known â€” see build() in site.js */
  aspect-ratio: 1 / 1.4142;
  max-width: 94vw; max-height: 78vh; width: auto; height: auto;
  border-radius: 6px; box-shadow: 0 40px 100px rgba(0, 0, 0, 0.5);
  background: #fff; display: block; cursor: zoom-in;
}
/* Clicking a page trades the whole-page view for legible small print. On a
   narrow screen the fitted page is already the full width, so zooming has to
   push past the viewport and pan â€” hence the 720px floor. Snapping comes off
   while zoomed, since you're now scrolling within a page, not between them. */
.lb-stage.zoom { overflow: auto; scroll-snap-type: none; }
.lb-stage.zoom .lb-page {
  max-width: none; max-height: none; cursor: zoom-out;
  width: min(1080px, max(94vw, 720px));
}
/* video previews are a single element, not a paged document */
.lb-stage.is-video {
  height: auto; overflow: visible; scroll-snap-type: none;
  display: flex; align-items: center; justify-content: center;
}
.lb-video { max-width: 94vw; max-height: 88vh; border-radius: 10px; box-shadow: 0 40px 100px rgba(0,0,0,0.5); background:#000; }

.lb-bar {
  position: absolute; left: 0; right: 0; bottom: 18px; z-index: 2;
  display: flex; align-items: center; justify-content: center; gap: 18px;
  color: rgba(255, 255, 255, 0.82); font-size: 13.5px;
  text-shadow: 0 1px 6px rgba(0,0,0,0.6);
}
.lb-bar b { font-weight: 600; color: #fff; }
.lb-bar a {
  display: inline-flex; align-items: center; gap: 7px;
  color: #fff; font-weight: 600; border-bottom: 1px solid rgba(255,255,255,0.4);
}
.lb-bar a svg { width: 14px; height: 14px; }

.lb-close, .lb-nav {
  position: absolute; z-index: 3;
  width: 46px; height: 46px; border-radius: 50%;
  background: rgba(255, 255, 255, 0.13); border: 1px solid rgba(255,255,255,0.22);
  color: #fff; cursor: pointer;
  display: inline-flex; align-items: center; justify-content: center;
  transition: background 0.25s var(--ease), transform 0.25s var(--ease);
}
.lb-close:hover, .lb-nav:hover { background: rgba(255, 255, 255, 0.26); }
.lb-close { top: 22px; right: 22px; font-size: 22px; line-height: 1; }
.lb-nav.prev { left: 22px; top: 50%; transform: translateY(-50%); }
.lb-nav.next { right: 22px; top: 50%; transform: translateY(-50%); }
.lb-nav svg { width: 20px; height: 20px; }
.lb-nav:hover { transform: translateY(-50%) scale(1.06); }
.lb-nav[hidden] { display: none; }

@media (max-width: 1180px) {
  .hero-float { position: static; transform: none; width: 100%; margin-top: 40px; max-width: 460px; }
  .hero-v5 { min-height: 0; }
}
@media (max-width: 720px) {
  .hf-pills span { padding: 8px 13px; font-size: 13px; }
  .hf-card { padding: 22px 20px 20px; }
  /* A document runs to eight pages, so the arrows have to survive on a
     phone â€” tucked to the edge and darkened, since here they overlap the
     white page rather than the backdrop. */
  .lb-nav { width: 40px; height: 40px; background: rgba(24, 21, 16, 0.58); }
  .lb-nav.prev { left: 6px; }
  .lb-nav.next { right: 6px; }
  .lb-nav svg { width: 18px; height: 18px; }
  /* the hint wraps the bar onto two lines here, and on touch the scroll and
     the tap-to-zoom are self-evident */
  .lb-hint { display: none; }
  .lb-bar { bottom: 10px; font-size: 12.5px; gap: 12px; }
}
@media (prefers-reduced-motion: reduce) {
  .lb, .lb-stage, .lb-page { transition: none; }
  .lb-stage { scroll-behavior: auto; }
}
/* the caption bar floats over the bottom of the stage, so it needs its own
   backing rather than the page showing through behind it */
.lb-video { max-height: 82vh; }
.lb-bar {
  padding: 10px 16px;
  background: linear-gradient(to top, rgba(10,9,6,0.85), transparent);
}
/* --- fix: the nav's five links + CTA stop fitting well before 720px, so the
   button overflowed the viewport between ~721px and ~900px. Collapse to just
   the CTA at 1000px instead. --- */
@media (max-width: 1000px) {
  .nav-links { display: none; }
  .nav-links.keep { display: flex; }
}

/* The rule above assumed .keep held one item. It now carries the login link
   as well, and the pair overflows a 390px viewport by 15px â€” the logo plus
   both actions simply do not fit. Drop the login text and keep the demo CTA:
   the same destination is still in the footer, and "Book a demo" is the one
   action this bar exists for. */
@media (max-width: 480px) {
  .nav-links.keep a.auth { display: none; }
}

/* ============================================================================
   v7 â€” photo hero. The classroom video drops back to an ambient wash and the
   teacher photograph becomes the focal visual, carrying the pills and card.
   ========================================================================= */
.hero-grid {
  display: grid;
  grid-template-columns: 1fr 0.94fr;
  gap: 58px;
  align-items: center;
}
.hero-v5.has-photo .hero-copy { max-width: 600px; }


.hf-photo {
  position: relative;
  margin: 0;
  border-radius: var(--r-xl);
  overflow: hidden;
  aspect-ratio: 4 / 3.95;
  background: var(--cream);
  box-shadow: 0 4px 14px rgba(40, 30, 20, 0.09), 0 34px 80px rgba(40, 30, 20, 0.19);
  border: 1px solid rgba(255, 255, 255, 0.7);
}
.hf-photo > img {
  width: 100%; height: 100%;
  object-fit: cover;
  object-position: 44% 26%;
  display: block;
}
.hf-photo .hf-pills {
  position: absolute; top: 18px; left: 18px; right: 18px;
  margin: 0; z-index: 2;
  flex-wrap: wrap;
}
.hf-photo .hf-card {
  position: absolute; left: 4.5%; right: 4.5%; bottom: 4.5%;
  z-index: 2;
}

/* the pills are navigation now, not decoration */
.hf-pills span {
  cursor: pointer;
  -webkit-user-select: none; user-select: none;
}
.hf-pills span:hover { transform: translateY(-1px); }
.hf-pills span:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
}

@media (max-width: 1080px) {
  .hero-grid { grid-template-columns: 1fr; gap: 40px; }
  .hero-v5.has-photo .hero-copy { max-width: none; }
  .hf-photo { aspect-ratio: 4 / 3.1; max-width: 620px; }
}
@media (max-width: 720px) {
  .hf-photo { aspect-ratio: 4 / 4.3; }
  .hf-photo .hf-pills { top: 12px; left: 12px; right: 12px; }
  .hf-photo .hf-card { left: 3.5%; right: 3.5%; bottom: 3.5%; padding: 18px 16px 16px; }
  .hf-card h3 { font-size: 19px; }
  .hf-card p { font-size: 13.5px; margin-bottom: 14px; }
}

/* ------------------------------------------------ section video --------- */
.secvid { position: absolute; inset: 0; overflow: hidden; z-index: 0; }
.secvid video, .secvid img {
  width: 100%; height: 100%; object-fit: cover; display: block;
}
.secvid video { opacity: 0; transition: opacity 1.1s var(--ease); }
.secvid video.on { opacity: 1; }

/* map animation sits behind the section copy â€” wash it so text stays legible */
.secvid-scrim {
  position: absolute; inset: 0; z-index: 1; display: block;
  background:
    linear-gradient(100deg,
      rgba(238,241,236,0.95) 0%,
      rgba(238,241,236,0.88) 34%,
      rgba(238,241,236,0.60) 62%,
      rgba(238,241,236,0.34) 100%),
    linear-gradient(to bottom,
      rgba(238,241,236,0.72) 0%,
      rgba(238,241,236,0.25) 26%,
      rgba(238,241,236,0.45) 72%,
      rgba(238,241,236,0.9) 100%);
}
@media (max-width: 1000px) {
  .secvid-scrim {
    background:
      linear-gradient(160deg, rgba(238,241,236,0.95) 0%, rgba(238,241,236,0.9) 50%,
        rgba(238,241,236,0.8) 100%),
      linear-gradient(to bottom, rgba(238,241,236,0.7) 0%, rgba(238,241,236,0.35) 30%,
        rgba(238,241,236,0.9) 100%);
  }
}
/* three stats, always one row â€” flex-wrap was breaking them 2 + 1 */
.stars-stats { display: grid; grid-template-columns: repeat(3, 1fr); gap: 26px; align-items: start; }
.stars-head { max-width: 680px; }
@media (max-width: 720px) {
  .stars-stats { grid-template-columns: 1fr 1fr; gap: 22px; }
}

/* ============================================================================
   v7.1 â€” the has-photo scrim override had pushed the right edge from 0.04 to
   0.78, which washed the classroom footage out almost entirely. Revert to the
   v5 values so the video reads again behind the photo card.
   ========================================================================= */

/* Card 35% smaller and pushed right. The grid column shrinks with it so the
   copy keeps the space, and the figure sits hard against the right edge. */
.hero-grid { grid-template-columns: 1fr 0.62fr; gap: 46px; }
.hero-figure { justify-self: end; width: 100%; max-width: 400px; }
.hf-photo { border-radius: var(--r-lg); }
.hf-photo .hf-pills { top: 13px; left: 13px; right: 13px; gap: 6px; }
.hf-photo .hf-pills span { padding: 7px 11px; font-size: 12.5px; gap: 5px; }
.hf-photo .hf-pills span svg { width: 13px; height: 13px; }
.hf-photo .hf-card { left: 4%; right: 4%; bottom: 4%; padding: 18px 18px 17px; }
.hf-photo .hf-card h3 { font-size: clamp(17px, 1.5vw, 20px); margin-bottom: 5px; }
.hf-photo .hf-card p { font-size: 12.5px; margin-bottom: 13px; line-height: 1.45; }
.hf-photo .hf-btn { padding: 8px 15px 8px 9px; font-size: 13px; gap: 8px; }
.hf-photo .hf-btn i { width: 21px; height: 21px; }
.hf-photo .hf-btn svg { width: 11px; height: 11px; }
.hf-photo .hf-note { font-size: 12px; }

@media (max-width: 1080px) {
  .hero-grid { grid-template-columns: 1fr; gap: 36px; }
  .hero-figure { justify-self: start; max-width: 430px; }
}

/* ============================================================================
   v7.2 â€” every background clip reads too faint. One pass over all six scrims,
   lifting the footage roughly 25-35 percentage points while keeping enough
   wash under the headline column that ink-on-video stays legible.
   Text sits in the first ~45% of each gradient; the rest can open right up.
   ========================================================================= */




/* curriculum-map animation */
.secvid-scrim {
  background:
    linear-gradient(100deg,
      rgba(238,241,236,0.90) 0%,
      rgba(238,241,236,0.76) 32%,
      rgba(238,241,236,0.34) 58%,
      rgba(238,241,236,0.06) 100%),
    linear-gradient(to bottom,
      rgba(238,241,236,0.48) 0%,
      rgba(238,241,236,0.04) 24%,
      rgba(238,241,236,0.16) 72%,
      rgba(238,241,236,0.72) 100%);
}
@media (max-width: 1000px) {
  .secvid-scrim {
    background:
      linear-gradient(160deg, rgba(238,241,236,0.92) 0%, rgba(238,241,236,0.84) 50%,
        rgba(238,241,236,0.66) 100%),
      linear-gradient(to bottom, rgba(238,241,236,0.52) 0%, rgba(238,241,236,0.14) 30%,
        rgba(238,241,236,0.74) 100%);
  }
}

/* ============================================================================
   v7.3 â€” keep the footage open, protect the text locally.
   Measured over live video frames, the opened-up scrim left the lede at
   2.2:1 and the hero tags at 1.7:1 (1.07:1 on tutor) against dark patches in
   the clip â€” body copy needs 4.5:1. Rather than re-washing the whole frame,
   a soft elliptical plate sits behind the copy column only. It has no visible
   edge, so the video still reads everywhere else.
   ========================================================================= */
.hero-copy { position: relative; }
.hero-copy::before {
  content: "";
  position: absolute;
  inset: -14% -30% -16% -26%;
  z-index: -1;
  pointer-events: none;
  background: radial-gradient(105% 88% at 24% 50%,
    rgba(243, 239, 229, 0.96) 0%,
    rgba(243, 239, 229, 0.92) 38%,
    rgba(243, 239, 229, 0.72) 62%,
    rgba(243, 239, 229, 0.34) 82%,
    rgba(243, 239, 229, 0) 100%);
}
body.tutor .hero-copy::before {
  background: radial-gradient(105% 88% at 24% 50%,
    rgba(238, 241, 236, 0.96) 0%,
    rgba(238, 241, 236, 0.92) 38%,
    rgba(238, 241, 236, 0.72) 62%,
    rgba(238, 241, 236, 0.34) 82%,
    rgba(238, 241, 236, 0) 100%);
}

/* the two lightest strings sat at ~1.1:1 over the clip's dark patches */
.hero-v5 .hero-tags span { color: var(--ink-2); }
.hero-v5 .hero-rail .hr-l { color: var(--ink-2); }

/* same treatment for the map section's copy over the constellation clip */
.stars-head { position: relative; }
.stars-head::before {
  content: "";
  position: absolute;
  inset: -6% -34% -4% -26%;
  z-index: -1;
  pointer-events: none;
  background: radial-gradient(100% 86% at 26% 50%,
    rgba(238, 241, 236, 0.95) 0%,
    rgba(238, 241, 236, 0.90) 40%,
    rgba(238, 241, 236, 0.66) 64%,
    rgba(238, 241, 236, 0.28) 84%,
    rgba(238, 241, 236, 0) 100%);
}

/* --- fix: the plate's radial gradient reached its box edge before hitting
   zero alpha, so it clipped into a visible rectangle. Widen the box well
   past the copy and land on transparent earlier in the ramp. --- */
.hero-copy::before {
  inset: -70% -120% -70% -60%;
  background: radial-gradient(46% 42% at 30% 50%,
    rgba(243, 239, 229, 0.95) 0%,
    rgba(243, 239, 229, 0.90) 40%,
    rgba(243, 239, 229, 0.62) 62%,
    rgba(243, 239, 229, 0.26) 80%,
    rgba(243, 239, 229, 0) 94%);
}
body.tutor .hero-copy::before {
  background: radial-gradient(46% 42% at 30% 50%,
    rgba(238, 241, 236, 0.95) 0%,
    rgba(238, 241, 236, 0.90) 40%,
    rgba(238, 241, 236, 0.62) 62%,
    rgba(238, 241, 236, 0.26) 80%,
    rgba(238, 241, 236, 0) 94%);
}
.stars-head::before {
  inset: -80% -130% -80% -60%;
  background: radial-gradient(44% 40% at 32% 50%,
    rgba(238, 241, 236, 0.94) 0%,
    rgba(238, 241, 236, 0.88) 40%,
    rgba(238, 241, 236, 0.58) 63%,
    rgba(238, 241, 236, 0.22) 81%,
    rgba(238, 241, 236, 0) 94%);
}

/* ============================================================================
   v7.4
   Â· card moves fully into the right third of the viewport (out of .wrap)
   Â· every scrim / plate alpha and every backdrop blur cut by 15%
   ========================================================================= */

/* Break the figure out of the content column so it can sit against the
   viewport edge rather than the 1180px wrap. Above 1080px only â€” below that
   it returns to normal flow. */
@media (min-width: 1081px) {
  .hero-grid { grid-template-columns: 1fr; }
  .hero-v5.has-photo .hero-copy { max-width: 620px; }
  .hero-figure {
    position: absolute;
    top: 50%;
    right: max(24px, 3vw);
    transform: translateY(-50%);
    width: min(400px, 29vw);
    max-width: none;
    z-index: 3;
  }
}

.secvid-scrim {
  background:
    linear-gradient(100deg,
      rgba(238,241,236,0.765) 0%,
      rgba(238,241,236,0.646) 32%,
      rgba(238,241,236,0.289) 58%,
      rgba(238,241,236,0.051) 100%),
    linear-gradient(to bottom,
      rgba(238,241,236,0.408) 0%,
      rgba(238,241,236,0.034) 24%,
      rgba(238,241,236,0.136) 72%,
      rgba(238,241,236,0.612) 100%);
}
.hero-copy::before {
  background: radial-gradient(
    calc(46% * var(--hero-copy-plate-size)) calc(42% * var(--hero-copy-plate-size)) at 30% 50%,
    rgba(243,239,229,calc(0.808 * var(--hero-copy-plate))) 0%,
    rgba(243,239,229,calc(0.765 * var(--hero-copy-plate))) 40%,
    rgba(243,239,229,calc(0.527 * var(--hero-copy-plate))) 62%,
    rgba(243,239,229,calc(0.221 * var(--hero-copy-plate))) 80%,
    rgba(243,239,229,0) 94%);
}
/* Same knob as the cream skin â€” this rule used to hardcode the alphas, so
   --hero-copy-plate silently did nothing on the one page that wears the
   busiest footage. --hero-copy-plate-size widens/tightens the ellipse: the
   plate's *reach* is what reads as milk spreading across the frame, and it
   is a separate decision from how opaque the plate is at its centre. */
body.tutor .hero-copy::before {
  background: radial-gradient(
    calc(46% * var(--hero-copy-plate-size)) calc(42% * var(--hero-copy-plate-size)) at 30% 50%,
    rgba(238,241,236,calc(0.808 * var(--hero-copy-plate))) 0%,
    rgba(238,241,236,calc(0.765 * var(--hero-copy-plate))) 40%,
    rgba(238,241,236,calc(0.527 * var(--hero-copy-plate))) 62%,
    rgba(238,241,236,calc(0.221 * var(--hero-copy-plate))) 80%,
    rgba(238,241,236,0) 94%);
}
.stars-head::before {
  background: radial-gradient(44% 40% at 32% 50%,
    rgba(238,241,236,0.799) 0%,
    rgba(238,241,236,0.748) 40%,
    rgba(238,241,236,0.493) 63%,
    rgba(238,241,236,0.187) 81%,
    rgba(238,241,236,0) 94%);
}

/* backdrop blurs, 15% lower */
.hf-card {
  -webkit-backdrop-filter: blur(13.6px) saturate(1.15);
  backdrop-filter: blur(13.6px) saturate(1.15);
}
.hf-pills span {
  -webkit-backdrop-filter: blur(8.5px);
  backdrop-filter: blur(8.5px);
}
.hero-v5 .btn-ghost {
  -webkit-backdrop-filter: blur(5.95px);
  backdrop-filter: blur(5.95px);
}

/* --- fix: .hero-figure was absolutely positioned inside .wrap, which is
   position:relative, so `right` anchored to the 1180px content column rather
   than the viewport and the card never cleared the middle third. It is now a
   sibling of .wrap; the same rule governs the tutor page's .hero-float. --- */
@media (min-width: 1081px) {
  .hero-figure,
  .hero-float {
    position: absolute;
    top: 50%;
    right: max(28px, 3.4vw);
    left: auto;
    transform: translateY(-50%);
    width: min(400px, 28vw);
    max-width: none;
    margin: 0;
    z-index: 3;
  }
  /* the copy column keeps the left two thirds to itself */
  .hero-v5 .hero-copy { max-width: min(620px, 56vw); }

  /* v20 â€” the planner window moves off the rider's head. It used to be
     vertically centred on the hero (top:50%), which put its bottom edge at
     roughly y=666 on an 846px-tall hero â€” well into where both the rider and
     the schoolhouse roofline reach. Measured against the actual clip (6
     frames across the loop, .hero-figure hidden): the rider's head tops out
     at yâ‰ˆ440 at its highest point in the push-in, the school's roofline at
     yâ‰ˆ455. The window now docks under the nav instead, its top pinned a
     fixed distance below it so the whole card â€” trimmed to 4 rows for this â€”
     clears both with margin to spare. Re-measure both figures if the clip or
     the row count changes. */
  .hero-figure.uid-sky {
    top: 90px;
    bottom: auto;
    transform: none;
  }
}
@media (max-width: 1080px) {
  .hero-figure, .hero-float { position: static; transform: none; width: 100%; max-width: 430px; }
}

/* --- ...and because .hero-v5 is a flex row, the figure became a flex sibling
   of .wrap once it moved out, squashing it to ~130px on phones. Stack them
   below the split breakpoint and give the figure the wrap's own padding. --- */
@media (max-width: 1080px) {
  .hero-v5 { flex-direction: column; align-items: stretch; justify-content: center; }
  .hero-figure, .hero-float {
    position: static; transform: none;
    width: 100%; max-width: 486px;
    margin: 34px 0 0; padding: 0 28px;
  }
  .hero-v5 .hero-copy { max-width: none; }
}
@media (max-width: 720px) {
  .hero-figure, .hero-float { padding: 0 20px; margin-top: 28px; }
}

/* ============================================================================
   v7.5 â€” stronger hero type instead of stronger wash.
   The lede and tags were --ink-2/muted at 400-600 weight over live footage;
   they now sit at full ink with a touch more weight, and every hero string
   carries a soft paper-coloured halo â€” a light text-shadow lifts dark
   letterforms off a busy background without washing the clip back out.

   v11: the WIDE glows are gone (h1 had 18px + 46px, the lede 12px + 30px).
   Three stacked cream halos around near-black type ate the letterform edges
   and were the reason the hero read soft next to a flat-colour reference â€”
   the type is vector and renders at full device resolution, so nothing else
   was blurring it. Only the tight 1px lift remains, which is what actually
   separates the glyph from the footage. Safe to drop: the copy plate
   (--hero-copy-plate) now does the lifting these glows were added for, and
   the h1 measures 14.9:1 worst-tenth against a 3.0 floor.
   ========================================================================= */
.hero-v5 h1 {
  text-shadow: 0 1px 2px rgba(243, 239, 229, 0.55);
}
.hero-v5 .hs-lede {
  color: var(--ink);
  font-weight: 550;
  text-shadow: 0 1px 2px rgba(243, 239, 229, 0.7);
}
.hero-v5 .hero-rail .hr-l {
  color: var(--ink);
  font-weight: 650;
  text-shadow: 0 1px 2px rgba(243, 239, 229, 0.7);
}
/* v22 â€” the tags are the one string the reframing moved onto a busier plate.
   Showing the whole 16:9 frame brings the treeline and the schoolhouse roof up
   into the band this row sits in, and _src/verify-hero.py measures it at
   4.5:1 against a 4.5 floor at 1440 (5.8:1 when the frame was cropped).

   The halo is DENSER, NOT WIDER â€” 2px stays 2px. v11 above removed the wide
   glows because stacked cream around near-black type ate the letterform edges
   and read as softness, which is the opposite of what this hero is for. Alpha
   costs nothing in sharpness; radius does. Note the script composites glyph
   colour against the plate and cannot see a text-shadow at all, so the halo is
   uncounted headroom on top of that 4.5. If a future clip needs real margin
   here, the lever is --hero-copy-plate (~0.2 is a haze, not milk), not a
   wider shadow. */
.hero-v5 .hero-tags span {
  color: var(--ink);
  font-weight: 650;
  text-shadow: 0 1px 2px rgba(243, 239, 229, 0.95);
}

/* tutor skin: same halo in its own paper colour */
body.tutor .hero-v5 h1 {
  text-shadow: 0 1px 2px rgba(238, 241, 236, 0.55);
}
body.tutor .hero-v5 .hs-lede {
  text-shadow: 0 1px 2px rgba(238, 241, 236, 0.7);
}
body.tutor .hero-v5 .hero-tags span,
body.tutor .hero-v5 .hero-rail .hr-l {
  text-shadow: 0 1px 2px rgba(238, 241, 236, 0.7);
}

/* map section copy gets the same treatment over the constellation clip */
.stars-head h2 { text-shadow: 0 1px 2px rgba(238,241,236,0.55), 0 0 18px rgba(238,241,236,0.7); }
.stars-head > p { color: var(--ink); font-weight: 550;
  text-shadow: 0 1px 2px rgba(238,241,236,0.7), 0 0 12px rgba(238,241,236,0.8); }
.stars-stats div span { color: var(--ink-2); font-weight: 600;
  text-shadow: 0 1px 2px rgba(238,241,236,0.7), 0 0 10px rgba(238,241,236,0.8); }

/* ============================================================================
   v7.6 â€” show the whole teacher photo. The frame was near-square over a
   1.83:1 image, so cover-cropping discarded both sides (the blackboard and
   the shelves). The figure now takes the image's own ratio, and the glass
   card docks BELOW the photo instead of floating over it â€” nothing left to
   crop, nothing covered.
   ========================================================================= */
.hf-photo { aspect-ratio: auto; display: flex; flex-direction: column; }
.hf-photo > img {
  width: 100%; height: auto;
  aspect-ratio: auto;
  object-fit: contain;
  object-position: center;
  flex: none;
}
.hf-photo .hf-card {
  position: static;
  border-radius: 0;
  border: 0;
  border-top: 1px solid var(--line);
  box-shadow: none;
  -webkit-backdrop-filter: none;
  backdrop-filter: none;
  background: rgba(253, 252, 248, 0.97);
  padding: 18px 20px 18px;
}
/* pills keep floating over the picture itself */
.hf-photo .hf-pills { top: 12px; left: 12px; right: 12px; }

/* the photo is shorter now, so the column can afford to be a little wider */
@media (min-width: 1081px) {
  .hero-figure { width: min(440px, 31vw); }
}
@media (max-width: 1080px) {
  .hf-photo { aspect-ratio: auto; }
}
@media (max-width: 720px) {
  .hf-photo { aspect-ratio: auto; }
  .hf-photo .hf-card { position: static; padding: 16px 16px 15px; }
}

/* ============================================================================
   v8 â€” animated planner window in the hero (replaces the teacher photo).
   A hand-built mock of the planner "generating" a real Stage 4 plan: rows
   cascade in, badges pop, loop. All strings are true of the sample plan.
   Everything is visible by default â€” GSAP only animates FROM hidden, so
   no-JS and reduced-motion visitors see the completed state.
   ========================================================================= */
.uidemo {
  position: relative;
  border-radius: 18px 18px 0 0;
  padding: 50px 14px 16px;   /* top padding clears the floating pills */
  overflow: hidden;
  color: #f7f7f7;
  background:
    linear-gradient(rgba(255,255,255,0.04) 1px, transparent 1px),
    linear-gradient(90deg, rgba(255,255,255,0.04) 1px, transparent 1px),
    radial-gradient(130% 150% at 82% 8%, #2a2519 0%, #171712 55%, #14140f 100%);
  background-size: 40px 40px, 40px 40px, cover;
}
.ud-head {
  display: flex; align-items: center; justify-content: space-between; gap: 10px;
  background: rgba(255,255,255,0.07); border: 1px solid rgba(255,255,255,0.09);
  border-radius: 11px; padding: 9px 9px 9px 13px;
  font-size: 13px; font-weight: 700;
}
/* Cream, not the brand red. On the forest board the red chip measured 1.21:1 â€”
   and red-on-green is the deuteranopia collision, so for ~5% of men the button
   disappeared entirely. Cream is what the app itself draws here, it measures
   16.8:1 against the ink label, and it leaves the hero with exactly one red
   object: the real CTA below the card. */
.ud-gen {
  background: #f7f4ee; color: #1c1c16; border: 0; border-radius: 9px;
  padding: 7px 14px; font-weight: 700; font-size: 12.5px; font-family: inherit;
  pointer-events: none;   /* decorative â€” the card's real CTA sits below */
}
.ud-row {
  display: flex; align-items: center; gap: 9px;
  background: rgba(255,255,255,0.075); border: 1px solid rgba(255,255,255,0.06);
  border-radius: 11px; padding: 9px 12px; margin-top: 8px;
  font-size: 12.5px; font-weight: 600; color: #e9e4d6;
}
.ud-row svg { width: 15px; height: 15px; flex: none; color: #8fbf95; }
.ud-row.alt svg { color: #d8b05a; }
.ud-badge {
  position: absolute; z-index: 2;
  display: flex; align-items: center; gap: 8px;
  background: #fff; color: var(--ink);
  border-radius: 12px; padding: 8px 12px;
  box-shadow: 0 10px 30px rgba(20, 15, 5, 0.3);
  font-size: 11px; line-height: 1.15;
}
.ud-badge b { display: block; font-size: 12.5px; }
.ud-badge > span span { display: block; color: var(--muted); font-weight: 500; }
.ud-badge svg { width: 17px; height: 17px; flex: none; }
.ud-badge.b1 { left: 9px; bottom: 148px; }
.ud-badge.b2 { right: 9px; bottom: 62px; }

/* ============================================================================
   v7 â€” the dusk band. The Marble trick: the section below the hero is the
   SAME classroom footage graded to evening, so scrolling reads as the day
   ending â€” which is what the copy is about. Hero bottom no longer fades to
   paper on this page; it hands off into darkness via the shared gradient.
   ========================================================================= */
.dusk {
  position: relative;
  min-height: min(88vh, 820px);
  display: flex; align-items: center; justify-content: center;
  overflow: hidden; isolation: isolate;
  background: #16130d;
}
.dusk-media { position: absolute; inset: 0; z-index: 0; }
.dusk-media video, .dusk-media img {
  position: absolute; inset: 0; width: 100%; height: 100%;
  object-fit: cover; display: block;
}
.dusk-media video { opacity: 0; transition: opacity 1.2s var(--ease); }
.dusk-media video.on { opacity: 1; }
.dusk-scrim {
  position: absolute; inset: 0; z-index: 1;
  background:
    /* dusk falling: the cream of the hero dies out across the top edge */
    linear-gradient(to bottom,
      var(--paper) 0%,
      color-mix(in srgb, var(--paper) 42%, transparent) 7%,
      transparent 20%),
    /* readability pool behind the copy */
    radial-gradient(58% 62% at 50% 54%, rgba(16, 13, 9, 0.66), rgba(16, 13, 9, 0.18) 78%),
    /* and night settling at the bottom, handing back to the cream proof strip */
    linear-gradient(to bottom, transparent 78%, rgba(16, 13, 9, 0.8) 100%);
}
.dusk-in {
  position: relative; z-index: 2;
  text-align: center; max-width: 880px;
  padding: 120px 24px;
}
.dusk h2 {
  font-family: var(--display);
  font-size: clamp(40px, 6vw, 78px);
  font-weight: 600; letter-spacing: -0.025em; line-height: 1.02;
  color: #f6f1e5; margin: 0 0 30px;
}
.dusk h2 em {
  font-style: italic; font-weight: 500;
  color: #a8cabe;
}
.dusk-in p {
  font-size: clamp(16px, 1.8vw, 19px);
  line-height: 1.7; margin: 0 auto;
  color: rgba(243, 239, 228, 0.82);
  max-width: 62ch;
}
.dusk-in p b { color: #fff; font-weight: 600; }


@media (max-width: 720px) {
  .dusk { min-height: 0; }
  .dusk-in { padding: 96px 20px; }
}
/* tighter dusk seam â€” the haze band was reading too thick */
.dusk-scrim {
  background:
    linear-gradient(to bottom,
      var(--paper) 0%,
      color-mix(in srgb, var(--paper) 34%, transparent) 4.5%,
      transparent 13%),
    radial-gradient(58% 62% at 50% 54%, rgba(16, 13, 9, 0.66), rgba(16, 13, 9, 0.18) 78%),
    linear-gradient(to bottom, transparent 78%, rgba(16, 13, 9, 0.8) 100%);
}

/* ============================================================================
   v9 â€” the planner window stands alone as warm glass over the classroom.
   Pills and the docked caption card are gone; the window is translucent with
   a backdrop blur so the footage reads through it, and rows type themselves.
   ========================================================================= */
.hf-photo { background: transparent; border: 0; box-shadow: none; overflow: visible; }
.uidemo {
  border-radius: 18px;
  padding: 14px 14px 16px;
  background:
    linear-gradient(rgba(255,255,255,0.05) 1px, transparent 1px),
    linear-gradient(90deg, rgba(255,255,255,0.05) 1px, transparent 1px),
    linear-gradient(160deg, rgba(90, 67, 27, 0.05), rgba(163, 132, 46, 0.06) 60%, rgba(119, 83, 12, 0.02));
  background-size: 40px 40px, 40px 40px, cover;
  -webkit-backdrop-filter: blur(13px) saturate(1.12);
  backdrop-filter: blur(13px) saturate(1.12);
  border: 1px solid rgba(255, 255, 255, 0.16);
  box-shadow: 0 24px 70px rgba(30, 20, 8, 0.35);
}
.ud-row .ud-txt { min-height: 1.2em; }
.ud-cursor {
  display: inline-block; width: 2px; height: 1.05em;
  background: #ffffff; margin-left: 2px; vertical-align: text-bottom;
  animation: udblink 0.9s steps(1) infinite;
}
@keyframes udblink { 0%, 55% { opacity: 1; } 56%, 100% { opacity: 0; } }
@media (prefers-reduced-motion: reduce) { .ud-cursor { display: none; } }
.ud-badge.b1 { left: 9px; bottom: 132px; }
.ud-badge.b2 { right: 9px; bottom: 54px; }

/* ============================================================================
   v10 â€” metric bubbles float OUTSIDE the window, clear of the plan text.

   They used to sit inside the frame, on top of the typing rows. Pushing them
   out needed real gutters: the figure box used to be exactly as wide as the
   window, so an "outside" bubble either overlapped the rows anyway or ran off
   the viewport. The figure is now wider than the window it contains, and the
   window is inset within it â€” creating a ~140px gutter on each side that the
   bubbles occupy without ever touching a row. The window's own size is
   unchanged; the extra width is empty space over the classroom footage.
   ------------------------------------------------------------------------- */
@media (min-width: 1081px) {
  /* Figure = window + a gutter each side. The window keeps its working width
     (rows stay on one line); the extra is gutter. Each bubble needs only ~80px
     of gutter now, because it straddles the window's edge â€” inner third over
     the frame, outer two-thirds hanging off it â€” instead of clearing it. */
  .hero-figure { width: min(660px, 46vw); right: max(10px, 1vw); }
  .hf-photo > .uidemo { margin: 0 84px; }
}
@media (min-width: 1400px) {
  .hero-figure { width: min(716px, 48vw); }
  .hf-photo > .uidemo { margin: 0 92px; }
}
/* 1081â€“1240px: the headline column reaches far enough right that a full-width
   left gutter would put the bubble on top of the h1. Narrow the figure and its
   gutters; the bubble offsets for this range are set at the end of the file,
   after the unconditional .ud-badge rules they need to override. */
@media (min-width: 1081px) and (max-width: 1240px) {
  /* All the gutter goes to the RIGHT: both bubbles sit on that edge here (the
     headline column crowds the left), so the left gutter is dead space better
     spent on the window â€” the wider window is what keeps rows on one line. */
  .hero-figure { width: min(600px, 50vw); }
  .hf-photo > .uidemo { margin: 0 108px 0 0; }
}
/* The bubbles are children of .hf-photo (not .uidemo) so they position against
   the wider figure box and land in the gutters beside the window. */
.hf-photo { position: relative; }

.ud-badge {
  /* max-content + flex:none: without both, an absolutely-positioned bubble
     resolves its width against the containing block and collapses to ~40px
     once anchored outside the window. */
  white-space: nowrap;
  width: max-content;
  max-width: none;
  /* .hf-photo is a flex column: without these the bubble stretches to the
     full cross-axis height and its box swallows the rows underneath it. */
  flex: none;
  align-self: flex-start;
  height: max-content;
  left: auto; right: auto; bottom: auto;
}
/* Anchored to the FIGURE's edges (the .hf-photo box), not the window's, so the
   offset controls how far each bubble hangs off the frame. The window is inset
   84â€“92px within the figure, so these offsets land each bubble ON the window's
   edge: ~25px of it overlaps the frame, the rest hangs outside. That's the
   reference look â€” a clip attached to the window, not a card floating beside
   it. The overlap is bounded by the rows' dead margin: row text begins ~52px
   inside the border, and b1's drift carries it ~11px right of its resting
   place, so 26px keeps its far edge clear of the first glyph.

   b1 is deliberately kept BELOW the head band. The "Science Â· Stage 4" heading
   starts only ~29px inside the border â€” half the rows' margin â€” so a bubble at
   that height collides with it even at this offset. Sitting it against the row
   stack, in the gap between rows, gives the drift room to move. */
/* Both offsets came down 29px when the browser-chrome bar was removed and the
   row stack moved up by exactly that much â€” the bubbles are anchored to the
   rows, so they had to travel with them. */
.ud-badge.b1 { left: 20px; right: auto; top: 89px; }
.ud-badge.b2 { right: 42px; left: auto; top: 271px; }

/* Continuous drift â€” the bubbles are never still, which is what makes the
   window read as a live product rather than a screenshot. GSAP's beat
   emphasis animates box-shadow only, so it never cancels these transforms. */
/* Four-point paths rather than a two-point ping-pong: the bubble never
   retraces its own line, so the motion reads as drifting rather than sliding.
   The small rotation is what sells it as hanging in the air â€” a pure
   translation looks mechanical at this amplitude. Travel is kept under ~20px
   so the bubble's overlap with the window edge never fully detaches. */
/* b1's path is biased UPWARD (every keyframe has a negative or zero Y): it sits
   just above a row of plan text, so any downward travel puts it on the glyphs.
   Biasing the path is more robust than tuning each breakpoint's `top`, since
   the row heights change as rows wrap at narrower widths. */
@keyframes udfloat1 {
  0%, 100% { transform: translate3d(0, 0, 0) rotate(0deg); }
  25%      { transform: translate3d(-9px, -13px, 0) rotate(-1.4deg); }
  50%      { transform: translate3d(-3px, -21px, 0) rotate(0.5deg); }
  75%      { transform: translate3d(-12px, -9px, 0) rotate(1.2deg); }
}
@keyframes udfloat2 {
  0%, 100% { transform: translate3d(0, 0, 0) rotate(0deg); }
  25%      { transform: translate3d(11px, 10px, 0) rotate(1.5deg); }
  50%      { transform: translate3d(4px, 18px, 0) rotate(-0.6deg); }
  75%      { transform: translate3d(12px, 6px, 0) rotate(-1.3deg); }
}
.ud-badge.b1 { animation: udfloat1 5.2s ease-in-out infinite; }
.ud-badge.b2 { animation: udfloat2 6.1s ease-in-out infinite 0.7s; }

@media (prefers-reduced-motion: reduce) {
  .ud-badge { animation: none !important; }
}

/* Stacked layout (window is full-width and centred, no side gutters): tuck the
   bubbles into the top-left and bottom-right corners, clear of every row. */
/* Stacked layout: the window fills the figure, so there are no side gutters.
   Park the bubbles above the top edge and below the bottom edge â€” clear of
   every row â€” and give the figure vertical room for the overhang. */
@media (max-width: 1080px) {
  /* No side gutters here, so the bubbles straddle the window's TOP and BOTTOM
     edges instead â€” same half-on/half-off look, rotated to the axis that has
     room. The padding gives the figure space for the overhang. */
  .hf-photo { padding: 30px 0 30px; }
  /* b1 straddles the TOP edge. The window spans nearly the full viewport here,
     so there is no side gutter to hang off, and it drifts DOWN by up to 20px
     mid-cycle â€” the resting overhang absorbs that. */
  .ud-badge.b1 { right: auto; left: 10px; top: 14px; }
  .ud-badge.b2 { left: auto; right: 14px; top: auto; bottom: 8px; }
}

/* ============================================================================
   v7b â€” the band is now a SUNDOWNER (bright warm dust haze), not night.
   Warm base, soft warm pool behind the copy, gentler seam, and the coded
   paper plane that glides across like Marble's fish.
   ========================================================================= */
.dusk { background: #6d5233; min-height: min(82vh, 760px); }
.dusk-media video { opacity: 0; }
.dusk-media video.on { opacity: 1; }
.dusk-scrim {
  background:
    linear-gradient(to bottom,
      var(--paper) 0%,
      color-mix(in srgb, var(--paper) 30%, transparent) 4%,
      transparent 12%),
    radial-gradient(56% 58% at 50% 52%, rgba(52, 36, 18, 0.52), rgba(52, 36, 18, 0.12) 76%),
    linear-gradient(to bottom, transparent 82%, color-mix(in srgb, var(--paper) 88%, transparent) 100%);
}
.dusk h2 { color: #fdf9ef; text-shadow: 0 2px 26px rgba(52, 36, 18, 0.55); }
.dusk h2 em { color: #ffe9c4; }
.dusk-in p { color: rgba(253, 249, 239, 0.93); text-shadow: 0 1px 16px rgba(52, 36, 18, 0.5); }
.dusk-in p b { color: #fff; }

/* ----------------------------------------------------------------------------
   v7c â€” Marble-style scale contrast in the band. The heading carries the
   statement and the body recedes to a supporting note: bigger display, smaller
   copy, and both lifted to near-white so they read cleanly off the warm haze.
   -------------------------------------------------------------------------- */
.dusk h2 {
  font-size: clamp(46px, 7.6vw, 104px);
  line-height: 0.98;
  letter-spacing: -0.03em;
  color: #fffdf8;
  text-shadow: 0 2px 30px rgba(52, 36, 18, 0.5);
  margin: 0 0 34px;
}
.dusk h2 em { color: #fff4dc; }
.dusk-in p {
  font-size: clamp(15px, 1.35vw, 17px);
  line-height: 1.72;
  max-width: 58ch;
  color: #fffdf8;
  text-shadow: 0 1px 18px rgba(52, 36, 18, 0.55);
}
.dusk-in p b { color: #fff; font-weight: 600; }
.dusk-in { max-width: 940px; }

@media (max-width: 720px) {
  .dusk h2 { font-size: clamp(38px, 10vw, 56px); margin-bottom: 26px; }
  .dusk p { font-size: 15px; max-width: 46ch; }
}

/* Mid-width badge offsets â€” last in the file so they beat the unconditional
   .ud-badge.b1/.b2 placement above. Both bubbles sit on the RIGHT edge in this
   range: the headline column runs close enough to the figure that anything on
   the left would land on the h1 or the lede. */
@media (min-width: 1081px) and (max-width: 1240px) {
  .uidemo .ud-txt { font-size: 12.5px; }
  /* b1 sits above the rows, not beside one. The window is too narrow here to
     hold the plan text on one line, so rows wrap and grow as they type â€” every
     row-height slot is clear at some moments and covered at others. The band
     above the first row is the one place with a fixed height. */
  .ud-badge.b1 { left: auto; right: 62px; top: 2px; }
  /* b2 straddles the window's BOTTOM edge from inside the figure box: the hero
     section clips overflow, so a negative offset here would cut the bubble
     rather than let it overhang. The last rows are empty for most of the loop,
     so the bite is over blank panel. */
  .ud-badge.b2 { right: 62px; left: auto; top: auto; bottom: 8px; }
}

/* ============================================================================
   v8 â€” the band now carries two blocks on ONE continuous background: the
   statement, then curriculum coverage. Card treatment is the measured pick:
   light glass over a darkened pool (the design does not change, the footage
   gets out of the way), cream type, and each icon in one of the app's own
   hues lifted far enough in luminance to sit on a dark ground.

   Measured on the live frame, worst tenth of pixels: heading and body clear
   4.5:1; the icon tints clear the 3:1 non-text floor. See docs/contrast.md.
   ========================================================================= */
.dusk {
  display: block;            /* was a centring flex box â€” now two stacked blocks */
  min-height: 0;
}
.dusk-in {
  max-width: 940px; margin: 0 auto;
  padding: clamp(104px, 14vh, 156px) 24px clamp(52px, 7vh, 86px);
}

/* The mock was tuned at a 25% knock-back, but the mock had no scrim beneath it.
   Live, the band already carries a scrim AND the coverage pool, and the source
   clip is genuinely dark (full-frame mean rgb(77,53,29)) â€” three dark layers
   stacked read as night, which is the note this band already failed once. So:
   lift the footage itself and take only a token knock-back here. */
.dusk-media video, .dusk-media img {
  filter: brightness(1.2) saturate(1.06);
  /* right of centre is where the last light actually falls in this clip:
     column 4/5 means rgb(116,87,56) against a rgb(77,53,29) whole frame */
  object-position: 60% 40%;
}
.dusk-dim {
  position: absolute; inset: 0; z-index: 2;
  background: rgba(30, 20, 9, 0.08);
  pointer-events: none;
}

/* â”€â”€ curriculum coverage â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€ */
/* The statement block and the coverage heading were reading as one run-on
   column â€” .dusk-in's bottom padding was carrying the whole gap on its own.
   Give the coverage block its own top padding so the eyebrow rule has room to
   register as the start of a new section. The pool's -70px top inset still
   sits above the eyebrow, so the darkened ground starts before the type. */
.paths { position: relative; padding: clamp(56px, 7vh, 92px) 0 clamp(96px, 12vh, 132px); }
/* The pool has to reach the OUTER cards, not just the middle of the row. At 62%
   wide it had faded out by the right edge, and the fourth card â€” sitting over
   the brightest part of the clip â€” measured 4.1:1 while its neighbours sat near
   7. Wide, and with a floor rather than a fade to transparent at the edges. */
.paths-pool {
  position: absolute; inset: -70px 0 -40px; z-index: 3;
  background: radial-gradient(104% 78% at 50% 56%,
    rgba(24, 16, 7, 0.5), rgba(24, 16, 7, 0.34) 58%,
    rgba(24, 16, 7, 0.22) 84%, rgba(24, 16, 7, 0.14));
  /* the gradient no longer ends at transparent, so the BOX edge would show as a
     hard horizontal seam across the band â€” fade the top and bottom off */
  -webkit-mask-image: linear-gradient(to bottom, transparent 0, #000 15%, #000 84%, transparent 100%);
          mask-image: linear-gradient(to bottom, transparent 0, #000 15%, #000 84%, transparent 100%);
  pointer-events: none;
}
.paths-in { position: relative; z-index: 5; }

.paths-head { text-align: center; max-width: 760px; margin: 0 auto 52px; }
/* The band's paragraph rules are scoped to `.dusk-in` (the statement copy), not
   `.dusk` â€” as `.dusk p` they also matched every <p> down here in .paths, and
   their 58ch reading measure combined with these rules' `margin: 0` pinned the
   eyebrow and the lede to the LEFT of the centred .paths-head instead of
   centring them. Keep new paragraph styling inside `.dusk-in`. */
.dusk .paths-eyebrow {
  display: flex; align-items: center; justify-content: center; gap: 10px;
  font-size: 11.5px; letter-spacing: 0.18em; text-transform: uppercase;
  font-weight: 700; color: #ffe9c4; margin: 0 0 16px;
}
.dusk .paths-eyebrow::before, .dusk .paths-eyebrow::after {
  content: ""; height: 1px; width: 26px; background: rgba(255, 233, 196, 0.5);
}
.paths-head h3 {
  font-family: var(--display);
  font-size: clamp(22px, 2.8vw, 34px);
  font-weight: 600; line-height: 1.05; letter-spacing: -0.024em;
  color: #fffdf8; margin: 0; text-wrap: balance;
  text-shadow: 0 2px 24px rgba(52, 36, 18, 0.5);
}
.paths-head h3 em { font-style: italic; font-weight: 500; color: #ffe9c4; }
.paths-head p {
  margin: 0; font-size: 16px; line-height: 1.65;
  color: rgba(253, 249, 239, 0.9);
  text-shadow: 0 1px 14px rgba(52, 36, 18, 0.45);
}

.paths-grid {
  display: grid; grid-template-columns: repeat(4, 1fr);
  gap: 18px; max-width: 1180px; margin: 0 auto;
}

/* light glass â€” unchanged from the current design; the pool behind it is
   what buys the contrast */
.pathc {
  position: relative; border-radius: 20px; padding: 26px 24px 24px;
  background: rgba(253, 249, 239, 0.16);
  border: 1px solid rgba(253, 249, 239, 0.3);
  -webkit-backdrop-filter: blur(15px) saturate(1.2);
  backdrop-filter: blur(15px) saturate(1.2);
  box-shadow: 0 16px 40px rgba(28, 18, 6, 0.28);
  /* The hover lift rides the independent `translate` property, NOT `transform`.
     These cards carry data-fade, so GSAP writes `transform` on reveal â€” and a
     CSS transition on the same property chases GSAP frame by frame and strands
     the card partway (measured: stuck at y=36.6px, never resolving). Separate
     properties compose instead of fighting. */
  transition: translate 0.4s var(--ease), background 0.4s var(--ease),
              border-color 0.4s var(--ease);
}
.pathc:hover {
  translate: 0 -4px;
  background: rgba(253, 249, 239, 0.21);
  border-color: rgba(253, 249, 239, 0.42);
}
/* one app hue per pathway, lifted for a dark ground: terracotta â†’ apricot,
   maths blue â†’ sky, science green â†’ sage, library navy â†’ pale slate */
.pathc-cam { --ic: 235, 172, 126; }
.pathc-low { --ic: 163, 199, 236; }
.pathc-pyp { --ic: 158, 209, 174; }
.pathc-myp { --ic: 186, 201, 224; }

/* The icon chip is deliberately NOT translucent to the footage. When it was, the
   card sitting over the brightest patch of the clip dropped its glyph to 2.2:1 â€”
   the tint tracked the background instead of separating from it. A controlled
   dark chip holds every icon well clear wherever its card happens to land. */
.pathc-ico {
  width: 42px; height: 42px; border-radius: 12px;
  display: grid; place-items: center; margin-bottom: 18px;
  background: rgba(22, 15, 7, 0.5);
  border: 1px solid rgba(var(--ic), 0.45);
}
.pathc-ico svg { width: 21px; height: 21px; color: rgb(var(--ic)); }
.pathc h4 {
  font-family: var(--display); font-weight: 600;
  font-size: 19.5px; letter-spacing: -0.012em; line-height: 1.2;
  color: #fff; margin: 0 0 6px;
}
.pathc p { margin: 0; font-size: 13.5px; line-height: 1.55; color: rgba(253, 249, 239, 0.9); }

.pathc-status {
  position: absolute; top: 22px; right: 22px;
  font-size: 10.5px; font-weight: 700; letter-spacing: 0.1em;
  text-transform: uppercase; padding: 5px 10px; border-radius: 999px;
}
/* green keeps meaning "on", exactly as it does inside the product */
.pathc-status.live {
  background: rgba(158, 209, 174, 0.24); color: #d9f2e1;
  border: 1px solid rgba(158, 209, 174, 0.5);
}
.pathc-status.next {
  background: rgba(253, 249, 239, 0.14); color: rgba(253, 249, 239, 0.84);
  border: 1px solid rgba(253, 249, 239, 0.28);
}

.dusk .paths-foot {
  text-align: center; margin: 44px auto 0; max-width: 70ch;
  color: rgba(253, 249, 239, 0.86); font-size: 14.5px;
  text-shadow: 0 1px 14px rgba(52, 36, 18, 0.45);
}
.dusk .paths-foot a {
  color: #ffe9c4; font-weight: 600; text-decoration: none;
  border-bottom: 1px solid rgba(255, 233, 196, 0.45);
}
.dusk .paths-foot a:hover { border-bottom-color: #ffe9c4; }
.dusk .paths-foot a:focus-visible { outline: 2px solid #ffe9c4; outline-offset: 3px; }

/* â”€â”€ the band comes in â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€
   1625px at 1440Ã—900 â€” nearly two screens for a statement, a mission and a
   coverage grid â€” and the worst of it was one stretch of empty ground between
   the pledges and the coverage eyebrow. The head is centred at 760px, so down
   the left margin, where the eye actually falls, there was nothing at all from
   the last pledge to the first card.

   Every value here is AIR. No type is smaller, nothing is dropped, the ground
   and its cast are untouched â€” the two paragraphs' worth of vertical padding
   between blocks was doing no work that the blocks' own weight wasn't already
   doing.

   The one place the trim does NOT go is the top of the statement. The scrim's
   seam ramp finishes at 188px and .dusk h2 has to start clear of it, or the
   heading is measured against a plate the seam contributes to â€” see the stop
   list below. Pulling the statement up is how you quietly break that, so the
   air under it comes out instead and the air above it stays. */
.dusk-in { padding-bottom: clamp(28px, 3.5vh, 46px); }
/* nothing follows the statement inside .dusk-in on this page â€” the h2's own
   bottom margin was stacking on the padding above */
.dusk-in h2 { margin-bottom: 0; }
/* The trim below was written for the old stacked band (statement, then a
   two-column mission, then a coverage grid) where the air between beats was
   the thing making it long. That band is one centred column now and the beats
   were asked to breathe, so the trim on .mission-in is released back to the
   base rule's clamp; .paths-head/.paths-foot/.paths are retired selectors. */

/* the seam back to the cream proof strip has to clear the coverage block now */
.dusk-scrim {
  background:
    /* The band's top starts on --seam, the shared tone the hero now grades down
       to, so the join has almost no step left to make (~7 L* instead of 61) and
       the fade can stay short. Stops are in px, not %: this layer spans the
       whole band, and the band's height swings with the coverage grid, so
       percentages made the seam 144px at 1440 and 193px at 420 for no reason.

       Done by 188px, so the ramp finishes before .dusk h2 starts at 180px and
       never becomes part of the plate the heading is measured against. Measured:
       swapping this whole layer in and out moves that plate by one unit of blue
       (rgb 120,91,52 vs 120,91,51), so the seam is contrast-neutral by
       construction â€” keep it that way if these stops are ever retuned. */
    linear-gradient(to bottom,
      /* Smoothstep spacing, matching the hero-side ramp: zero slope where the
         solid seam ends and where the fade lands, so neither edge of the ramp
         prints as a crease. Same 188px finish as before.

         v24 â€” each stop also drifts the paint from --seam (yellowish brown)
         toward --seam-deep (warm brown, the footage's own tone) as the alpha
         falls, so the ramp descends in saturation as well as opacity. With a
         single seam colour the mid-ramp was a half-alpha orange over dark
         footage â€” more saturated than either side, and it read as a stripe. */
      var(--seam)                                       0px,
      var(--seam)                                      10px,
      color-mix(in srgb, color-mix(in srgb, var(--seam-deep) 12%, var(--seam)) 96%, transparent)  32px,
      color-mix(in srgb, color-mix(in srgb, var(--seam-deep) 25%, var(--seam)) 84%, transparent)  55px,
      color-mix(in srgb, color-mix(in srgb, var(--seam-deep) 38%, var(--seam)) 68%, transparent)  77px,
      color-mix(in srgb, color-mix(in srgb, var(--seam-deep) 50%, var(--seam)) 50%, transparent)  99px,
      color-mix(in srgb, color-mix(in srgb, var(--seam-deep) 63%, var(--seam)) 32%, transparent) 121px,
      color-mix(in srgb, color-mix(in srgb, var(--seam-deep) 75%, var(--seam)) 16%, transparent) 144px,
      color-mix(in srgb, color-mix(in srgb, var(--seam-deep) 88%, var(--seam))  4%, transparent) 166px,
      transparent                                      188px),
    /* readability pool behind the statement only â€” lifting the footage put
       bright dust right under the display type (worst tenth hit 2.9:1) */
    radial-gradient(56% 38% at 50% 25%, rgba(48, 33, 16, 0.54), rgba(48, 33, 16, 0.1) 76%),
    linear-gradient(to bottom, transparent 92%, color-mix(in srgb, var(--paper) 88%, transparent) 100%);
}

@media (max-width: 1000px) { .paths-grid { grid-template-columns: 1fr 1fr; } }
@media (max-width: 620px) {
  .paths-grid { grid-template-columns: 1fr; }
  .paths { padding-bottom: 84px; }
  .paths-head { margin-bottom: 38px; }
  .pathc { padding: 22px 20px 20px; }
}

/* ============================================================================
   v10 â€” THE HERO WASH. This is the authoritative block; there is no other.

   Seventeen overlapping .hero-scrim rules accumulated across v5â†’v7.4. Fifteen
   of them were unreachable â€” including every body.tutor rule, which is why the
   tutor hero shipped teacher cream, and both @media overrides, which is why the
   hero had no responsive wash at any width. They are gone. If the hero wash
   needs changing, change it HERE. Do not add another override.

   Two layers, deliberately in different places:

   1 Â· THE LIFT â€” .hero-media::after
       A warm radial `screen` blend that raises the footage's shadows toward
       daylight. Screen never darkens and never clips highlights, so the room
       and the sunbeam still read. The cream alpha wash this replaces did the
       opposite: neutral over warm footage desaturates it to grey milk, and over
       the sunlit window it clipped to a flat blob with a visible diagonal edge.

       It lives on .hero-media, NOT on .hero-scrim. .hero-scrim is
       position:absolute + z-index:1, which makes it a stacking context and
       therefore an isolated blending group â€” a .hero-scrim::before with
       mix-blend-mode blends against the scrim's own background and never
       touches the video, rendering as flat tan. Verified in Chromium.

   2 Â· THE HANDOFF â€” .hero-scrim
       Normal blending only: a short top fade and the hand-off to --paper at the
       bottom seam. It paints after .hero-media, so it sits outside the blend
       group and the bottom edge matches the next section exactly.

   Both take their colour from tokens, so body.tutor recolours itself.
   ========================================================================= */

/* .hero-v5 is isolation:isolate and the <video> starts at opacity:0, so without
   a backstop the blend group's backdrop is transparent for the first second and
   `screen` degrades to painting raw tan over the page. Required, not a nicety. */
.hero-media { background: var(--paper); }

/* â”€â”€ HERO WASH TUNING KNOBS â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€
   Three numbers control how milky the hero reads. Change them HERE; the
   gradient stops below are derived from them, so you never edit a stop list.

   --hero-daylight    strength of the daylight lift over the whole frame.
                      1 = as shipped, 0 = raw footage. This is the big one:
                      on open footage it moves L* 54 -> 34 as it goes 1 -> 0.
                      Lower this first if the hero looks washed out.

   --hero-copy-plate  the paper plate behind the headline, there for text
                      contrast. 1 = as shipped, 0 = none. Behind the copy it
                      moves L* 95 -> 83. On the cream page there is far more
                      headroom here than the plate's size suggests: measured
                      worst-tenth, the h1 holds 14.9:1 at 1.0 and still 10.7:1
                      at 0.2, against a 3.0 floor (the lede likewise 14.1 ->
                      10.2 against 4.5). So on THAT page it is a look decision,
                      not a contrast one â€” take it well below 1 if the copy
                      reads too milky. On body.tutor it is a contrast decision:
                      the footage is much busier and the hero tags bottom out
                      first. See the measured ladder on body.tutor below.

   --hero-copy-plate-size
                      how far that plate spreads, independent of how opaque it
                      is at the centre. 1 = as shipped. The spread is what
                      reads as milk creeping across the frame; the alpha is
                      what reads as haze over the type. Widening it slightly
                      while dropping --hero-copy-plate keeps the low-lying
                      strings (tags, rail) legible on a much lighter wash.

   --hero-lift        the COLOUR the shadows are lifted toward, not a
                      strength. Warmer/cooler, not lighter/darker.

   Measured caveat: with BOTH layers switched off the top-left still reads
   L* ~97. That is the sunlit window in the source clip, not the wash â€” no
   value here will darken it; that needs a re-grade or a separate plate.

   v15 â€” BOTH ARE ZERO ON THE CREAM SKIN NOW, and the reason is that the
   footage underneath them changed. Everything above was written against the
   photographed classroom: interior, mid-dark, a blown window in one corner.
   Both layers existed to drag that upward into the cream page.

   video/hero.* is now an illustrated sunrise â€” pale gold field, cream sky,
   already sitting where the wash was trying to push the old clip to. Lifting
   it again bleached it: measured over the rendered hero at 1440, the daylight
   layer alone took the left half to L* 96 and the schoolhouse â€” the subject â€”
   fell to a 2 L* separation from the sky behind it. The plate then added a
   second bloom on the same side. The clip was doing the wash's job already.

   The contrast the knobs were buying is bought by the footage instead, and it
   is measured, not assumed: _src/verify-hero.py samples what is actually
   behind each string across five seek points. See _src/contrast-hero.md.

   Leave them at zero unless the hero clip is replaced with darker or busier
   footage â€” in which case re-run that script rather than guessing a value. */
:root {
  --hero-lift: 180 200 220;   /* warm daylight, the classroom */
  --hero-daylight: 0;
  --hero-copy-plate: 0;
  --hero-copy-plate-size: 1;  /* how far the plate spreads, not how strong */
}
/* These are the CREAM skin's numbers and they are load-bearing on THIS page â€”
   the teach hero flattens its footage upward and its type is near-black.
   Zeroed, the h1 measures 1.58:1 at 1024 and 1.79:1 at 390, i.e. gone. The
   tutor page no longer uses either knob; see v12 at the end of this block. */
body.tutor {
  --hero-lift: 190 184 162;   /* warm bone, to sit with --paper #f2eee1 */
  /* BOTH OFF ON PURPOSE. This page flattens its footage downward instead â€”
     see v12 at the end of this block for why, and do not switch them back on
     without reading it. Kept as tokens rather than deleted so the cream skin's
     rules stay one shared codepath. */
  --hero-daylight: 0;
  --hero-copy-plate: 0;
  --hero-copy-plate-size: 1;
}

/* â”€â”€ THE GRADE â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€
   v21 â€” a third layer, and the only one still doing anything on the cream
   skin. The wash knobs above are zero because the sunrise clip was already
   light enough; what it is NOT is saturated. Ungraded it reads as pale sand
   under a grey-white sky: the field, the road and the sky sit within a few
   points of chroma of each other, so nothing in the frame has an edge and the
   whole hero reads flat next to the page's own red.

   This is a grade on the footage itself, not paint over it â€” the difference
   matters, because a tint layer washes the frame toward one colour while
   saturate() pushes every hue away from grey along its own axis. The gold goes
   gold, the sky goes blue, and the schoolhouse keeps its terracotta instead of
   being pulled toward whatever the tint was.

   THE GRADE IS CONTRAST-LED, NOT SATURATION-LED, and that is the correction
   this block exists to record. The first pass reached for saturate() as the
   main lever and shipped 1.55; it read as a yellow cast within a minute of
   being looked at. The reason is that this frame is warm end to end â€” sky,
   field, road and schoolhouse are all on the same amber side of the wheel â€”
   so saturation has no second hue to open up. It just drives the one hue
   already there harder, and a frame that is 90% amber going more amber is
   indistinguishable from a colour cast, however honest the maths.

   Value is the part that was actually missing. contrast() separates the road
   from the field and the treeline from the hills, and it does it without
   touching hue at all â€” which is why the "defined" half survives at four
   knobs' worth of yellow removed. Saturation stays only as a small lift so the
   sky keeps its blue.

   Four knobs, in the order they matter:

   --hero-con   the big one now. Value separation, and the only thing here that
                buys definition. Small numbers still: this is a global curve,
                so it brightens the sun as much as it deepens the treeline, and
                the sun is where the tightest string on the page sits (below).
   --hero-sat   a lift, not a lever. Above ~1.3 the yellow returns; at 1.55 it
                was plainly a cast, and by 1.9 the schoolhouse and the grass
                measure the same yellow â€” the subject gone.
   --hero-bri   pays the contrast back. Without it the grade lifts the whole
                frame and walks the copy toward its floor.
   --hero-hue   the de-yellow, and the reason the other three can stay honest.
                Negative degrees walk the amber back toward terracotta, so the
                brick and the grass separate again. Keep it small â€” past about
                -10 the sky goes lilac.

   CONTRAST, MEASURED, NOT ASSUMED â€” this layer moves every plate in
   _src/contrast-hero.md, so it was re-run rather than reasoned about. The
   binding row is `h1 em`: the italic "back." is --accent red sitting on the
   sunrise itself, it had 3.8:1 against a 3.0 floor before this, and BOTH the
   saturation and the contrast push its plate the wrong way â€” a redder ground
   under red type. It survives at these numbers; it is why they are not higher.
   Re-run `python3 _src/verify-hero.py` before raising any of the three.

   Not on body.tutor. That page's clip is the book loop, its own grade problem,
   and its hero copy has far less headroom â€” see v12 below. Neutral there.

   v22 â€” THE GRADE IS OFF EVERYWHERE. Everything above stays as the record of
   why it existed and what each knob costs; none of it is applied. The call is
   that the hero should read as the clip reads, not as a graded version of it:
   the four knobs together are a warm cast (hue-rotate alone recolours every
   pixel) and side by side with the source file the difference was obvious.

   The definition the contrast knob was buying is bought instead by not
   magnifying the footage â€” the hero now frames the whole 16:9 frame rather
   than cropping it (see .hero-v5's aspect-ratio), so the same source pixels
   cover ~30% less screen and the edges the grade was sharpening are simply
   there. Measured on a 3258px-wide paint, edge energy 1.94 native vs 2.03
   graded-and-upscaled: the crop, not the grade, was the softness.

   Contrast still passes without it â€” the binding row is `h1 em`, the red
   italic on the sunrise, and 3.8:1 against a 3.0 floor is the UNGRADED
   reading; the grade moved it the wrong way. Re-run `python3
   _src/verify-hero.py` if the clip is ever replaced.

   To bring the grade back: restore the four values below and re-add the
   filter to the rule under them. The tokens stay defined so nothing that
   references them breaks. */
:root {
  --hero-con: 1;
  --hero-sat: 1;
  --hero-bri: 1;
  --hero-hue: 0deg;
}
body.tutor { --hero-con: 1; --hero-sat: 1; --hero-bri: 1; --hero-hue: 0deg; }

/* No filter at all, deliberately â€” not a neutral one. `filter` on a <video>
   forces the frame through a filter pass on its own composited layer, so even
   contrast(1) is a resample; `none` is the only value that paints the decoded
   frame straight. .hero-poster (the still motion.js swaps in on phones) and
   the canvas fallback are listed for the same reason. */
.hero-media video,
.hero-media canvas,
.hero-media .hero-poster {
  filter: none;
}

.hero-media::after {
  content: "";
  position: absolute; inset: 0;
  pointer-events: none;
  mix-blend-mode: screen;
  background: radial-gradient(120% 130% at 20% 50%,
    rgb(var(--hero-lift) / calc(0.95 * var(--hero-daylight)))  0%,
    rgb(var(--hero-lift) / calc(0.78 * var(--hero-daylight))) 26%,
    rgb(var(--hero-lift) / calc(0.42 * var(--hero-daylight))) 48%,
    rgb(var(--hero-lift) / calc(0.12 * var(--hero-daylight))) 68%,
    rgb(var(--hero-lift) / 0)                                 84%);
}

/* Without blending this layer would paint as flat tan. Fall back to the paper
   wash it replaces â€” removing it entirely drops the tutor tags to 3.37:1. */
@supports not (mix-blend-mode: screen) {
  .hero-media::after {
    mix-blend-mode: normal;
    background: linear-gradient(100deg,
      color-mix(in srgb, var(--paper) 80%, transparent)  0%,
      color-mix(in srgb, var(--paper) 68%, transparent) 26%,
      color-mix(in srgb, var(--paper) 38%, transparent) 48%,
      color-mix(in srgb, var(--paper) 10%, transparent) 70%,
      transparent 100%);
  }
}

/* v22 â€” the top fade is gone with the grade. It was a 20% paper wash across
   the first 18% of the frame, which is exactly where this clip's sky is, so it
   was the one remaining layer painting cream over footage. The header sits on
   its own backdrop-filter bar and does not need it.

   The BOTTOM hand-off stays and is not a tint decision: it is the join to the
   next section. Without it the hero ends on a hard cut. */
.hero-scrim {
  position: absolute; inset: 0; z-index: 1;
  pointer-events: none;
  background: linear-gradient(to bottom,
    transparent 0%,
    transparent 80%,
    var(--paper) 100%);
}

/* â”€â”€ the dusk seam â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€
   On the one page that carries the sundowner band, the hero does not hand off
   to cream â€” it grades down to --seam, the same tone the band's top starts on.
   Both sides meeting on one colour is what lets the fade be short: the step
   across the join drops from 61 L* to ~7, so there is no edge left to soften.

   Ramping to --paper was actively wrong here. Pure cream (L* 96) is LIGHTER
   than the washed classroom photo above it (L* 85), so the hand-off overshot
   into a highlight â€” measured L* 85 â†’ 94 across the hero's last 24px â€” and
   that bright lip, not the darkness below it, was the visible line. The v7
   build scoped this per-page; the v10 consolidation dropped the scoped rule
   and left the generic cream ramp in charge. This restores the intent.

   --seam is the band's own #6d5233 lifted to L* 60, so the two sides meet on a
   colour that already belongs to the band. Scoped to body.has-dusk: every other
   page still needs the cream hand-off into its next section.            */
/* Re-derived once the band stopped being footage. Rather than lift the band's
   base colour to a guessed L*, this is the classroom floor MEASURED off the
   rendered hero where the two sections meet â€” rgb(166,133,97). The hero grades
   down to the colour its own floor already is, and the band opens on exactly
   the same value, so the join has no step to make at all.

   v15 â€” re-measured against the sunrise clip, the same way: the hero's last 14
   rows sampled across eight frames of the loop, scrim off, at 1440. They
   average rgb(188,158,123). The old rgb(166,133,97) was the classroom's shaded
   floor and is 22 points of red darker than the new clip's lit road, which
   would put a visible dark lip exactly on the join.

   Take the MEAN over the loop, not one frame. The push-in walks this edge from
   rgb(173,147,120) to rgb(201,168,127) â€” a 28-point spread in red â€” so any
   single frame is a colour the seam is only right for once per cycle, and the
   ramp is 240px of gradient that cannot track it anyway.

   v23 â€” the old value was a PALE TAN, hsl(33, 33%, 61%), and both sides of the
   join dissolve into it across 240px above and 188px below. Against the new
   golden hero that laid a milky wash over the bottom of the wheat: measured
   with the ramps switched off, the artwork there is hsl(31, 48%, 33%) and the
   ramp was turning it into hsl(33, 33%, 60%) â€” 27 points of lightness ADDED
   and a third of the saturation taken away. That is a bright, flat stripe
   sitting between a saturated yellow field and saturated brown soil, and no
   amount of smoothing the ramp fixes it, because the colour being ramped TO is
   the problem.

   So the seam is now the hero's own field colour rather than a lightened
   abstraction of it: rgb(190,127,60), the mean of the wheat and path across the
   hero's lower half with the ramps off. The join now happens ON a colour that
   is already the largest thing in the picture, which is why the wash stops
   reading as haze â€” there is no lightness hump left to see, and nothing
   between the yellow and the brown that is greyer than either.

   If this is ever retuned: keep it AT or BELOW the lightness of the wheat above
   it and the band's first stop below it. A seam lighter than both is a bright
   lip however gently it is ramped.

   v24 â€” the field-colour seam fixed the lightness hump and introduced a
   SATURATION hump. rgb(190,127,60) is hsl(31, 52%, 49%): as saturated as the
   wheat but as dark as the footage, so at mid-ramp it printed as an orange
   stripe more vivid than either side â€” measured on the rendered join, the
   wheat above is hsl(37, 53%, 61%), the footage below hsl(32, 27%, 42%), and
   the seam band between them sat at hsl(31, 50%, 47%). The join colour has to
   sit between its neighbours on ALL THREE axes, not just lightness.

   So the seam is split into two tones and the blend walks from one to the
   other:

   --seam       rgb(185,147,91), hsl(36, 40%, 54%) â€” yellowish brown. Hue with
                the wheat, saturation and lightness roughly midway between the
                two sides. This is the colour the two sections actually meet on.
   --seam-deep  rgb(131,105,73), hsl(33, 28%, 40%) â€” warm brown, the footage's
                own tone. The band-side ramp drifts its stops toward this as
                the alpha fades, so the hand-off reads yellow-brown at the top
                and warm brown at the bottom with no saturated peak between. */
body.has-dusk { --seam: #b9935b; --seam-deep: #836949; }

body.has-dusk .hero-scrim {
  background:
    /* fixed 300px, so the join is identical at every viewport height â€” the
       percentage version ran 169px at 900 tall and 212px at 1061.

       The stops sit on a smoothstep curve, not a straight ladder. A linear
       fade has a slope discontinuity where it starts and where it lands, and
       the eye reads both as faint horizontal creases (Mach bands) â€” which was
       the visible edge at the old 240px mark and again where the solid seam
       began. Zero slope at both ends is what makes the ramp invisible AS a
       ramp; if these are ever retuned, keep the S-shape, don't equal-space. */
    linear-gradient(to top,
      var(--seam)                                       0px,
      var(--seam)                                       8px,
      color-mix(in srgb, var(--seam) 96%, transparent)  45px,
      color-mix(in srgb, var(--seam) 84%, transparent)  81px,
      color-mix(in srgb, var(--seam) 68%, transparent) 118px,
      color-mix(in srgb, var(--seam) 50%, transparent) 154px,
      color-mix(in srgb, var(--seam) 32%, transparent) 190px,
      color-mix(in srgb, var(--seam) 16%, transparent) 227px,
      color-mix(in srgb, var(--seam)  4%, transparent) 264px,
      transparent                                      300px)
      bottom left / 100% 300px no-repeat;
  /* v22 â€” the top fade this used to carry is dropped, same reason as the
     generic scrim above: it was cream over the clip's sky. The seam layer is
     the only thing this rule exists for. */
}

/* The dim covers the band but not the hero, so at full strength from the band's
   very first pixel it put a ~2 L* ledge exactly on the join â€” the two sides are
   the same colour, then one of them gets darkened. Fade it in across the same
   226px the seam layer uses, so it only starts biting once the join is behind us. */
body.has-dusk .dusk-dim {
  -webkit-mask-image: linear-gradient(to bottom, transparent 0px, #000 188px);
          mask-image: linear-gradient(to bottom, transparent 0px, #000 188px);
}

/* The copy's readability wash has to stop before the seam. It lives on
   .hero-copy::before with inset -70% top/bottom, so it bleeds far below the
   copy â€” and because it sits inside .wrap (z-index 2) it paints ABOVE the
   scrim (z-index 1). That washed the seam ramp back toward paper on the
   left-hand half only: a measured 12.6 L* step at x=8% against 0.4 L* at
   x=92%, which is the faint line that survived matching the two sides.

   Raising the scrim above .wrap instead is not an option â€” the tags sit only
   96px above the hero's bottom at 1120, and the ramp would tint them to
   ~4.4:1, under the 4.5 floor.

   Measured across 1120â€“1680 at two heights: the tags always end at 70.8% of
   this wash's own box and the hero's bottom always falls at 81â€“88%. Fading
   between those leaves the copy fully backed and the seam completely clear. */
body.has-dusk .hero-copy::before {
  -webkit-mask-image: linear-gradient(to bottom, #000 73%, transparent 80%);
          mask-image: linear-gradient(to bottom, #000 73%, transparent 80%);
}

/* the cue sits at bottom:26px, inside the darkened base now â€” var(--muted)
   would read at ~2.5:1 against it.

   v15 â€” inverted with the footage. Cream at 78% was right over the classroom's
   dark floor; the sunrise clip ends on its own lit field instead, and
   _src/verify-hero.py measured the cue at 2.0:1 there against a 3.0 floor â€”
   cream on cream. The plate under it runs rgb(177,143,107) at its lightest, so
   the cue takes ink and a dark rule, and measures 6.6:1. */
body.has-dusk .hero-scroll { color: rgba(43, 33, 24, 0.82); }
body.has-dusk .hero-scroll i {
  background: linear-gradient(rgba(43, 33, 24, 0.55), transparent);
}

/* â”€â”€ the night seam Â· body.tutor â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€
   The tutor hero hands off to #promise, the starry night band. The generic
   scrim above ramps to --paper, so the join measured rgb(226,228,221) one
   pixel up against rgb(18,29,35) one pixel down â€” an ~85 L* cliff, the
   hardest edge on the page, and the only reason it looked like a cut rather
   than a nightfall.

   Same two-sided trick as the dusk seam: the hero grades down to the exact
   colour the band opens on, so the fade has no step left to hide.
   --night-seam is that band's own first row, measured off the render â€” its
   centre is rgb(26,41,50) and its corners rgb(18,29,35), so one value between
   them leaves under 2 L* at every x and the horizontal variation is carried
   by the band's own radial.

   The ramp's height is bounded, not chosen. .hero-copy::before paints inside
   .wrap (z-index 2) and therefore ABOVE the scrim, so a ramp reaching up past
   the copy gets re-lifted toward cream on the left half only â€” a diagonal
   brightening, worse than the edge it replaces. The hero tags end 152px above
   the hero floor at every width measured (1280â€“1680); the ramp gets that
   space plus the slack the plate's own fade-out leaves, and no more.

   KNOBS
   --night-seam-h        how long the fall takes. Past ~230px the plate starts
                         showing through the top of the ramp.
   --night-seam-warmth   0 = cream straight to blue-black, which passes through
                         grey and reads as haze. Above 0 the fall bends through
                         a warm tone on the way down and reads as evening.
   â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€ */
body.tutor {
  --night-seam: 22 36 44;         /* the band's own first row */
  /* v25 â€” 210px â†’ 90px, and the ramp goes light. The night clip does the
     seam's job itself: its own last rows measure rgb(10,20,28) against the
     band's rgb(22,36,44) â€” under 2 rgb units of step once blended. The 210px
     near-opaque ramp was therefore double-darkening footage that was already
     night, and its 0.85-alpha zone sat exactly on the glowing book, which
     lives in the clip's bottom 15%. The book is the hero's subject; the seam
     must never reach it. */
  --night-seam-h: 90px;
  --night-seam-warm: 124 97 66;   /* lantern-side dusk, not the band's blue */
  --night-seam-warmth: 0.15;
}

body.tutor .hero-scrim {
  background:
    /* 1 Â· the fall to night. The alpha ramp is eased, not linear â€” this much
         value change across a flat photo bands visibly on a straight ramp. */
    linear-gradient(to top,
      /* v25 â€” light-touch stops: gone by a third of the way up. Verified
         against the frame: the book's pages sit above the 0.2-alpha line at
         every stop here.
         v25.2 â€” the FOOT goes fully opaque, in px, and stays under 20px. The
         0.9 edge was letting 10% of the LIT grass through â€” the footage
         around the book is bright, not the rgb(10,20,28) the corners
         measure, so the last rows sat visibly warmer than the band's flat
         blue and drew a line at the join. Opacity 1 kills the line; keeping
         the foot in px (not % of the 90px box) is what stops it climbing
         back up the book, whose pages end ~35px above the floor. */
      rgb(var(--night-seam) / 1)      0px,
      rgb(var(--night-seam) / 0.96)  10px,
      rgb(var(--night-seam) / 0.86)  20px,
      rgb(var(--night-seam) / 0.5)   30%,
      rgb(var(--night-seam) / 0.22)  55%,
      rgb(var(--night-seam) / 0.07)  80%,
      rgb(var(--night-seam) / 0)    100%)
      bottom left / 100% var(--night-seam-h) no-repeat,
    /* 2 Â· the dusk in between. Only ever visible where layer 1 is still
         translucent, which is the whole point â€” it colours the transit. */
    linear-gradient(to top,
      rgb(var(--night-seam-warm) / 0)                                     0%,
      rgb(var(--night-seam-warm) / calc(0.34 * var(--night-seam-warmth))) 32%,
      rgb(var(--night-seam-warm) / calc(1.00 * var(--night-seam-warmth))) 57%,
      rgb(var(--night-seam-warm) / calc(0.52 * var(--night-seam-warmth))) 78%,
      rgb(var(--night-seam-warm) / 0)                                    100%)
      bottom left / 100% var(--night-seam-h) no-repeat;
  /* The generic scrim's cream top fade is deliberately dropped, not forgotten:
     it would put haze back at the top of a hero that no longer has any. The
     shade lives on .hero-media::after â€” see v12. */
}

/* v25.1 â€” a soft-focus band ON TOP of the colour ramp. The v25 seam got light
   enough that footage detail (grass blades, the flower heads, the book's
   bottom pages) now survives all the way to the section edge, where it meets
   the band's flat colour in one row â€” a texture cliff where the value cliff
   used to be. This blurs the last of the footage into the flat, so detail and
   colour now fade out together.

   A child pseudo of the scrim, because backdrop-filter samples everything
   painted BELOW the element â€” the video, the v12 shade on .hero-media::after,
   and the scrim's own seam gradient all get blurred as one image, which is
   exactly the blend wanted. The mask is what makes it progressive: partial
   alpha composites the blurred output over the sharp original at that
   opacity, so the frame sharpens continuously upward instead of on a line.

   Height is deliberately LESS than --night-seam-h and top-weighted to zero:
   the glowing book's pages end ~35px above the hero floor at a 900px hero,
   and the mask holds the blur under 40% opacity there â€” the book's body
   stays sharp, only its grass-line footing dissolves. */
body.tutor .hero-scrim::after {
  content: "";
  position: absolute; inset: auto 0 0;
  height: 72px;
  pointer-events: none;
  -webkit-backdrop-filter: blur(9px);
  backdrop-filter: blur(9px);
  -webkit-mask-image: linear-gradient(to bottom,
    transparent 0, rgba(0, 0, 0, 0.38) 50%, #000 92%);
  mask-image: linear-gradient(to bottom,
    transparent 0, rgba(0, 0, 0, 0.38) 50%, #000 92%);
}

/* the cue sits at bottom:26px, which is now inside the night */
body.tutor .hero-scroll { color: rgba(233, 239, 234, 0.72); }
body.tutor .hero-scroll i {
  background: linear-gradient(rgba(233, 239, 234, 0.5), transparent);
}

/* ============================================================================
   v12 â€” THE TUTOR HERO FLATTENS DOWN, NOT UP.

   Every cream-side knob above is off on this page. Not taste â€” the cream
   approach cannot be made to work on this clip, and the numbers say so.

   THE PROBLEM. Strip every wash and measure the raw footage under each string,
   sampled ten times across the clip's 20s. The copy column does not sit on a
   dark background; it sits on one that crosses nearly the whole tonal range as
   the book animates:

                   footage L* p10 -> p90     near-black ink, worst case
       h1                5.4 -> 67.4                    1.00:1
       lede              5.2 -> 76.8                    1.08:1
       tags             15.3 -> 65.2                    1.04:1

   1.00:1 is invisible. So the plate was never decoration â€” it was flattening a
   70 L* swing under 620x500px of type, and the ONLY way a cream layer flattens
   a swing that wide is to lift the dark end all the way to the light end.
   Measured behind the copy: raw footage L* 20.3, +daylight 35.2, +plate 61.1.
   That L* 61 is the milk. It is not a tuning error â€” any cream wash strong
   enough to make dark ink safe here reads as fog, and the ladder proved it:
   the plate could not go below 0.62 without the tags failing AA (0.30 -> 2.84:1,
   0.45 -> 3.53, 0.62 -> 4.80, 1.00 -> 7.14), and 0.62 still measured L* 61.

   THE FIX. Flatten in the other direction. A shade cast from the copy edge
   pulls the BRIGHT end down instead, and the type goes light. Same job, and
   darkening a photo reads as evening rather than haze â€” which is also the
   honest thing for a page whose next section is the night band. Measured
   worst case, cream type over the shade at 0.72:

       h1 5.67:1    lede 7.00:1    tags 12.22:1    rail 13.97:1

   â€” against a 4.5 floor, with zero cream on the frame, where the cream route
   topped out at 4.80:1 on the tags while looking like fog.

   KNOBS
   --hero-shade-a   how deep the shade goes. Measured h1 worst case: 0.62 ->
                    4.61:1, 0.72 -> 5.67:1, 0.82 -> 6.76:1. Below ~0.6 the
                    headline starts failing on the clip's bright frames.
   --hero-shade     the colour it darkens toward. Kept on the night band's own
                    blue-black so the hero, the seam and the band are one ramp.
   ========================================================================= */
body.tutor {
  --hero-shade: 11 20 25;
  --hero-shade-a: 0.72;
  /* Brighter and more saturated than the page's --accent: on the shade the
     old #E8B45C (HSL 38 74 63) sat too close to the cream to separate from
     it. This is HSL 41 91 62 â€” the tutor app's speech-bubble gold, which is
     where the colour comes from. Hero only; the night band's marigold is a
     different signal and must not follow this. */
  --hero-gold: #F6BC45;
}

/* The shade replaces the daylight lift in the same slot â€” same element, same
   job, opposite direction. Overriding the rule outright (rather than zeroing
   --hero-daylight) is what also neutralises the @supports-not fallback, which
   paints a --paper wash the token cannot reach.

   It lives here, on .hero-media, NOT on .hero-scrim: the seam owns the scrim,
   and keeping the two apart means the responsive shade below is one rule
   instead of a duplicated copy of the seam's gradient stack. */
body.tutor .hero-media::after {
  display: block;
  mix-blend-mode: normal;
  /* cast from the copy edge, gone by the two-thirds mark, so the lit book
     keeps the right of the frame */
  background: linear-gradient(100deg,
    rgb(var(--hero-shade) / calc(1.00 * var(--hero-shade-a)))  0%,
    rgb(var(--hero-shade) / calc(0.94 * var(--hero-shade-a))) 26%,
    rgb(var(--hero-shade) / calc(0.66 * var(--hero-shade-a))) 46%,
    rgb(var(--hero-shade) / calc(0.24 * var(--hero-shade-a))) 66%,
    rgb(var(--hero-shade) / 0)                                82%);
}

/* Stacked, the copy goes full-bleed and the card drops below it, so a shade
   raked across the WIDTH leaves the end of every line sitting where it has
   already fallen off â€” measured h1 2.81:1 at 1024 and tags 2.40:1 at 390,
   both under the floor. Below the split the shade rakes down the height
   instead, which is the same move the daylight lift makes at this width. */
@media (max-width: 1080px) {
  body.tutor .hero-media::after {
    background: linear-gradient(184deg,
      rgb(var(--hero-shade) / calc(1.00 * var(--hero-shade-a)))  0%,
      rgb(var(--hero-shade) / calc(0.97 * var(--hero-shade-a))) 40%,
      rgb(var(--hero-shade) / calc(0.88 * var(--hero-shade-a))) 60%,
      rgb(var(--hero-shade) / calc(0.62 * var(--hero-shade-a))) 76%,
      rgb(var(--hero-shade) / calc(0.30 * var(--hero-shade-a))) 88%,
      rgb(var(--hero-shade) / 0)                               100%);
  }
}

/* No cream flash in the second before the video decodes â€” the backstop under
   the blend group has to be the tone the hero settles on, not paper. */
body.tutor .hero-media { background: #101a20; }

/* The plate is off at the token, so this only stops the empty ellipse from
   generating a paint layer at all. */
body.tutor .hero-copy::before { display: none; }

/* --- v23 Â· copy offset ---------------------------------------------------
   Nudged off the wrap's grid position: 30% of the way into the left gutter,
   and down 15% of the hero's height, so the block sits lower-left and clears
   the island's orbit rings on the right of the clip.

   translate, not margin/padding: the copy is the flex child that gives the
   hero its height, so shifting it geometrically would grow the section and
   drag the bottom seam with it. A transform moves paint only â€” the layout
   box, the video framing and the seam all stay put.

   Both numbers are proportional to the things they were measured against.
   The gutter is (100vw - var(--wrap)) / 2, so 30% of it is that / 2 * 0.3;
   it self-zeroes below 1180px, where the wrap goes full-bleed and there is
   no gutter left to move into.

   The drop is 15% of the hero's OWN height, which has two sources on this
   page, so it is carried in a variable that switches with them rather than
   one expression pretending to cover both:

   Â· >1180px the tutor hero is min(94vh, 900px) â€” the aspect-ratio override
     at line ~2191 is scoped to body.has-dusk, i.e. the teach hero, not this
     one. 15% of that is the ~127px drop this was asked for.
   Â· â‰¤1180px the figure returns to normal flow and CONTENT sets the height,
     so there is no box expression to take a share of. A self-percentage
     (basis = the copy block) tracks it instead.

   Getting this wrong is not cosmetic: keying the whole range to the desktop
   expression left a fixed 127px drop against a 642px hero at 1180, and the
   tags row cleared the hero's bottom edge by 31px and printed over the seam
   into the next section. Keying the whole range to the self-percentage was
   safe everywhere but only moved 76px on desktop, short of the ask. */
body.tutor .hero-v5 .hero-copy {
  --hero-drop: 15%;
  transform: translate(
    calc(min(0px, (var(--wrap) - 100vw) / 2) * 0.3),
    var(--hero-drop)
  );
}
/* v2 â€” the copy sits 100px higher and 50px further left than the numbers
   above placed it. The lift is a straight subtraction from the drop. The
   sidestep cannot be: the room to move left IS the gutter, and the gutter is
   ~0px at 1181 and 370px at 1920, so a flat -50px prints the headline off the
   left edge on any window near the breakpoint. max() of two negatives takes
   the less-leftward one, which makes the second term a floor â€” below roughly
   1250 it takes over and holds the copy 8px clear of the viewport edge
   instead. Desktop only, for the same reason the drop is: at â‰¤1180 the figure
   returns to normal flow, the gutter is gone, and the hero's top padding is
   all that separates the headline from the nav. */
@media (min-width: 1181px) {
  body.tutor .hero-v5 .hero-copy {
    --hero-drop: calc(min(94vh, 900px) * 0.15 - 100px);
    transform: translate(
      max(calc(min(0px, (var(--wrap) - 100vw) / 2) * 0.3 - 50px),
          calc((var(--wrap) - 100vw) / 2 - 20px)),
      var(--hero-drop)
    );
  }
}

/* --- the type, inverted. The cream halos from v7.5/v11 are wrong side up
       here: a light glow around light glyphs on a dark ground fattens them.
       A tight dark shadow does the separating instead. --- */
body.tutor .hero-v5 h1 {
  color: #F6F1E5;
  text-shadow: 0 1px 3px rgba(8, 14, 18, 0.5);
}
body.tutor .hero-v5 h1 em { color: var(--hero-gold); }

/* --- v24 Â· hero type, -20% ------------------------------------------------
   All three clamp() values scaled by 0.8, not just the preferred one: the
   min and max are what the size actually resolves to at the ends of the
   range, so scaling the middle alone would leave the headline unchanged on
   phones (pinned to the 46px floor) and on wide windows (pinned to the 88px
   ceiling) â€” i.e. everywhere except a narrow band in between.

   Scoped to body.tutor. The base rule at ~2217 is shared with the teach
   hero, whose h1 is a different string against a different clip. */
body.tutor .hero-v5 h1 { font-size: clamp(36.8px, 5.28vw, 70.4px); }
body.tutor .hero-v5 .hs-lede { font-size: clamp(13.6px, 1.52vw, 16.4px); }
/* Pure #FFF, not the cream â€” the lede is the one string that sits over the
   brightest part of the clip (footage p90 = L* 77 under it), so it takes the
   whitest value available. Also puts the emphasis back in the headline: the
   gold em is once again the only gold in the copy block. */
body.tutor .hero-v5 .hs-lede {
  color: #FFFFFF;
  text-shadow: 0 1px 3px rgba(8, 14, 18, 0.5);
}
/* the eyebrow is a label, not prose, and already carries a gold dot */
body.tutor .hero-v5 .hero-rail .hr-l { color: var(--hero-gold); }
body.tutor .hero-v5 .hero-rail .hr-l i { background: var(--hero-gold); }
body.tutor .hero-v5 .hero-tags span {
  color: rgba(240, 244, 238, 0.9);
  text-shadow: 0 1px 3px rgba(8, 14, 18, 0.5);
}
body.tutor .hero-v5 .hero-tags svg { color: var(--hero-gold); }

/* the dark-green pill was a solid on cream; on the shade it disappears into
   its own ground, so the hero's one button takes the gold. Dark green ink on
   it measures 8.2:1, which is why the label stays ink rather than going cream
   â€” cream on this gold is 1.5:1. */
body.tutor .hero-v5 .btn-primary {
  background: var(--hero-gold);
  color: #16301F;
  border-color: transparent;
}
body.tutor .hero-v5 .btn-primary:hover { background: #FFC95C; }

/* the nav CTA matches: same gold, same ink label. Specificity must clear the
   .nav-links a.btn-primary cream-text fix (0,2,1) and the (0,2,1)/(0,3,1)
   hover patches above, so this is stated at (0,3,1)/(0,4,1) via body.tutor. */
body.tutor .nav-links a.btn-primary {
  background: var(--hero-gold);
  color: #16301F;
  border-color: transparent;
}
body.tutor .nav-links a.btn-primary:hover { background: #FFC95C; color: #16301F; }

/* The band opens on its own radial, whose top row is 8 rgb units lighter in
   the centre than at the corners. Meeting a flat --night-seam that would be a
   faint centre step, so the band's first 120px carry the same flat value and
   dissolve into the radial below the join. */
body.tutor .night-home::before {
  content: "";
  position: absolute; inset: 0 0 auto; height: 120px;
  z-index: 0; pointer-events: none;
  background: linear-gradient(to bottom,
    rgb(var(--night-seam) / 0.9) 0%,
    rgb(var(--night-seam) / 0.5) 46%,
    rgb(var(--night-seam) / 0)  100%);
}

/* Stacked, the exercise-book card returns to normal flow at z-index 3 and
   ends a measured 76px above the hero floor at every width from 390 to 860.
   The ramp would therefore run almost entirely behind an opaque white card
   and the join would go card-white to night in one step â€” the same cliff,
   just wearing a different colour. Buy the fall its own room below the card.
   v25 dropped the 170px height override: the base is 90px now, which already
   fits inside the 196px of bought room with the card clear above it. */
@media (max-width: 1080px) {
  body.tutor .hero-v5 { padding-bottom: 196px; }
}

/* --- stacked layouts: the copy sits over the full-width frame, so the lift
       becomes top-weighted rather than left-weighted --- */
@media (max-width: 1080px) {
  .hero-media::after {
    background: radial-gradient(155% 108% at 50% 20%,
      rgb(var(--hero-lift) / calc(0.95 * var(--hero-daylight)))  0%,
      rgb(var(--hero-lift) / calc(0.80 * var(--hero-daylight))) 30%,
      rgb(var(--hero-lift) / calc(0.44 * var(--hero-daylight))) 54%,
      rgb(var(--hero-lift) / calc(0.12 * var(--hero-daylight))) 74%,
      rgb(var(--hero-lift) / 0)    88%);
  }
  /* below 1081px the figure returns to normal flow, so it lost to the
     positioned scrim and the paper handoff washed out the card's base. */
  .hero-v5 .hero-figure,
  .hero-v5 .hero-float { position: relative; z-index: 3; }
}

@media (max-width: 860px) {
  .hero-media::after {
    background: linear-gradient(to bottom,
      rgb(var(--hero-lift) / calc(0.95 * var(--hero-daylight)))  0%,
      rgb(var(--hero-lift) / calc(0.86 * var(--hero-daylight))) 34%,
      rgb(var(--hero-lift) / calc(0.50 * var(--hero-daylight))) 60%,
      rgb(var(--hero-lift) / calc(0.14 * var(--hero-daylight))) 80%,
      rgb(var(--hero-lift) / 0)   100%);
  }
}

/* ============================================================================
   v20 â€” the planner window becomes a pane of the sky it sits on.

   Supersedes the v11 green glass below (kept as history, no longer applied):
   v11 was tuned against a dark photographed classroom, and over the pale
   sunrise footage it read as a heavy slab dropped onto the picture. The
   .mission-panel further down the page independently copied v11's green
   (see the v18 note near .mission-panel), so it is untouched by this change.

   The gradient is SAMPLED from the footage behind the window, not picked by
   eye: with .hero-figure hidden, the sky under the card's own rect measures
   rgb(202,212,218) cool blue along its top edge and rgb(250,220,187) warm
   peach along its bottom, steady to within ~3 points across the loop (the
   push-in barely moves this part of the frame). Those two are the endpoints
   below, carried down-right at 148deg and taken a little pinker at the far
   end than the sky actually is â€” enough to tell the pane from what is behind
   it, not enough to read as a different material.

   The alphas are the whole trick. Under ~0.80 the rider and the horizon line
   swim through the panel; over ~0.95 it stops being sky and becomes a card
   again. 0.86â†’0.92 keeps the footage faintly alive inside the glass while
   still carrying the row chips.

   Everything that sat ON the old dark board had to invert: rows and head are
   white veils rather than white-on-dark, the type is ink, and .ud-gen flips
   from a cream chip to an ink one â€” cream on a pale sky pane is invisible.
   It stays NOT the brand red, for the reason the v11 note records: red here
   is the deuteranopia collision, and the hero keeps exactly one red object,
   the real CTA below.
   ========================================================================= */
.uidemo {
  color: #241a0e;
  background:
    linear-gradient(rgba(28,22,14,0.045) 1px, transparent 1px),
    linear-gradient(90deg, rgba(28,22,14,0.045) 1px, transparent 1px),
    /* v19 â€” the board is tuned to the sunset clip behind it, not to itself.
       Top: the old sky blue holds its hue and lightness but its chroma is
       scaled 2.6x (28 -> 73) at alpha 0.94. That looks aggressive as a hex
       value and is not: the panel is translucent over a BRIGHT WARM sky and
       backdrop-filter saturate(1.12) pulls that warmth through, so a gently
       lifted blue composited back to warm grey (measured rgb(215,210,195),
       chroma 20). The strong value is what survives the composite as sky.
       Sample the render, not the stylesheet, if this is ever retuned.
       Bottom: the two pink stops were mixed 60/40
       toward a warm yellow, landing on hue ~35deg â€” the field-and-low-sun
       half of the footage â€” instead of the rose that fought it. The middle
       stop warms just enough to carry blue into gold without a grey waist. */
    linear-gradient(148deg,
      rgba(176, 215, 249, 0.94)  0%,
      rgba(226, 216, 199, 0.88) 44%,
      rgba(248, 216, 168, 0.90) 78%,
      rgba(249, 213, 166, 0.92) 100%);
  background-size: 40px 40px, 40px 40px, cover;
  border-color: rgba(255, 255, 255, 0.5);
  box-shadow: 0 18px 52px rgba(74, 62, 52, 0.22);
}
.ud-head {
  background: rgba(255,255,255,0.52);
  border-color: rgba(40,30,20,0.10);
  color: #1c1409;
}
.ud-gen { background: #241d13; color: #f7f4ee; }
.ud-row {
  background: rgba(255,255,255,0.38);
  border-color: rgba(40,30,20,0.08);
  color: #201608;
}
.ud-row svg { color: #33704a; }
.ud-row.alt svg { color: #9a5a1b; }
.ud-cursor { background: #1c1409; }


/* ============================================================================
   v11 â€” the sundowner clip stops being magnified.

   .dusk grew tall enough to hold the coverage cards: 1440x1222 at a 1440px
   viewport. With the media covering the whole band, object-fit: cover had to
   scale a 16:9 clip 1.70x (2.00x at 2560) and crop it to an 849x720 window â€”
   3.39 device pixels painted per source pixel on a retina panel. That is why
   the dust read bigger, faster (1.7x more screen crossed per second) and
   softer than the source file does.

   Fix: the media is a viewport-tall sticky window, so cover scale is set by the
   window (1.25x at 1440x900) instead of by the band's height. The footage now
   holds still while the copy scrolls over it, which is also the nicer read.
   The clip itself was re-encoded at 1920x1080 â€” see video/README.md.

   overflow: clip, NOT hidden. `hidden` makes .dusk a scroll container, and a
   sticky child sticks to its scroll container, so the video would pin to the
   band's own top edge and this entire block would silently do nothing.
   ========================================================================= */
.dusk { overflow: clip; }
.dusk-media {
  position: sticky; inset: auto; top: 0;
  width: 100%; height: 100vh;
  margin-bottom: -100vh;     /* sticky, but takes no room in flow */
  z-index: 0;
}

/* Safari < 16 has no overflow: clip â€” fall back to the full-band cover it had
   before. Magnified, but never broken. */
@supports not (overflow: clip) {
  .dusk { overflow: hidden; }
  .dusk-media {
    position: absolute; inset: 0; height: auto; margin-bottom: 0;
  }
}

/* ============================================================================
   v9 â€” the band drops the footage. A painted sundowner ground, three drifting
   dust layers and the Ember planes.

   Two things fall out of this that the video could not give us. The ~2.8MB of
   mp4/webm goes away entirely. And contrast stops being something to sample
   from moving frames: the ground is a known colour, so what sits on it can be
   computed exactly.
   ========================================================================= */
.dusk {
  /* Sampled from the rendered hero, not guessed: the hero's own last rows are
     rgb(189,159,123) â€” hue 33 â€” which is also --seam's own hue, so the band
     opens on exactly what the road above it already is, then steps down in
     lightness at the same hue. It has to fall away fairly quickly: the
     statement copy starts at 12% of the band, and on the seam colour itself
     white body text would only reach ~4.8:1.

     Written in hsl() on purpose â€” these are meant to be hand-tuned, and the
     three dials only make sense in that space.

     Chroma (S*(1-|2L-1|)) tapers rather than holding flat, tracking the hero's
     own falloff into shadow â€” held flat into the deep stops it read as burnt
     orange instead of a field losing the light.

     v22 re-derived these stops against the new golden hero (hue 31, ~1.5x the
     chroma) so the band would read as the same field losing the light. Reverted
     by request: the saturated ground stopped reading as the FIRST PASS at this
     theme â€” the quiet tan derived from the hero clip's last rows â€” and that
     first pass is the look the page keeps. These are the original hue-33 stops
     exactly as shipped (deploys k0vksqbzo/7yr5t4h6j, 2026-08-09). The top stop
     still opens on var(--seam), which stays measured against the CURRENT hero
     (see the v23 note at body.has-dusk) so the join keeps its provable match;
     the chroma relaxes from the seam into these stops across the band's first
     6%, under the 150px seam ramp.

     Useful hue range is 30â€“38; below is mauve, above is mustard.

     The furrow rows above the gradient are rows.svg, generated by
     _src/rows_gen.py â€” the first pass's read that this ground is the hero
     clip's last rows, the field's furrows running on under the soil. NOT the
     flat repeating-gradient stripes tried and removed earlier: dead-straight
     lines read as banding, so each crest wanders on two incommensurate sines
     and the group is blurred into corrugation. Stretched 100% 100% like
     beds.svg, and versioned the same way, for the same caching reason.
     Re-run _src/verify-band.py after moving any L value here or any alpha in
     the generator â€” the copy sits straight on this ground. */
  background:
    url("rows.svg?v=20260810f") 0 0 / 100% 100% no-repeat,
    /* v25 â€” same two endpoints, same hue path, CONTINUOUS slope between them.
       The six-stop version above stepped: its first segment fell at
       -1.461 L* per % and its second at -0.564, so the rate dropped to 39% of
       itself at the 6% mark. Measured down the rendered column that printed as
       a slope kink at the join (-0.10 to -1.18 L* per 10px in one step), a
       spike at +120px, and an actual brightening reversal at +140..+160px â€”
       the yellow-to-brown "band" the eye picks up.

       (Note for whoever edits this next: always write the unit as "L* per %".
       Spelling it with a slash puts an asterisk immediately before that slash,
       which is a comment terminator â€” the comment ends early and the whole
       declaration below it silently dies. That is exactly how this block got
       broken once already.)

       v26 rewrites the stop list under the same rule. The per-segment fall in
       HSL L per % runs .83 .57 .50 .50 .42 .36 .20 .07 .07 (monotone
       non-increasing, so still no rate change for a Mach band to form on;
       the steeper first segment is the eyebrow's doing â€” at its ~12% depth
       the ramp has to sit at the v25 curve's own darkness or the label
       fails its 4.5 floor even before the glow layer adds light),
       and two things depart from v25 on purpose:
       - the hue drifts 35 down to 25 as the light dies. Held flat at 33 the
         fall read as soil, not evening; amber into ember is the drift that
         reads as dusk.
       - the foot rests at hsl(25, 43%, 21%), a warm ember rather than the
         old hue-33 L 25% brown, and chroma RISES into the deep stops so the
         dark stays orange instead of going dead.
       Retuning rule: keep the slope list monotonic. Equal-spacing these stops
       is what put the corner in last time. */
    linear-gradient(to bottom,
      var(--seam)          0%,
      hsl(35, 36%, 48%)    6%,
      hsl(34, 34%, 44%)   13%,
      hsl(33, 33%, 40%)   21%,
      hsl(31, 32%, 35%)   31%,
      hsl(29, 33%, 30%)   43%,
      hsl(28, 35%, 26%)   57%,
      hsl(27, 38%, 23%)   72%,
      hsl(26, 41%, 22%)   86%,
      hsl(25, 43%, 21%)  100%);
}

/* v26 â€” the last light. One glow pooled top-right, on its own layer rather
   than in the background list above, because of the seam: a radial has its
   maximum at its core, and any core near the top edge leaves real alpha AT
   y=0 â€” about 0.2 on the right in the first cut â€” which printed as a hard
   horizontal line where the hero's provable join met a band top that was
   suddenly lighter. The mask is the fix: the glow fades in from nothing
   across the first ~150px, so at the seam itself this layer contributes
   exactly zero and the join stays the ramp's alone.

   Shape and position are constrained by the eyebrow, not taste: the label
   sits centred at ~12% depth, where NO light text clears 4.5:1 once the
   plate passes ~rgb(148,110,66) â€” a full-width wash dropped it to 3.3:1.
   This pool keeps glow alpha under ~0.02 on the eyebrow's column. Rerun
   verify-band.py after touching ANY of these numbers; the eyebrow is
   always the row that breaks first. */
.dusk::before {
  content: ""; position: absolute; inset: 0; z-index: 0;
  pointer-events: none;
  background: radial-gradient(44% 30% at 83% -10%,
      rgba(255, 206, 138, 0.34),
      rgba(255, 192, 122, 0.16) 42%,
      rgba(255, 180, 110, 0.05) 70%,
      transparent 100%);
  -webkit-mask-image: linear-gradient(to bottom, transparent 0, #000 150px);
          mask-image: linear-gradient(to bottom, transparent 0, #000 150px);
}

/* the video layers are gone with it */
.dusk-media, .dusk-dim { display: none; }

/* â”€â”€ dust â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€
   Individual motes, not a tiled layer. Three repeating-background layers were
   the whole cost of the band: each had to be oversized by a tile and
   recomposited every frame, which measured 47fps against 121 with the dust
   gone â€” and swapping CSS gradients for an SVG tile barely moved it, because
   the cost was the layer AREA, not what filled it. Forty-eight 3px elements
   each get their own tiny compositor layer and are effectively free (121fps),
   and giving every mote its own drift reads far less mechanical than a grid
   sliding as one piece.
   ------------------------------------------------------------------------ */
.dust-field {
  position: absolute; inset: 0; z-index: 1;
  pointer-events: none; overflow: hidden;
}
.dust-field i {
  position: absolute; border-radius: 50%;
  left: var(--x); top: var(--y);
  width: var(--s); height: var(--s);
  /* a lit sphere, not a flat dot â€” at bubble sizes a solid fill reads as a
     paint fleck; the off-centre core is what makes it read as a bubble
     catching the band's own light */
  background: radial-gradient(circle at 34% 30%,
      rgba(255, 250, 236, 0.98),
      rgba(255, 243, 219, 0.82) 42%,
      rgba(255, 233, 198, 0.46) 72%,
      rgba(255, 228, 188, 0.16) 90%, transparent 100%);
  opacity: var(--a);
  animation: mote var(--d) linear var(--delay) infinite;
  will-change: transform, opacity;
}
/* One-way rise, not `alternate`: each bubble climbs its --dy (negative, set
   in dust.js), fading in as it leaves its seat and OUT well before the loop
   ends â€” the die-at-the-top is what keeps risers from stacking against the
   hero seam, and the 0-opacity ends are what hide the snap home. */
@keyframes mote {
  0%   { transform: translate3d(0, 0, 0); opacity: 0; }
  14%  { opacity: var(--a); }
  68%  { opacity: var(--a); }
  100% { transform: translate3d(var(--dx), var(--dy), 0); opacity: 0; }
}

@media (prefers-reduced-motion: reduce) {
  .dust-field i { animation: none; }      /* still dust, just still */
}

/* The cards' backdrop blur existed to soften moving footage. Blurring a flat
   ground returns the same flat ground, so it was only costing frames â€” the
   four of them were forcing a re-filter every time anything moved beneath. */
.pathc {
  -webkit-backdrop-filter: none;
  backdrop-filter: none;
  background: rgba(253, 249, 239, 0.14);
}
.pathc:hover { background: rgba(253, 249, 239, 0.19); }

/* The top gradient used to wash cream across the band's first 12%, which is
   what drew the pale bar between the hero and this section. The hero now ends
   on the floor and the band opens on the same colour, so the seam only has to
   carry the hand-off to the cream strip BELOW. */
.dusk-scrim {
  z-index: 2;
  background:
    linear-gradient(to bottom, transparent 92%, color-mix(in srgb, var(--paper) 88%, transparent) 100%);
}

/* ============================================================================
   v12 â€” the Plan step is a coded, animated mock, not a screenshot.

   `.tshot img` is `aspect-ratio: 16/10; object-fit: cover`, so a dense app
   screenshot was being hard-cropped and scaled â€” which is exactly why the
   lesson plan was unreadable. This replaces step 01's image with real DOM at
   real type sizes, stepping through the actual Create Lesson Plan flow the way
   Marble animates its onboarding card. It stays crisp at any width, weighs
   nothing, and needs no re-shoot when the app's chrome changes.

   No browser-chrome bar here on purpose â€” the frame added nothing and ate
   vertical space the content needed.
   ========================================================================= */
.tshot-wiz {
  padding: 0;
  background: var(--card);
  container-type: inline-size;
}

/* --- the 1 / 2 / 3 rail ------------------------------------------------- */
.wz-rail {
  display: flex; gap: 7px; flex-wrap: wrap;
  padding: 14px 16px;
  border-bottom: 1px solid var(--line);
  background: linear-gradient(var(--cream), color-mix(in srgb, var(--cream) 55%, var(--card)));
}
.wz-pip {
  display: inline-flex; align-items: center; gap: 7px;
  padding: 6px 12px 6px 7px; border-radius: var(--r-pill);
  font-size: 12px; font-weight: 600; color: var(--muted);
  background: color-mix(in srgb, var(--ink) 5%, transparent);
  transition: background 0.4s var(--ease), color 0.4s var(--ease);
}
.wz-pip i {
  display: grid; place-items: center; width: 18px; height: 18px; flex: none;
  border-radius: 50%; font-size: 11px; font-style: normal; font-weight: 700;
  background: color-mix(in srgb, var(--ink) 10%, transparent); color: var(--muted);
  transition: background 0.4s var(--ease), color 0.4s var(--ease);
}
.wz-pip.on  { background: var(--accent-tint); color: var(--accent-deep); }
.wz-pip.on i { background: var(--accent); color: #fff; }
.wz-pip.ok  { background: var(--green-tint); color: var(--green); }
.wz-pip.ok i { background: var(--green); color: #fff; font-size: 0; }
.wz-pip.ok i::after { content: "âœ“"; font-size: 11px; }

/* --- the pane stage. Fixed aspect so swapping panes never jolts layout --- */
.wz-stage { position: relative; aspect-ratio: 16 / 8.6; overflow: hidden; }
.wz-pane {
  position: absolute; inset: 0;
  padding: 18px 18px 16px;
  display: flex; flex-direction: column; gap: 7px;
  opacity: 0; transform: translateY(10px);
  /* Leaving: fade out fast and stay put, then reset the offset once already
     invisible (0s duration, delayed past the fade).

     The panes are stacked in one box, so the old symmetric 0.45s crossfade put
     BOTH of them near half opacity at the midpoint â€” two sets of labels legible
     through each other â€” while the outgoing drifted down through the incoming
     drifting up. Two things moving in opposite directions through each other is
     what read as jitter; neither timing nor easing was the problem. */
  transition: opacity 0.22s var(--ease), transform 0s linear 0.22s;
  pointer-events: none;
}
/* Entering: waits out that fade, then rises alone. The stagger is the whole
   trick â€” one thing moving at a time, across a card that is briefly just the
   card, instead of two half-drawn screens competing. Transform runs slightly
   longer than opacity so the pane is fully legible while it settles the last
   pixel or two, rather than arriving and stopping dead. */
.wz-pane.on {
  opacity: 1; transform: none;
  transition: opacity 0.4s var(--ease) 0.16s, transform 0.52s var(--ease) 0.16s;
}

.tshot-wiz .wz-lbl {
  margin: 4px 0 0; font-size: 10px; font-weight: 700;
  letter-spacing: 0.11em; text-transform: uppercase; color: var(--muted);
}
.tshot-wiz .wz-lbl em { font-style: normal; font-weight: 500; text-transform: none; letter-spacing: 0; opacity: 0.75; }

/* --- pane 1 -------------------------------------------------------------- */
.wz-seg {
  display: grid; grid-template-columns: repeat(3, 1fr); gap: 4px;
  background: var(--paper-2); border-radius: 11px; padding: 4px;
}
.wz-seg span {
  text-align: center; padding: 7px 4px; border-radius: 8px;
  font-size: 12.5px; font-weight: 600; color: var(--muted);
}
.wz-seg .sel { background: var(--card); color: var(--ink); box-shadow: var(--sh-1); }

.wz-grid { display: grid; grid-template-columns: repeat(3, 1fr); gap: 6px; }
.wz-grid span {
  display: flex; flex-direction: column; gap: 1px;
  padding: 8px 10px; border-radius: 10px;
  border: 1px solid var(--line); background: var(--card);
  font-size: 12.5px; font-weight: 600; color: var(--muted);
  transition: border-color 0.35s var(--ease), background 0.35s var(--ease), color 0.35s var(--ease);
}
.wz-grid span b { font-size: 10.5px; font-weight: 500; color: var(--muted); opacity: 0.8; }
.wz-grid span.pick {
  border-color: var(--accent); background: var(--accent-tint); color: var(--ink);
}
.wz-chip {
  align-self: flex-start;
  display: inline-flex; align-items: center; gap: 7px;
  padding: 7px 14px; border-radius: var(--r-pill);
  background: var(--blue-deep); color: #fff; font-size: 12.5px; font-weight: 600;
}
.wz-chip i { width: 7px; height: 7px; border-radius: 50%; background: #fff; opacity: 0.9; }

/* --- pane 2 -------------------------------------------------------------- */
.wz-select {
  position: relative; z-index: 4;
  display: flex; align-items: center; justify-content: space-between; gap: 8px;
  padding: 10px 12px; border-radius: 11px;
  border: 1px solid var(--line-2); background: var(--card);
  font-size: 13px; font-weight: 600;
  transition: border-color 0.35s var(--ease), box-shadow 0.35s var(--ease);
}
.wz-select.open { border-color: var(--accent); box-shadow: 0 0 0 3px var(--accent-tint); }
.wz-select svg { width: 15px; height: 15px; flex: none; stroke: var(--muted); stroke-width: 2.2; fill: none; }
.wz-menu {
  position: absolute; left: 0; right: 0; top: calc(100% + 5px); z-index: 3;
  margin: 0; padding: 5px; list-style: none;
  border-radius: 11px; background: #fff;      /* opaque: it overlays the rows below */
  border: 1px solid var(--line-2); box-shadow: var(--sh-3);
  opacity: 0; transform: translateY(-6px) scale(0.99); pointer-events: none;
  transition: opacity 0.28s var(--ease), transform 0.28s var(--ease);
}
.wz-select.open .wz-menu { opacity: 1; transform: none; }
.wz-menu li {
  padding: 6px 9px; border-radius: 7px; font-size: 12.5px; font-weight: 500; color: var(--ink-2);
}
.wz-menu li.hl { background: var(--accent); color: #fff; font-weight: 600; }
.wz-toggle {
  display: flex; align-items: center; justify-content: space-between; gap: 10px;
  margin-top: 2px; font-size: 13px; font-weight: 600;
}
.wz-toggle i {
  position: relative; width: 38px; height: 21px; border-radius: 999px; flex: none;
  background: var(--line-2); transition: background 0.35s var(--ease);
}
.wz-toggle i::after {
  content: ""; position: absolute; top: 2.5px; left: 2.5px;
  width: 16px; height: 16px; border-radius: 50%; background: var(--card);
  box-shadow: var(--sh-1); transition: transform 0.35s var(--ease);
}
.wz-toggle i.on { background: var(--green); }
.wz-toggle i.on::after { transform: translateX(17px); }
.wz-field {
  min-height: 62px; padding: 10px 12px; border-radius: 11px;
  background: var(--paper); border: 1px solid var(--line);
  font-size: 12.5px; line-height: 1.5; color: var(--ink-2);
}
.wz-car {
  display: inline-block; width: 1.5px; height: 1em; margin-left: 1px;
  background: var(--accent); vertical-align: text-bottom;
  animation: wzcar 1s steps(1) infinite;
}
@keyframes wzcar { 50% { opacity: 0; } }

/* --- pane 3 -------------------------------------------------------------- */
.wz-gen { margin: auto 0; display: flex; flex-direction: column; gap: 8px; }
.wz-spin {
  width: 26px; height: 26px; border-radius: 50%;
  border: 2.5px solid var(--line-2); border-top-color: var(--accent);
  animation: wzspin 0.9s linear infinite;
}
@keyframes wzspin { to { transform: rotate(360deg); } }
.wz-gen h4 {
  margin: 0; font-family: var(--display); font-size: clamp(19px, 2.4cqi, 26px);
  font-weight: 600; letter-spacing: -0.02em; color: var(--ink);
}
.tshot-wiz .wz-note { margin: 0; font-size: 13px; line-height: 1.55; color: var(--ink-2); max-width: 46ch; }
.tshot-wiz .wz-note b { color: var(--accent-deep); }
.wz-bar { height: 6px; border-radius: 999px; background: var(--paper-2); overflow: hidden; margin-top: 4px; }
.wz-bar i { display: block; height: 100%; width: 0; border-radius: 999px; background: var(--accent); }
.wz-pane.on .wz-bar i { animation: wzfill 3.4s var(--ease) forwards; }
@keyframes wzfill { to { width: 100%; } }
/* The waiting screen is the least interesting pane in every flow â€” it earns its
   place by saying what the agent is working FROM, and nothing after that. Both
   generating panes now hold ~2s, so the bar has to fill in step with them. */
.wz-pane.on .wz-gen .wz-bar i  { animation-duration: 1.7s; }
.wz-pane.on .wz-read .wz-bar i { animation-duration: 3s; }

/* --- pane 4 Â· the finished plan ----------------------------------------- */
.wz-plan { gap: 0; }
.wz-doc {
  flex: 1; min-height: 0; overflow: hidden;
  padding: 14px 15px; border-radius: 12px;
  background: #fff; border: 1px solid var(--line);
}
.tshot-wiz .wz-doc-h { margin: 0; font-family: var(--display); font-size: clamp(17px, 2.2cqi, 23px); font-weight: 600; letter-spacing: -0.02em; }
.tshot-wiz .wz-doc-s { margin: 2px 0 0; font-size: 12px; color: var(--muted); }
.wz-obj { margin: 5px 0 0; padding: 0; list-style: none; display: flex; flex-direction: column; gap: 4px; }
.wz-obj li {
  display: flex; gap: 8px; align-items: baseline;
  padding: 6px 9px; border-radius: 8px; background: var(--paper);
  font-size: 12px; line-height: 1.45; color: var(--ink-2);
  opacity: 0; transform: translateY(5px);
}
.wz-pane.on .wz-obj li { animation: wzin 0.45s var(--ease) forwards; }
.wz-pane.on .wz-obj li:nth-child(2) { animation-delay: 0.12s; }
.wz-pane.on .wz-obj li:nth-child(3) { animation-delay: 0.24s; }
@keyframes wzin { to { opacity: 1; transform: none; } }
.wz-obj em {
  font-style: normal; font-family: var(--mono); font-size: 10.5px; font-weight: 700;
  color: var(--accent-deep); flex: none;
}
.wz-cols { display: grid; grid-template-columns: 1fr 1fr; gap: 12px; margin-top: 8px; }
.tshot-wiz .wz-cols p:not(.wz-lbl) { margin: 3px 0 0; font-size: 11.5px; line-height: 1.45; color: var(--ink-2); }
.wz-done {
  display: inline-flex; align-items: center; gap: 7px; margin-top: 10px;
  font-size: 12px; font-weight: 600; color: var(--green);
}
.wz-done svg { width: 14px; height: 14px; flex: none; }

/* --- narrow cards: the 3-up grids get cramped below ~430px of card width - */
@container (max-width: 430px) {
  .wz-grid { grid-template-columns: repeat(2, 1fr); }
  .wz-cols { grid-template-columns: 1fr; gap: 6px; }
  .wz-stage { aspect-ratio: 16 / 13; }
}

/* --- reduced motion: no cycling. Show the finished plan, which is the step's
       actual point, with every transition and spinner stilled. ------------- */
.no-motion .wz-pane { transition: none; }
.no-motion .wz-pane[data-pane="3"] { opacity: 1; transform: none; }
.no-motion .wz-obj li { opacity: 1; transform: none; animation: none; }
.no-motion .wz-spin, .no-motion .wz-car { animation: none; }
.no-motion .wz-pip[data-pip] { background: var(--green-tint); color: var(--green); }

/* v12a â€” the two fields the first pass left out, and the plan's lower half. */
.tshot-wiz .wz-num {
  padding: 9px 12px; border-radius: 10px;
  background: var(--paper); border: 1px solid var(--line);
  font-size: 13px; font-weight: 600; color: var(--ink);
}
.tshot-wiz .wz-tbl {
  width: 100%; border-collapse: collapse; margin-top: 4px;
  table-layout: fixed;
}
.tshot-wiz .wz-tbl td {
  padding: 5px 8px; border: 1px solid var(--line);
  font-size: 11px; line-height: 1.4; color: var(--ink-2); vertical-align: top;
}
.tshot-wiz .wz-tbl td:first-child { width: 34%; font-weight: 600; color: var(--ink); }
@container (max-width: 430px) { .tshot-wiz .wz-tbl { display: none; } }

/* "Refine a single section" made visible: the row can be told to change on
   its own, without touching the rest of the plan â€” paying off the "Guide the
   agent" steer from pane 2 instead of just describing it. Reuses .wz-ptr's
   base rule and the same tint/accent/green states the mark wizard's "Give 2"
   chip already carries; standalone here since that chip's chrome lives under
   .wz-opts, which this row doesn't have. */
.wz-rfval { transition: color 0.3s var(--ease); }
.wz-rfrow.done .wz-rfval { color: var(--ok-ink); }
.wz-rf {
  position: relative; display: inline-flex; align-items: center; gap: 4px;
  margin-left: 6px; padding: 2px 8px; border-radius: var(--r-pill);
  background: var(--flag-tint); color: var(--flag-ink);
  font-size: 9.5px; font-weight: 700; white-space: nowrap;
  transition: background 0.3s var(--ease), color 0.3s var(--ease), transform 0.13s var(--ease);
}
.wz-rf .wz-ptr { right: -7px; bottom: -9px; }
.wz-rf.aim .wz-ptr { opacity: 1; transform: translate(0, 0); }
.wz-rf.press { background: var(--accent); color: #fdfcf8; transform: scale(0.94); }
.wz-rf.press .wz-ptr { transform: translate(-1px, -1px); }
.wz-rf.done { background: var(--green); color: #fdfcf8; }

/* v12b â€” narrow screens. The trail reserves a 78px rail gutter, so the card is
   only ~256px on a phone; the fixed 16/13 stage then clipped the taller panes.
   Give the stage explicit room instead of an aspect ratio, tighten the padding,
   and reclaim the container's right padding for a little more measure. */
@media (max-width: 720px) {
  .tshot-wiz { margin-right: -20px; }
}
@container (max-width: 430px) {
  .wz-stage { aspect-ratio: auto; height: 430px; }
  .wz-pane { padding: 14px 13px 13px; gap: 5px; }
  .tshot-wiz .wz-rail { padding: 11px 12px; gap: 5px; }
  .wz-pip { padding: 5px 9px 5px 5px; font-size: 11px; }
  .wz-grid span { padding: 7px 8px; font-size: 11.5px; }
  .wz-seg span { font-size: 11.5px; padding: 6px 3px; }
  .tshot-wiz .wz-doc { padding: 12px 12px; }
  .wz-obj li { font-size: 11px; padding: 5px 7px; }
}

/* ============================================================================
   v13 â€” the Prepare step's wizard. Shares every .wz-* primitive with the Plan
   wizard above; only the pieces unique to Create Worksheet live here.
   ========================================================================= */

/* output type â€” a 2Ã—2 of big targets, like the real dialog */
.wz-quad { display: grid; grid-template-columns: 1fr 1fr; gap: 6px; }
.wz-quad span {
  text-align: center; padding: 10px 6px; border-radius: 10px;
  background: var(--paper-2); color: var(--ink-2);
  font-size: 12.5px; font-weight: 600;
}
/* --accent, not --accent-deep: the deep token is for TEXT and hover fills (see
   the palette note), and using it as a resting fill made the one chosen answer
   read as a disabled state next to the marker trail. White on --accent is
   4.83:1, so the brighter red clears the 4.5 this 12.5px label needs. */
.wz-quad .sel { background: var(--accent); color: #fdfcf8; }

/* the name field types a shorter string than the plan's steer */
.tshot-wiz .wz-field-1 { min-height: 0; padding: 9px 12px; }

/* question mix â€” three counters that tick up to the target split */
.wz-mix { display: grid; grid-template-columns: repeat(3, 1fr); gap: 6px; }
.wz-mix div {
  display: flex; flex-direction: column; align-items: center; gap: 2px;
  padding: 8px 6px; border-radius: 10px;
  border: 1px solid var(--line); background: var(--card);
}
.wz-mix span { font-size: 10.5px; font-weight: 600; color: var(--muted); text-align: center; }
.wz-mix b {
  font-family: var(--display); font-size: 21px; font-weight: 600;
  letter-spacing: -0.02em; color: var(--accent-deep); line-height: 1;
}

/* constraint chips â€” one of them switches on mid-pane */
.wz-chips { display: flex; flex-wrap: wrap; gap: 5px; }
.wz-chips span {
  padding: 6px 11px; border-radius: var(--r-pill);
  border: 1px solid var(--line-2); background: var(--card);
  font-size: 11.5px; font-weight: 600; color: var(--ink-2);
  transition: background 0.35s var(--ease), border-color 0.35s var(--ease), color 0.35s var(--ease);
}
.wz-chips span::before { content: "+ "; color: var(--muted); }
.wz-chips span.on {
  background: var(--accent-tint); border-color: var(--accent); color: var(--accent-deep);
}
.wz-chips span.on::before { content: "âœ“ "; color: var(--accent); }

/* the finished paper */
.wz-qs { margin-top: 6px; }
.tshot-wiz .wz-q {
  display: flex; gap: 6px; align-items: baseline; margin: 0;
  font-size: 12px; font-weight: 700; line-height: 1.4; color: var(--ink);
}
.tshot-wiz .wz-q b { flex: none; }
.tshot-wiz .wz-q em { margin-left: auto; font-style: normal; font-weight: 600; color: var(--muted); flex: none; }
.wz-wordbox {
  display: grid; grid-auto-flow: column; gap: 0; margin: 7px 0;
  border: 1.5px solid var(--ink); border-radius: 4px; overflow: hidden;
}
.wz-wordbox span {
  padding: 5px 4px; text-align: center;
  font-size: 10.5px; font-weight: 700; color: var(--ink);
  border-right: 1px solid var(--line-2);
}
.wz-wordbox span:last-child { border-right: 0; }
.tshot-wiz .wz-sub {
  margin: 0 0 4px; padding-left: 10px;
  font-size: 11.5px; line-height: 1.5; color: var(--ink-2);
}
.tshot-wiz .wz-sub i {
  display: inline-block; width: 54px; border-bottom: 1px solid var(--ink-2);
  margin: 0 2px; vertical-align: baseline;
}
.wz-key {
  display: inline-flex; align-items: center; gap: 6px; margin-top: 8px;
  padding: 5px 10px; border-radius: var(--r-pill);
  background: var(--paper-2); color: var(--muted);
  font-size: 10.5px; font-weight: 600;
}
.wz-key svg { width: 12px; height: 12px; flex: none; }

@container (max-width: 430px) {
  .wz-quad { grid-template-columns: 1fr; }
  .wz-mix { grid-template-columns: 1fr; }
  .wz-mix div { flex-direction: row; justify-content: space-between; padding: 6px 10px; }
  .wz-mix b { font-size: 16px; }
  .wz-wordbox { grid-auto-flow: row; grid-template-columns: 1fr 1fr 1fr; }
  .wz-wordbox span { border-right: 0; }
}

/* The worksheet's unit selector is the last row in its pane, so a downward menu
   would be clipped by the stage's overflow. Open it upward instead. */
.wz-select.wz-up .wz-menu { top: auto; bottom: calc(100% + 5px); transform: translateY(6px) scale(0.99); }
.wz-select.wz-up.open .wz-menu { transform: none; }

/* On a phone the paper is already at its limit; the answer-key chip was being
   half-clipped by the doc's overflow. The same promise is made by the caption
   under the card, so drop the chip rather than squeeze the question. */
@container (max-width: 430px) { .wz-key { display: none; } }

/* ============================================================================
   v13a â€” the Generate button, its click, and the scrolling paper.

   The finished worksheet is taller than the pane on purpose: it scrolls itself
   so you see a real paper â€” three questions, one with its generated figure â€”
   rather than a single question and a lot of white space. The figure and the
   question text are lifted from samples/lantern-worksheet.pdf, the same file
   the Samples section offers for download, so what the mock claims is exactly
   what a visitor can go and check.
   ========================================================================= */
.wz-foot {
  margin-top: auto; padding-top: 10px;
  display: flex; align-items: center; justify-content: space-between; gap: 10px;
}
.wz-valid {
  display: inline-flex; align-items: center; gap: 6px;
  font-size: 11px; font-weight: 600; color: var(--green);
}
.wz-valid svg { width: 13px; height: 13px; flex: none; }
.wz-btn {
  position: relative;
  display: inline-flex; align-items: center; gap: 7px;
  padding: 9px 15px; border-radius: var(--r-pill);
  /* the accent, not --primary: black is for type in these mocks, so a filled
     black pill reads as a second ink rather than as the one thing to press.
     --accent rather than --accent-deep for the same reason the trail uses it â€”
     this is the one thing to press on the step, and the deep token is the hover
     end of the ramp, not the resting state. White on it is 4.83:1. */
  background: var(--accent); color: #fdfcf8;
  font-size: 12.5px; font-weight: 700; white-space: nowrap;
  box-shadow: var(--sh-1);
  transition: transform 0.13s var(--ease), box-shadow 0.13s var(--ease), filter 0.13s var(--ease);
}
.wz-btn svg { width: 14px; height: 14px; flex: none; fill: currentColor; }
.wz-btn.press { transform: scale(0.94); box-shadow: none; filter: brightness(1.25); }

/* The pointer that drifts in and taps the button. It used to sit at
   -6px/-8px, which put its tip inside the card's 22px corner radius when the
   button was the last thing in a pane â€” the stage's overflow:hidden then
   sliced the tip off. Tucked in so it clears the curve at both wizards. */
.wz-ptr {
  position: absolute; right: -4px; bottom: -7px;
  width: 15px; height: 20px; pointer-events: none;
  opacity: 0; transform: translate(16px, 16px);
  transition: opacity 0.3s var(--ease), transform 0.45s var(--ease);
  background: #fdfcf8;
  clip-path: polygon(0 0, 0 76%, 26% 58%, 44% 100%, 62% 90%, 44% 50%, 76% 46%);
  filter: drop-shadow(0 1px 2px rgba(0,0,0,0.45));
}
.wz-btn.aim .wz-ptr { opacity: 1; transform: translate(0, 0); }
.wz-btn.press .wz-ptr { transform: translate(-1px, -1px); }

/* --- the paper scrolls itself ------------------------------------------- */
.wz-scroll { overflow: hidden; position: relative; }
.wz-scroll .wz-qs { transition: transform 2.6s cubic-bezier(0.4, 0, 0.3, 1); }
.wz-scroll.roll .wz-qs { transform: translateY(var(--roll, -150px)); }
.tshot-wiz .wz-fig {
  display: block; width: min(100%, 300px); height: auto;
  margin: 6px auto 8px; border-radius: 6px;
}
.wz-plan .wz-doc { display: flex; flex-direction: column; }
.wz-qs { flex: 1; min-height: 0; }

/* a soft edge so the clipped paper reads as "continues", not "cut off" */
.wz-scroll::after {
  content: ""; position: absolute; left: 0; right: 0; bottom: 0; height: 34px;
  background: linear-gradient(transparent, #fff 78%);
  pointer-events: none;
}

@container (max-width: 430px) {
  .wz-foot { flex-direction: column; align-items: stretch; gap: 6px; }
  .wz-valid { justify-content: center; }
  .wz-btn { justify-content: center; }
  .tshot-wiz .wz-fig { width: min(100%, 210px); }
}

/* v13b â€” scroll ONLY the questions. The paper's title and its answer-key note
   are static, so when .wz-qs translated inside the document box it slid under
   them and the text collided. The scroller is now its own clipped box between
   the two fixed rows. */
.wz-plan .wz-doc { overflow: hidden; }
.wz-scroll { flex: 1; min-height: 0; overflow: hidden; position: relative; margin-top: 4px; }
.wz-scroll .wz-qs { display: block; }

/* ============================================================================
   v13c â€” the generated worksheet, rendered as a printed paper.

   The first pass styled it like a web card, so it read as UI rather than as the
   thing a teacher prints. This matches the real export: a bordered session
   header, the name/marks grid, an underlined TOPIC line, then numbered
   questions with their mark in the right margin and rules between them â€”
   black on white, in a document face rather than the site's UI face.

   Content is verbatim from samples/lantern-worksheet.pdf, the file the Samples
   section offers for download, so the mock can be checked against the real one.
   ========================================================================= */
.wz-paper {
  flex: 1; min-height: 0;
  background: #fff;
  border: 1px solid var(--line-2);
  box-shadow: 0 1px 0 rgba(0,0,0,0.04), 0 10px 26px rgba(40, 28, 12, 0.08);
  padding: 12px 13px 0;
  display: flex; flex-direction: column;
  overflow: hidden;
  /* a document face, deliberately not the site's UI font */
  font-family: Arial, Helvetica, "Liberation Sans", sans-serif;
  color: #111;
}
.wz-paper .wz-scroll { margin-top: 0; }
.wz-paper .wz-scroll::after { background: linear-gradient(transparent, #fff 80%); }

/* the session header block */
.wz-ph {
  border: 1.5px solid #111; text-align: center;
  padding: 5px 8px; margin-bottom: 6px;
}
.tshot-wiz .wz-ph p { margin: 0; font-size: 10px; line-height: 1.5; color: #111; font-weight: 700; }
.tshot-wiz .wz-ph p b { font-size: 10.5px; }
.tshot-wiz .wz-ph .mm { font-weight: 400; font-size: 9.5px; }

/* name / marks grid */
.wz-meta { width: 100%; border-collapse: collapse; margin-bottom: 7px; table-layout: fixed; }
.wz-meta td {
  border: 1px solid #111; padding: 4px 6px;
  font-size: 9.5px; line-height: 1.6; color: #111;
}
.wz-meta i { display: inline-block; width: 52px; border-bottom: 1px solid #111; margin-left: 3px; }

.tshot-wiz .wz-topic {
  margin: 0 0 8px; text-align: center;
  font-size: 10.5px; font-weight: 700; color: #111;
  text-decoration: underline; text-underline-offset: 2px;
}

/* questions â€” number, stem, mark in the right margin, rule beneath */
.wz-item { border-bottom: 1px solid #d9d9d9; padding-bottom: 7px; margin-bottom: 7px; }
.wz-item:last-child { border-bottom: 0; }
.tshot-wiz .wz-item .wz-q {
  display: flex; gap: 6px; align-items: baseline; margin: 0 0 4px;
  font-size: 10.5px; font-weight: 700; line-height: 1.45; color: #111;
}
.tshot-wiz .wz-item .wz-q em {
  margin-left: auto; font-style: normal; font-weight: 400; color: #111; flex: none;
}
.tshot-wiz .wz-item .wz-sub {
  margin: 0 0 3px; padding-left: 14px;
  font-size: 10px; line-height: 1.6; color: #111;
}
.tshot-wiz .wz-item .wz-sub i {
  display: inline-block; width: 46px; border-bottom: 1px solid #111; margin: 0 2px;
}
/* the word bank is a full-width ruled table on the real paper */
.wz-paper .wz-wordbox {
  border: 1.5px solid #111; border-radius: 0; margin: 5px 0 6px;
  grid-auto-flow: column;
}
.wz-paper .wz-wordbox span {
  font-size: 9.5px; font-weight: 700; color: #111; padding: 4px 3px;
  border-right: 1px solid #111;
}
.wz-paper .wz-wordbox span:last-child { border-right: 0; }
.tshot-wiz .wz-paper .wz-fig { width: min(100%, 285px); margin: 4px auto 6px; border-radius: 0; }

@container (max-width: 430px) {
  .wz-paper { padding: 10px 10px 0; }
  .wz-meta td:nth-child(2) { display: none; }
  .wz-paper .wz-wordbox { grid-auto-flow: row; grid-template-columns: repeat(3, 1fr); }
  .wz-paper .wz-wordbox span { border-right: 0; border-bottom: 1px solid #111; }
  .tshot-wiz .wz-paper .wz-fig { width: min(100%, 200px); }
}

/* `.tstep p` (0-1-1) sets `max-width: 44ch` for the walkthrough's prose, and it
   was also narrowing every <p> inside the wizards â€” which left the paper's
   centred header sitting left of centre and pulled the mark out of the right
   margin. Bare-class rules can't out-specify it, so release the cap here.
   `.tshot-wiz .wz-note` (0-2-0) still sets its own measure and wins. */
.tshot-wiz p { max-width: none; }

/* ============================================================================
   v16 â€” Mark and Report, rebuilt from the approved motion mocks.

   The four beats of each step are unchanged; what changed is that both now have
   a physical moment where you watch the machine do its job rather than watching
   states switch on. Mark: one PDF fans into named scripts, a reading beam
   sweeps the handwriting while pen marks stamp behind it, the queue deals
   itself least-sure-first, and the override REDRAWS the mark on the scan.
   Report: a three-panel dashboard whose by-question bars are the same six
   questions the Eye just marked, then the drafted parent comment.

   Two house rules this block obeys, both learned the hard way here:
     Â· `.tstep p` (0-1-1) sets 16.5px on every <p> in the walkthrough, so the
       markup uses div/span/b/em almost everywhere and each <p> rule carries a
       `.tshot-wiz` prefix.
     Â· No <img>. Everything is real DOM, so it stays crisp at any width.

   Palette: black is TYPE ONLY. Two chromatic families carry every state â€”
   accent = "this one wants you", green = "agreed / done" â€” with the middle
   state drawn as the accent at lower strength rather than a third hue.
   ========================================================================= */
.tshot-wiz {
  --flag:        var(--accent);
  --flag-soft:   #cfa276;
  --flag-tint:   var(--accent-tint);
  --flag-tint-2: color-mix(in srgb, var(--accent-tint) 52%, var(--card));
  /* small bold labels sit ON the tints, where the plain hues only reach ~4.1:1 */
  --flag-ink:    color-mix(in srgb, var(--accent-deep) 84%, var(--ink));
  --ok-ink:      color-mix(in srgb, var(--green) 82%, var(--ink));
  --hand: "Bradley Hand", "Segoe Print", "Segoe Script", "Comic Sans MS", cursive;
}

/* â”€â”€ MARK Â· pane 1 â€” one PDF becomes 28 named scripts â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€ */
.wz-pdf {
  align-self: center; display: inline-flex; align-items: center; gap: 9px;
  padding: 10px 15px; border-radius: 12px;
  background: #fff; border: 1px solid var(--line-2);
  box-shadow: 0 8px 22px rgba(40,28,12,0.10);
  font-size: 12.5px; font-weight: 700; color: var(--ink);
  opacity: 0; transform: translateY(-140px) rotate(-6deg);
}
.wz-pdf em { font-style: normal; font-weight: 500; color: var(--muted); }
.wz-pdf svg { width: 15px; height: 15px; flex: none; stroke: var(--accent-deep); fill: none; stroke-width: 1.9; }
.wz-pdf.drop {
  opacity: 1; transform: none;
  transition: transform 0.7s var(--drop, cubic-bezier(0.34,1.4,0.44,1)), opacity 0.25s linear;
}
.wz-fan { position: relative; height: 178px; margin-top: 22px; }
.wz-pg {
  position: absolute; left: 50%; top: 0;
  width: 92px; height: 122px; margin-left: -46px;
  background: #fff; border: 1px solid var(--line-2); border-radius: 6px;
  box-shadow: 0 6px 16px rgba(40,28,12,0.10);
  padding: 10px 9px 6px;
  display: flex; flex-direction: column;
  opacity: 0; transform: translateY(-30px) scale(0.86);
  transition: transform 0.65s cubic-bezier(0.34,1.4,0.44,1), opacity 0.3s linear;
}
.wz-pg > i { display: block; height: 4px; border-radius: 2px; margin-bottom: 6px; background: var(--paper-2); }
.wz-pg > i:nth-child(1) { width: 60%; background: var(--line-2); }
.wz-pg > i:nth-child(3) { width: 82%; }
.wz-pg > i:nth-child(5) { width: 70%; }
.wz-pgt {
  margin-top: auto; align-self: flex-start;
  padding: 2px 7px; border-radius: var(--r-pill);
  font-size: 8.5px; font-weight: 700;
  background: var(--green-tint); color: var(--ok-ink);
  opacity: 0; transform: translateY(5px);
  transition: opacity 0.3s var(--ease), transform 0.3s var(--ease);
}
.wz-ask .wz-pgt { background: var(--flag-tint); color: var(--flag-ink); }
.wz-ask.fixed .wz-pgt { background: var(--green-tint); color: var(--ok-ink); }
.wz-fan.out .wz-pg { opacity: 1; }
.wz-fan.out .wz-pg:nth-child(1) { transform: translateX(-129px) rotate(-3deg); }
.wz-fan.out .wz-pg:nth-child(2) { transform: translateX(-43px)  rotate(-1deg); }
.wz-fan.out .wz-pg:nth-child(3) { transform: translateX(43px)   rotate(1.5deg); }
.wz-fan.out .wz-pg:nth-child(4) { transform: translateX(129px)  rotate(3.5deg); }
.wz-fan.named .wz-pgt { opacity: 1; transform: none; }
.wz-fanmore {
  position: absolute; right: 4px; bottom: -4px;
  font-size: 10.5px; font-weight: 600; color: var(--muted);
  opacity: 0; transition: opacity 0.4s var(--ease);
}
.wz-fan.named .wz-fanmore { opacity: 1; }
/* the fan is pretty; the outcome is the point, so it gets said out loud */
.wz-matched {
  align-self: center; display: inline-flex; align-items: center; gap: 7px;
  margin-top: 18px; padding: 6px 14px; border-radius: var(--r-pill);
  background: var(--green-tint); color: var(--ok-ink);
  font-size: 11.5px; font-weight: 700;
  opacity: 0; transform: translateY(6px);
  transition: opacity 0.4s var(--ease), transform 0.4s var(--ease);
}
.wz-matched.in { opacity: 1; transform: none; }
.wz-matched svg { width: 13px; height: 13px; flex: none; }

/* â”€â”€ MARK Â· pane 2 â€” the beam reads, the pen stamps â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€ */
.wz-sheet {
  position: relative; align-self: center; width: min(430px, 100%);
  background: #fff; border: 1px solid var(--line-2); border-radius: 8px;
  box-shadow: 0 10px 26px rgba(40,28,12,0.09);
  padding: 15px 17px 24px; overflow: hidden;
  font-family: Arial, Helvetica, "Liberation Sans", sans-serif; color: #111;
}
.wz-sh { display: block; text-align: center; font-size: 11.5px; font-weight: 700; letter-spacing: 0.05em; }
.wz-sl { display: block; font-size: 9px; color: #444; margin: 3px 0 7px; }
.wz-qa { position: relative; padding: 6px 0 7px; border-bottom: 1px solid #efefef; }
.wz-qa:last-of-type { border-bottom: 0; }
.wz-sq { display: block; font-size: 10px; font-weight: 700; line-height: 1.4; color: #111; }
.wz-hw {
  display: block; padding-right: 52px; margin-top: 2px;
  font-family: var(--hand); font-size: 12px; line-height: 1.45; color: #24406b;
}
.wz-mark {
  position: absolute; right: 2px; bottom: 7px;
  font-size: 11.5px; font-weight: 700; color: var(--accent-deep);
  opacity: 0; transform: scale(1.9) rotate(4deg);
}
.wz-mark.in { animation: wzstamp 0.42s cubic-bezier(0.34,1.4,0.44,1) forwards; }
.wz-total { right: 13px; bottom: 5px; font-size: 13px; }
@keyframes wzstamp { to { opacity: 1; transform: scale(1) rotate(-7deg); } }
.wz-conf {
  position: absolute; right: -6px; top: 3px;
  padding: 2px 8px; border-radius: var(--r-pill);
  background: var(--flag-tint); color: var(--flag-ink);
  font-family: var(--font); font-size: 8.5px; font-weight: 700;
  opacity: 0; transform: translateY(4px);
  transition: opacity 0.3s var(--ease), transform 0.3s var(--ease);
}
.wz-conf.in { opacity: 1; transform: none; }
/* the claim of the whole step, made literal: it is reading this */
.wz-beam {
  position: absolute; left: 0; right: 0; top: -14%; height: 13%; pointer-events: none;
  background: linear-gradient(to bottom,
    rgb(var(--beam-rgb, 181 103 58) / 0) 0%, rgb(var(--beam-rgb, 181 103 58) / 0.13) 55%,
    rgb(148 89 46 / 0.42) 98%, rgb(148 89 46 / 0) 100%);
}
.wz-beam.sweep { transition: top 3.4s linear; top: 104%; }

/* â”€â”€ MARK Â· pane 3 â€” what it is unsure of â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€ */
.wz-big {
  font-family: var(--display); font-size: clamp(26px, 4.4cqi, 42px);
  font-weight: 600; letter-spacing: -0.03em; line-height: 1.05; color: var(--ink);
}
.wz-big em { font-style: normal; color: var(--ok-ink); font-variant-numeric: tabular-nums; }
.wz-said { font-size: 13px; color: var(--ink-2); }
.wz-stack { display: flex; gap: 3px; height: 9px; margin-top: 5px; }
.wz-stack i { display: block; width: 0; border-radius: 999px; transition: width 0.9s var(--ease); }
.wz-stack .a { background: var(--green); }
.wz-stack .p { background: var(--flag-soft); }
.wz-stack .d { background: var(--flag); }
.wz-pane.on .wz-stack .a { width: calc(87.2% - 5.2px); }
.wz-pane.on .wz-stack .p { width: calc(9.7%  - 0.6px); transition-delay: 0.55s; }
.wz-pane.on .wz-stack .d { width: calc(3.1%  - 0.2px); transition-delay: 0.95s; }
.wz-legend {
  display: flex; flex-wrap: wrap; gap: 12px; margin-top: 6px;
  font-size: 10.5px; font-weight: 600; color: var(--muted);
}
.wz-legend span { display: inline-flex; align-items: center; gap: 5px; }
.wz-legend span::before { content: ""; width: 7px; height: 7px; border-radius: 50%; }
.wz-legend .a::before { background: var(--green); }
.wz-legend .p::before { background: var(--flag-soft); }
.wz-legend .d::before { background: var(--flag); }

.tshot-wiz .wz-lbl-r { display: flex; justify-content: space-between; gap: 10px; }
.wz-queue { display: grid; grid-template-columns: repeat(4, 1fr); gap: 6px; }
.wz-qc {
  padding: 7px 9px 8px; border-radius: 10px; background: var(--flag-tint);
  opacity: 0; transform: translateY(20px) rotate(1.6deg);
}
.wz-qc.p2 { background: var(--flag-tint-2); }
.wz-qc.deal { animation: wzdeal 0.5s cubic-bezier(0.34,1.4,0.44,1) forwards; }
@keyframes wzdeal { to { opacity: 1; transform: none; } }
.wz-qc b {
  display: flex; align-items: baseline; justify-content: space-between; gap: 5px;
  font-size: 11.5px; color: var(--ink);
}
.wz-qc b i { font-style: normal; font-size: 8.5px; font-weight: 700; color: var(--flag-ink); white-space: nowrap; }
.wz-qc em { display: block; font-style: normal; font-size: 9px; line-height: 1.35; color: var(--ink-2); margin-top: 1px; }
/* the bare number means nothing at a glance, so how sure it is gets drawn */
.wz-qc u { display: block; height: 3px; border-radius: 999px; margin-top: 5px; position: relative;
  background: color-mix(in srgb, var(--accent-deep) 18%, transparent); }
.wz-qc u::after {
  content: ""; position: absolute; inset: 0 auto 0 0; width: var(--s);
  border-radius: 999px; background: var(--accent-deep);
}
.wz-qc.p2 u::after { background: #a8825a; }
.wz-acts { display: flex; gap: 8px; margin-top: auto; padding-top: 12px; }
.wz-act {
  padding: 8px 14px; border-radius: var(--r-pill);
  border: 1px solid var(--line-2); background: var(--card);
  font-size: 11.5px; font-weight: 700; color: var(--ink-2);
}
.wz-act.go { background: var(--accent-deep); border-color: var(--accent-deep); color: #fdfcf8; }

/* â”€â”€ MARK Â· pane 4 â€” her whole script, one question open â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€ */
.wz-revhead { display: flex; align-items: baseline; gap: 10px; flex-wrap: wrap; margin-bottom: 10px; }
.wz-revhead b { font-size: 13px; color: var(--ink); }
.wz-revhead em { font-style: normal; margin-left: auto; font-size: 10.5px; color: var(--muted); }
.wz-ov { flex: 1; min-height: 0; display: grid; grid-template-columns: 138px 1fr; gap: 13px; align-items: start; }
.wz-mini {
  background: #fff; border: 1px solid var(--line-2); border-radius: 7px;
  padding: 9px 10px 8px; display: flex; flex-direction: column;
  font-family: Arial, Helvetica, sans-serif;
  box-shadow: 0 6px 16px rgba(40,28,12,0.08);
}
.wz-mini .wz-sq { font-size: 8px; }
.wz-mini .wz-hw { font-size: 9.5px; padding-right: 0; }
.wz-penline {
  position: relative; align-self: flex-start; margin-top: 6px;
  font-size: 11px; font-weight: 700; color: var(--accent-deep); transform: rotate(-6deg);
}
.wz-strike {
  position: absolute; left: -3px; right: -3px; top: 52%; height: 2px;
  background: var(--accent-deep); border-radius: 2px;
  transform: scaleX(0); transform-origin: left; transition: transform 0.35s var(--ease);
}
.wz-penline.done .wz-strike { transform: scaleX(1); }
.wz-pennew {
  align-self: flex-start; margin: 5px 0 0 8px;
  font-size: 11px; font-weight: 700; color: var(--ok-ink);
  opacity: 0; transform: scale(1.8) rotate(4deg);
}
.wz-pennew.in { animation: wzstamp 0.42s cubic-bezier(0.34,1.4,0.44,1) forwards; }

.wz-qlist { display: flex; flex-direction: column; gap: 4px; min-height: 0; }
.wz-qrow {
  display: grid; grid-template-columns: 24px 1fr auto auto; align-items: center;
  column-gap: 8px; padding: 6px 10px; border-radius: 9px;
  opacity: 0; transform: translateY(6px);
}
.wz-qrow.in { animation: wzdeal 0.4s var(--ease) forwards; }
.wz-qrow.ok { background: var(--green-tint); }
.wz-qrow.warn { background: var(--flag-tint-2); }
.wz-qrow > b { font-size: 10.5px; font-weight: 700; color: var(--ink); }
.wz-qrow > span { font-size: 11px; color: var(--ink-2); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.wz-qrow > u { text-decoration: none; font-size: 10.5px; font-weight: 700; color: var(--ink); font-variant-numeric: tabular-nums; }
.wz-qrow > i { font-style: normal; font-size: 9px; font-weight: 700; }
.wz-qrow.ok > i { color: var(--ok-ink); }
.wz-qrow.warn > i { color: var(--muted); }

.wz-qexp {
  background: #fff; border-radius: 11px; padding: 9px 11px 10px; margin: 2px 0;
  border: 1px solid color-mix(in srgb, var(--accent) 30%, var(--paper));
  border-left: 3px solid var(--flag);
  box-shadow: var(--sh-1);
  opacity: 0; transform: translateY(6px);
  transition: border-color 0.4s var(--ease);
}
.wz-qexp.in { animation: wzdeal 0.45s var(--ease) forwards; }
.wz-qexp.kept { border-color: color-mix(in srgb, var(--green) 30%, var(--paper)); border-left-color: var(--green); }
.wz-qexp-h { display: grid; grid-template-columns: 24px 1fr auto; align-items: baseline; column-gap: 8px; }
.wz-qexp-h b { font-size: 10.5px; font-weight: 700; color: var(--ink); }
.wz-qexp-h > span { font-size: 11px; font-weight: 700; color: var(--ink); }
.wz-qexp-h i { font-style: normal; font-size: 9px; font-weight: 700; color: var(--flag-ink); }
.wz-qexp.kept .wz-qexp-h i { color: var(--ok-ink); }
.wz-qexp-b { display: grid; grid-template-columns: auto 1fr; gap: 11px; align-items: start; margin-top: 6px; }
.wz-qexp-b > b {
  font-family: var(--display); font-size: 21px; font-weight: 600; color: var(--ink);
  font-variant-numeric: tabular-nums; transition: color 0.3s var(--ease);
}
.wz-qexp.kept .wz-qexp-b > b { color: var(--ok-ink); }
.wz-sch { display: block; font-size: 10.5px; color: var(--muted); }
/* an opaque flag is not a judgement you can take a view on; this says why */
.wz-why { display: block; margin-top: 4px; font-size: 10.5px; line-height: 1.45; color: var(--muted); }
.wz-why b { color: var(--ink-2); }
.wz-opts { display: flex; gap: 5px; margin-top: 9px; }
.wz-opts span {
  padding: 5px 10px; border-radius: var(--r-pill);
  background: var(--paper-2); font-size: 10.5px; font-weight: 700; color: var(--ink-2);
  transition: background 0.3s var(--ease), color 0.3s var(--ease), transform 0.13s var(--ease);
}
.wz-opts .wz-give { position: relative; background: var(--flag-tint); color: var(--flag-ink); }
.wz-give .wz-ptr { right: -8px; bottom: -10px; }
.wz-give.aim .wz-ptr { opacity: 1; transform: translate(0, 0); }
.wz-opts .wz-give.press { background: var(--accent); color: #fdfcf8; transform: scale(0.94); }
.wz-give.press .wz-ptr { transform: translate(-1px, -1px); }
.wz-qexp.kept .wz-opts .wz-give { background: var(--green); color: #fdfcf8; }
.wz-qfoot { margin-top: 6px; font-size: 10px; color: var(--muted); }
.wz-now { display: none; }
.wz-ask.fixed .wz-was, .wz-qexp.kept .wz-was, .wz-qfoot.done .wz-was { display: none; }
.wz-ask.fixed .wz-now, .wz-qexp.kept .wz-now, .wz-qfoot.done .wz-now { display: inline; }
.wz-valid.wz-hold, .wz-done.wz-hold { color: var(--muted); }

/* â”€â”€ REPORT Â· pane 1 â€” the term dashboard â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€
   Three panels, three ways of looking: a donut for where the class stands, a
   line for whether it is moving, and a bar per question for what to teach
   next. The bar panel is the one that earns its place â€” it is the same six
   questions the Eye just marked, seen from the class's side, so the two steps
   of the trail are visibly one body of evidence rather than two features. */
.wz-dashhead { display: flex; align-items: flex-start; gap: 12px; }
.wz-live {
  margin-left: auto; padding: 4px 11px; border-radius: var(--r-pill);
  background: var(--green-tint); color: var(--ok-ink);
  font-size: 9.5px; font-weight: 700; letter-spacing: 0.06em; text-transform: uppercase;
}
.tshot-wiz .wz-doc-s b { color: var(--ink); font-variant-numeric: tabular-nums; }
.wz-dash {
  flex: 1; min-height: 0;
  display: grid; grid-template-columns: 168px 1fr; grid-template-rows: auto auto;
  gap: 9px; margin-top: 12px;
}
.wz-dpanel {
  background: var(--paper); border-radius: 13px; padding: 11px 13px 12px;
  opacity: 0; transform: translateY(14px) scale(0.985);
}
.wz-dpanel.up { animation: wzpanel 0.55s cubic-bezier(0.34,1.4,0.44,1) forwards; }
@keyframes wzpanel { to { opacity: 1; transform: none; } }
.wz-p-donut { grid-row: 1 / span 2; display: flex; flex-direction: column; }
.wz-p-bars { grid-column: 2; }
.wz-plab {
  display: block; font-size: 9.5px; font-weight: 700;
  letter-spacing: 0.09em; text-transform: uppercase; color: var(--muted);
}
.wz-plab em {
  font-style: normal; letter-spacing: 0; text-transform: none;
  font-weight: 500; opacity: 0.85; margin-left: 5px;
}
.wz-donutwrap { position: relative; margin: 8px auto 0; width: 116px; height: 116px; }
.wz-donut { width: 100%; height: 100%; transform: rotate(-90deg); }
.wz-track { fill: none; stroke: var(--paper-2); stroke-width: 13; }
.wz-arc {
  fill: none; stroke-width: 13; stroke-linecap: butt;
  stroke-dasharray: 0 100; transition: stroke-dasharray 0.9s var(--ease);
}
.wz-arc.a1 { stroke: var(--green); }
.wz-arc.a2 { stroke: var(--flag-soft); stroke-dashoffset: -64.3; }
.wz-arc.a3 { stroke: var(--accent); stroke-dashoffset: -89.3; }
.wz-donut.draw .a1 { stroke-dasharray: 64.3 100; }
.wz-donut.draw .a2 { stroke-dasharray: 25 100;   transition-delay: 0.35s; }
.wz-donut.draw .a3 { stroke-dasharray: 10.7 100; transition-delay: 0.7s; }
.wz-dmid {
  position: absolute; inset: 0; display: flex; flex-direction: column;
  align-items: center; justify-content: center; gap: 1px;
}
.wz-dmid b {
  font-family: var(--display); font-size: 29px; font-weight: 600;
  letter-spacing: -0.03em; line-height: 1; color: var(--ink); font-variant-numeric: tabular-nums;
}
.wz-dmid em { font-style: normal; font-size: 8.5px; font-weight: 700; line-height: 1.2; text-align: center; color: var(--muted); }
.wz-dleg { margin-top: auto; padding-top: 10px; display: flex; flex-direction: column; gap: 4px; }
.wz-dleg span { display: flex; align-items: center; gap: 6px; font-size: 10px; font-weight: 600; color: var(--ink-2); }
.wz-dleg i { width: 8px; height: 8px; border-radius: 2px; flex: none; }
.wz-dleg .s { background: var(--green); }
.wz-dleg .d { background: var(--flag-soft); }
.wz-dleg .e { background: var(--accent); }

.wz-chartbody { position: relative; margin-top: 6px; }
.wz-chartbody svg { width: 100%; height: 90px; display: block; }
.wz-grid { stroke: var(--line); stroke-width: 1; }
.wz-area { fill: color-mix(in srgb, var(--accent) 10%, transparent); opacity: 0; }
.wz-spark {
  stroke: var(--accent); stroke-width: 2.4; stroke-linecap: round; stroke-linejoin: round;
  stroke-dasharray: 100; stroke-dashoffset: 100;
}
.wz-p-line.drawn .wz-spark { stroke-dashoffset: 0; transition: stroke-dashoffset 1.5s var(--ease); }
.wz-p-line.drawn .wz-area { opacity: 1; transition: opacity 0.6s var(--ease) 0.9s; }
.wz-dotend { fill: var(--accent-deep); opacity: 0; }
.wz-p-line.drawn .wz-dotend { opacity: 1; animation: wzpulse 2.4s var(--ease) 1.7s infinite; transition: opacity 0.3s var(--ease) 1.35s; }
@keyframes wzpulse { 0%, 100% { r: 4; } 50% { r: 5.4; } }
.wz-g0, .wz-g50, .wz-g100 {
  position: absolute; left: 0; font-size: 8px; color: var(--muted); font-variant-numeric: tabular-nums;
}
.wz-g0 { bottom: 22px; } .wz-g50 { top: calc(50% - 16px); } .wz-g100 { top: 0; }
.wz-endlab {
  position: absolute; right: 2px; top: 5px;
  font-family: var(--mono); font-size: 10px; font-weight: 700; color: var(--accent-deep);
  opacity: 0; transform: translateY(3px);
  transition: opacity 0.3s var(--ease), transform 0.3s var(--ease);
}
.wz-endlab.in { opacity: 1; transform: none; }
/* a line with no x-axis is a shape, not a measurement */
.wz-xaxis { display: flex; justify-content: space-between; gap: 6px; padding: 0 2px; }
.wz-xaxis span {
  flex: 1; text-align: center; font-size: 8px; font-weight: 700; color: var(--ink-2);
  line-height: 1.2; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.wz-xaxis i { display: block; font-style: normal; font-weight: 500; color: var(--muted); }

.wz-bars { display: grid; grid-template-columns: repeat(6, 1fr); gap: 8px; align-items: end; height: 58px; margin-top: 8px; }
.wz-bars span { display: flex; flex-direction: column; justify-content: flex-end; height: 100%; }
.wz-bars i {
  display: block; height: 0; border-radius: 5px 5px 0 0; background: var(--flag-soft);
  transition: height 0.7s cubic-bezier(0.34,1.4,0.44,1);
}
.wz-bars.grow i { height: var(--h); }
.wz-bars .low i { background: var(--accent); }
.wz-bars u { text-decoration: none; text-align: center; margin-top: 5px; font-size: 9px; font-weight: 700; color: var(--muted); }
.wz-bars .low u { color: var(--accent-deep); }
.wz-barnote {
  display: block; margin-top: 7px; font-size: 10px; line-height: 1.4; color: var(--muted);
  opacity: 0; transform: translateY(4px);
  transition: opacity 0.4s var(--ease), transform 0.4s var(--ease);
}
.wz-barnote.in { opacity: 1; transform: none; }

/* â”€â”€ REPORT Â· pane 2 â€” one learner, and the drafted comment â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€ */
.wz-tag {
  font-style: normal; flex: none;
  padding: 3px 9px; border-radius: var(--r-pill);
  font-size: 9.5px; font-weight: 700; letter-spacing: 0.01em;
}
.wz-tag.em  { background: var(--flag-tint);   color: var(--flag-ink); }
.wz-tag.dev { background: var(--flag-tint-2); color: var(--muted); }
.wz-tag.sec { background: var(--green-tint);  color: var(--ok-ink); }
.wz-rep { display: flex; flex-direction: column; }
.wz-objs { display: flex; flex-direction: column; gap: 5px; margin-top: 4px; }
.wz-ob {
  display: grid; grid-template-columns: auto 1fr auto; column-gap: 9px; row-gap: 2px;
  align-items: center; padding: 8px 10px; border-radius: 10px; background: var(--paper);
  opacity: 0; transform: translateY(5px);
}
.wz-pane.on .wz-ob { animation: wzin 0.45s var(--ease) forwards; }
.wz-pane.on .wz-ob:nth-child(2) { animation-delay: 0.13s; }
.wz-pane.on .wz-ob:nth-child(3) { animation-delay: 0.26s; }
.tshot-wiz .wz-ob b { font-family: var(--mono); font-size: 10px; font-weight: 700; color: var(--flag-ink); }
.wz-ob span { font-size: 11.5px; line-height: 1.4; color: var(--ink-2); }

.wz-prose { margin-top: 4px; min-height: 74px; }
.tshot-wiz .wz-prose p { margin: 0; font-size: 12.5px; line-height: 1.65; color: var(--ink-2); }
/* the underline is the claim's receipt â€” it only ever sits under a phrase the
   marked work can back, which is why it is drawn in the accent and animates */
.wz-prose u {
  text-decoration: none; color: var(--ink);
  background-image: linear-gradient(var(--accent), var(--accent));
  background-size: 0% 1.5px; background-repeat: no-repeat; background-position: 0 100%;
  padding-bottom: 1px;
}
.wz-prose u.drawn { background-size: 100% 1.5px; transition: background-size 0.7s var(--ease); }
.wz-evchip {
  display: inline-block; margin-left: 5px; padding: 1px 7px; border-radius: var(--r-pill);
  background: var(--flag-tint); color: var(--flag-ink);
  font-family: var(--mono); font-size: 9px; font-weight: 700; white-space: nowrap;
  vertical-align: 1px; opacity: 0; transform: translateY(4px) scale(0.9);
}
.wz-evchip.in { animation: wzdeal 0.4s cubic-bezier(0.34,1.4,0.44,1) forwards; }
.wz-ph {
  padding: 1px 6px; border-radius: 5px;
  background: var(--flag-tint-2); color: var(--flag-ink);
  font-size: 11.5px; font-weight: 600; opacity: 0;
}
.wz-ph.in { animation: wzph 0.9s var(--ease) forwards; }
@keyframes wzph {
  0%   { opacity: 0; transform: scale(0.96); }
  55%  { opacity: 1; transform: scale(1.035); background: var(--flag-tint); }
  100% { opacity: 1; transform: scale(1); }
}
.wz-caret {
  display: inline-block; width: 1.5px; height: 1em; margin-left: 1px;
  background: var(--accent); vertical-align: text-bottom;
  animation: wzcar 1s steps(1) infinite;
}

/* â”€â”€ narrow cards â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€ */
@container (max-width: 430px) {
  .wz-queue { grid-template-columns: 1fr 1fr; }
  .wz-queue span:nth-child(n + 5) { display: none; }
  .wz-acts { flex-direction: column; }
  .wz-act { text-align: center; }
  .wz-big { font-size: 26px; }
  .wz-said { font-size: 12px; }
  .wz-legend { display: none; }
  /* the drawn script is atmosphere; the marks are the point */
  .wz-ov { grid-template-columns: 1fr; }
  .wz-mini { display: none; }
  .wz-revhead em { margin-left: 0; }
  .wz-opts { flex-wrap: wrap; }
  .wz-qexp-b { grid-template-columns: 1fr; gap: 4px; }
  .wz-fan.out .wz-pg:nth-child(1) { transform: translateX(-58px) rotate(-3deg); }
  .wz-fan.out .wz-pg:nth-child(2) { transform: translateX(-20px) rotate(-1deg); }
  .wz-fan.out .wz-pg:nth-child(3) { transform: translateX(20px)  rotate(1.5deg); }
  .wz-fan.out .wz-pg:nth-child(4) { transform: translateX(58px)  rotate(3.5deg); }
  /* the dashboard's explicit column-2 placement lands in a phantom column
     once the grid is one wide â€” this bit once already */
  .wz-dash { grid-template-columns: 1fr; gap: 7px; }
  .wz-p-donut { grid-row: auto; }
  .wz-p-bars { grid-column: 1; }
  .wz-dpanel { padding: 9px 11px 10px; }
  .wz-donutwrap { width: 96px; height: 96px; }
  .wz-dmid b { font-size: 24px; }
  .wz-dleg { flex-direction: row; flex-wrap: wrap; gap: 4px 10px; padding-top: 8px; }
  .wz-chartbody svg { height: 72px; }
  .wz-bars { height: 46px; }
  .tshot-wiz .wz-prose p { font-size: 11.5px; line-height: 1.6; }
  .wz-ob span { font-size: 11px; }
  /* Both carry more than the other two panes do, and the trail reserves a 78px
     rail gutter, so the card is only ~256px here â€” measured, not guessed. */
  [data-wiz="mark"] .wz-stage   { height: 620px; }
  [data-wiz="report"] .wz-stage { height: 790px; }
  .wz-qlist { gap: 3px; }
  .wz-qrow { padding: 5px 9px; }
  .wz-dleg { gap: 3px 8px; }
  .wz-dleg span { font-size: 9.5px; }
}

/* Reduced motion parks every wizard on its finished artefact, so anything that
   only animates in under `.wz-pane.on` needs its end state spelled out â€” and
   the report's artefact is pane 1, not pane 3, so it needs its own rule. */
.no-motion .wz-ob, .no-motion .wz-qrow, .no-motion .wz-qexp,
.no-motion .wz-dpanel { opacity: 1; transform: none; animation: none; }
.no-motion .wz-donut .wz-arc.a1 { stroke-dasharray: 64.3 100; }
.no-motion .wz-donut .wz-arc.a2 { stroke-dasharray: 25 100; }
.no-motion .wz-donut .wz-arc.a3 { stroke-dasharray: 10.7 100; }
.no-motion .wz-spark { stroke-dashoffset: 0; }
.no-motion .wz-area { opacity: 1; }
.no-motion .wz-bars i { height: var(--h); }
.no-motion .wz-barnote, .no-motion .wz-endlab { opacity: 1; transform: none; }
.no-motion [data-wiz="report"] .wz-pane[data-pane="1"] { opacity: 1; transform: none; }
.no-motion .wz-caret { display: none; }

/* What survives from the previous Mark/Report pass: the rules that still have
   an element to style. The stale ones â€” a second set of stage heights, the old
   triage queue, the deleted insight bar and objective bands â€” were winning on
   SOURCE ORDER over the v16 block above them at equal specificity, which is why
   the narrow stages never grew. Same cascade trap the hero scrim had. */
@container (max-width: 430px) {
  .wz-pair { grid-template-columns: 1fr; }
  .wz-asmt-2 { display: none; }
  .wz-w { display: none; }
  .wz-n { display: inline; }
  /* at a 256px card an assessment title wraps to three lines â€” truncate the
     way the app itself does */
  .wz-row b { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
  /* a Cambridge objective title is a full sentence; code and state take the
     first line, the title clamps to two beneath it */
  .wz-ob { grid-template-columns: 1fr auto; }
  .wz-ob .wz-tag { grid-row: 1; }
  .wz-ob span {
    grid-column: 1 / -1; display: -webkit-box; -webkit-box-orient: vertical;
    -webkit-line-clamp: 2; line-clamp: 2; overflow: hidden;
  }
  /* The plan's document overflows once its table is dropped. The three
     framework-coded objectives ARE that step's claim, so they stay whole and
     the intentions/criteria columns go â€” with the label orphaned above the
     hidden table. */
  .wz-cols { display: none; }
  .wz-doc .wz-lbl:has(+ .wz-tbl) { display: none; }
}

.no-motion .wz-row { opacity: 1; transform: none; animation: none; }


/* ============================================================================
   v13 â€” the coverage tiles wear the product's green.

   Same trick the contrast note calls load-bearing: the fill is a COLOURED
   film, not a lighter one. Sage rgb(120,190,145) is darker than the cream it
   replaces, so at a slightly higher alpha the plate behind the type gets no
   brighter and every row keeps its measured ratio (re-verified with
   _src/verify-band.py after this change).

   The four icon hues stay per-pathway â€” one app hue each, which is what stops
   a row of four green cards reading as one undifferentiated block.
   ========================================================================= */
.pathc {
  background: rgba(120, 190, 145, 0.26);
  border-color: rgba(158, 209, 174, 0.55);
}
.pathc:hover {
  background: rgba(120, 190, 145, 0.33);
  border-color: rgba(158, 209, 174, 0.7);
}


/* ============================================================================
   v14 â€” Marble's join: a solid sheet with rounded corners, not a soft fade.

   Three things go, one arrives:
   Â· .paths-pool â€” the radial that darkened the whole lower half to hold the
     cards' contrast. The cards now carry their own green plate, so the dimming
     is paid for locally instead of across the band.
   Â· the scrim's bottom hand-off â€” a cream gradient blurring up into the
     footage, which is the "white blur" at the seam.
   Â· the old .proof section's top hairline â€” it would draw a straight line
     across the curve. .proof no longer exists (mission + metrics moved into
     the band itself); .trail is now the section that performs the curl.

   In their place the next section slides up OVER the band as a solid sheet
   with rounded top corners. The sticky footage is clipped by .dusk (overflow:
   clip), so the sheet passes across a still frame exactly the way Marble's
   cream page rises over its ocean.
   ========================================================================= */
.paths-pool { display: none; }

.dusk-scrim { background: none; }

.trail {
  position: relative;
  z-index: 3;
  margin-top: -38px;
  border-top: 0;
  border-radius: 38px 38px 0 0;
}

@media (max-width: 720px) {
  .trail { margin-top: -26px; border-radius: 26px 26px 0 0; }
}

/* ============================================================================
   v14 â€” the coverage tiles take the classroom's own board green.

   #57745e is MEASURED off the chalkboard in the hero footage (7,782 pixels,
   median rather than mean so the lighter grid lines do not pull it), not
   sampled by eye. The tiles now read as the same object as the board a
   metre above them.

   Why the fill is nearly opaque, when every other panel here is a film: the
   ground under these cards is rgb(65,41,19) â€” dark and warm. A green film at
   the old 0.26 composites to rgb(79,80,52), which is olive, not green; the
   ground simply dominates. To LAND on the board colour the film has to be
   0.9 and its own value lifted to #597c66 to allow for the 10% of warm ground
   still coming through. Solved, not tuned by eye.

   Two knock-on fixes, both forced by the arithmetic rather than taste:

   - Card body text was rgba(cream, .9), which on this green measures 4.34:1 â€”
     under the 4.5 floor. At full opacity it is 4.91:1. The headings are #fff
     and were always fine at 5.17:1.
   - Both status pills carried LIGHT fills, which on a mid-green collapse:
     LIVE 3.24:1 and NEXT 3.14:1, and at 10.5px they are body text, not large
     text, so 4.5 applies. Dark fills take them to 8.0 and 7.9 while keeping
     their green/cream identities in the border and the label.
   ========================================================================= */
/* ============================================================================
   v16 â€” the coverage tiles switch from board green to the band's own --seam
   tan (#a68561, the hero-to-band hand-off tone). Same derivation as the v14
   green: ground under these cards is rgb(65,41,19), so a 0.9 film has to be
   lifted to rgb(177,143,106) for the composite to land on #a68561 exactly
   (film * .9 + ground * .1 = target). Solved, not tuned by eye â€” see the
   v14 comment above for the method this repeats.
   ========================================================================= */
.pathc {
  background: rgba(177, 143, 106, 0.9);
  /* a shade lighter than the field, same relationship the green version used */
  border-color: rgba(199, 172, 141, 0.5);
}
.pathc:hover {
  background: rgba(184, 152, 117, 0.92);
  border-color: rgba(210, 186, 158, 0.66);
}
/* tan is light enough that cream/white text (the green version's colours)
   drop to 3.2-3.3:1 â€” dark text instead, --ink for the heading (5.6:1) and a
   close warm near-black for body (5.0:1; --ink-2 alone measured 3.9). */
.pathc h4 { color: var(--ink); }
.pathc p { color: #241e15; }

.pathc-status.live {
  background: rgba(22, 15, 7, 0.42); color: #d9f2e1;
  border-color: rgba(158, 209, 174, 0.5);
}
.pathc-status.next {
  background: rgba(22, 15, 7, 0.42); color: rgba(253, 249, 239, 0.92);
  border-color: rgba(253, 249, 239, 0.34);
}

/* ============================================================================
   v15 â€” three colours: cream ground, near-black text, one blue.

   The accent tokens moved to the app's own blue, taken deeper (see :root).
   This is the one place that needed a follow-up, and it is NOT about the
   accent: a handful of rules put GREEN TEXT on the GREEN TINT, which is the
   tightest pairing in the sheet at 4.11:1 â€” under the 4.5 that 12â€“14px text
   needs. That was failing long before any of this and stays failing if left
   alone.

   They point at --green-deep, not at the accent. These marks mean "yes", so
   turning them blue would trade a contrast bug for a semantic one; the deep
   green keeps the meaning and measures 6.20:1. Icons on that tint are left as
   they are â€” non-text UI, so 3.0 applies and 4.11 clears it.
   ========================================================================= */
.chip .code,
.cross-card.yes li,
.form-msg.ok,
.wz-pip.ok,
.no-motion .wz-pip[data-pip] { color: var(--green-deep); }

/* ============================================================================
   v16 â€” the accent goes red, and one rule has to stop using it as small text.

   .ws-num is the trail's step label ("01 â€” Plan") at 15px. --accent is sized
   for fills, rules and large type; at 15px on cream a mid-red measures 4.21:1,
   under the 4.5 body text needs. --accent-deep is 6.87:1.

   This rule has been failing for a long time and only briefly stopped: it was
   3.68:1 under the original terracotta, passed at 5.08 while the accent was
   blue, and fails again at 4.21 under red. That is the tell that it was always
   the wrong token â€” a label this small belongs on --accent-deep whatever the
   hue happens to be, so it will not break again the next time the accent moves.

   Everything else that paints with --accent measures fine: the 34px and 38â€“56px
   numbers and the 50px headline keywords are large text (3.0 floor), and the
   inline SVGs are non-text UI (also 3.0).
   ========================================================================= */
.tstep .ws-num { color: #E6352E; }

/* ============================================================================
   v17 â€” the callout.

   A blush panel with a thin red bar down its left edge, for the paragraph in a
   section that is the thing to remember rather than the narration around it.

   Deliberately sets no font-size and no colour. Two reasons: it has to sit on
   an 18px section lede and a 16.5px body paragraph without arguing with
   either, and `.sec-head p` is (0,1,1) while `.callout` is (0,1,0) â€” a colour
   declared here would lose that fight silently and leave the rule looking
   applied when it is not. Inheriting sidesteps the cascade entirely, and both
   inks clear the tint anyway: --ink 14.65:1, --ink-2 10.18:1, against a 4.5
   floor. The bar itself is 3.83:1 on the tint and 4.21:1 on paper, over a
   3.0 floor for non-text.

   Not for the dusk band â€” the tint is a light-ground colour and there is no
   dark variant. Anything inside .dusk keeps its own treatment.
   ========================================================================= */
.callout {
  background: var(--accent-tint);
  border-left: 3px solid var(--accent);
  border-radius: var(--r-md);
  padding: 20px 26px;
  max-width: 54ch;
}
/* so it can wrap a block as well as be one */
.callout > :last-child { margin-bottom: 0; }

@media (max-width: 620px) {
  .callout { padding: 17px 20px; }
}

/* ============================================================================
   v18 â€” the hero's brand notes go red with the accent.

   Four marks in the hero were on --green: the italic "back." in the headline,
   the pulsing rail dot, and the two check icons under the CTA â€” plus the CTA
   fill itself (handled at its own rule above). They predate the accent moving
   off terracotta, when green was the only saturated colour in the hero.

   Scoped to .hero-v5 deliberately. --green still means "yes" everywhere else:
   the safeguards ticks, the yes/no cross-cards, the ok message, the LIVE pill.
   Those marks are affirmations, and turning them red would read as a warning.
   The hero's are brand, not semantics, which is why only these move.

   Measured on the rendered hero plate rather than assumed â€” the ground is a
   scrim-lifted photo, so the margin is thinner than it looks on flat paper. */
.hero-v5 .hero-rail .hr-l i { background: var(--accent); }
.hero-v5 .hero-tags svg { color: var(--accent); }

/* ========================================================================
   v11 Â· lantern link â€” the crossover panel

   Replaces the `photos/home-tutor.jpg` slot, which 404'd and self-removed
   while `.photo`'s aspect-ratio box kept holding open 432Ã—475px of empty
   gradient â€” 40% of the section's height for no information.

   What sits there instead is the partner payload itself, field for field
   (`band`, `mean`, `lastPractisedAt`, and misconceptions at 2+ occurrences).
   Band words and their colours are the product's own ladder, matched to the
   report legend above â€” secure=green, developing=orange, emerging=accent â€”
   so one band reads the same everywhere on the page. `--flag-soft` would
   have been the closer match but it is scoped to `.tshot-wiz`.
   ======================================================================== */
.xfer {
  background: var(--card);
  border: 1px solid var(--line-2);
  border-radius: var(--r-lg);
  padding: 20px 22px 18px;
  box-shadow: var(--sh-2);
}
.xfer-top {
  display: flex; align-items: center; justify-content: space-between;
  gap: 12px; margin-bottom: 15px;
}
.xf-from {
  display: inline-flex; align-items: center; gap: 7px;
  font-family: var(--mono); font-size: 9.5px; font-weight: 700;
  letter-spacing: .13em; text-transform: uppercase; color: var(--accent-deep);
  background: var(--accent-tint); padding: 5px 11px; border-radius: var(--r-pill);
}
.xf-from i {
  width: 5px; height: 5px; border-radius: 50%;
  background: var(--accent); flex: none;
}
.xf-when { font-size: 11.5px; color: var(--muted); }

.xfer-who {
  display: flex; align-items: baseline; gap: 10px;
  padding-bottom: 13px; margin-bottom: 4px;
  border-bottom: 1px solid var(--line);
}
.xfer-who b { font-family: var(--display); font-size: 20px; font-weight: 600; letter-spacing: -.01em; }
.xfer-who span { font-size: 12.5px; color: var(--muted); }

.xfer-rows { list-style: none; margin: 0; padding: 0; }
.xfer-rows li {
  display: grid;
  grid-template-columns: 58px minmax(0, 1fr) 54px 76px;
  grid-template-areas: "code name bar band" "code age age age";
  align-items: center; gap: 2px 10px;
  padding: 9px 0; border-bottom: 1px solid var(--line);
}
.xfer-rows li:last-child { border-bottom: 0; }
.xr-code {
  grid-area: code; align-self: start;
  /* the code labels the objective, so it sits on the NAME's line â€” centring it
     across the two-line block floats it between name and date, belonging to
     neither. The 2px is the mono/sans cap-height difference at these sizes. */
  padding-top: 2px;
  font-family: var(--mono); font-size: 10.5px; font-weight: 700;
  color: var(--ink-2); letter-spacing: .01em;
}
.xr-name {
  grid-area: name; font-size: 13.5px; color: var(--ink);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.xr-bar {
  grid-area: bar; height: 5px; border-radius: var(--r-pill);
  background: var(--line); overflow: hidden;
}
.xr-bar u { display: block; height: 100%; width: var(--s, 0%); border-radius: inherit; }
.xr-band {
  grid-area: band; justify-self: end;
  font-size: 10.5px; font-weight: 700; letter-spacing: .01em;
  padding: 3px 9px; border-radius: var(--r-pill); white-space: nowrap;
}
/* same three bands, same three hues as the report legend */
.xr-band.s { background: var(--green-tint);  color: var(--green-deep); }
.xr-band.s + .xr-age { color: var(--muted); }
.xfer-rows li:has(.xr-band.s) .xr-bar u { background: var(--green); }
.xr-band.d { background: var(--orange-tint); color: #8a4d12; }
.xfer-rows li:has(.xr-band.d) .xr-bar u { background: var(--orange); }
.xr-band.e { background: var(--accent-tint); color: var(--accent-deep); }
.xfer-rows li:has(.xr-band.e) .xr-bar u { background: var(--accent); }
.xr-age {
  grid-area: age; font-style: normal;
  font-size: 11px; color: var(--muted);
}

.xfer-mis {
  margin: 13px 0 0; padding: 11px 13px;
  background: var(--paper-2); border-radius: var(--r-md);
  font-size: 12.5px; line-height: 1.5; color: var(--ink-2);
}
.xfer-mis b {
  font-family: var(--mono); font-size: 9.5px; font-weight: 700;
  letter-spacing: .12em; text-transform: uppercase;
  color: var(--accent-deep); margin-right: 7px;
}
.xfer-mis i {
  font-style: normal; font-weight: 600; color: var(--muted);
  white-space: nowrap;
}

/* the section itself, tightened â€” it ran 1.32 screens tall */
.link-sec { padding-block: 80px; }
/* the night version carries a centred head, so it needs a little more air
   above the eyebrow than the tutor page's left-ranged one */
.link-sec.night { padding-block: 92px 88px; }

@media (max-width: 1000px) {
  .link-sec.night .sec-head { margin-bottom: 36px; }
}
@media (max-width: 700px) {
  .link-sec.night .crosses { grid-template-columns: 1fr; }
  .link-sec.night .cross-card + .cross-card { border-left: 0; border-top: 1px solid rgba(233,239,234,.13); }
  .link-out { flex-direction: column; gap: 12px; text-align: center; }
}
@media (max-width: 520px) {
  .xfer { padding: 17px 16px 15px; }
  /* the bar is the first thing to go: band word + recency still carry the row */
  .xfer-rows li {
    grid-template-columns: 54px minmax(0, 1fr) 76px;
    grid-template-areas: "code name band" "code age age";
  }
  .xr-bar { display: none; }
  .chat-panel { padding: 17px 16px 15px; }
  .link-sec.night { padding-block: 66px 62px; }
}


/* ============================================================================
   v17 â€” the coverage tiles take the metrics panel's idiom.

   Four filled tan chips read as four objects competing with the headline above
   them; the metrics panel says the same kind of thing (a fact, a line of
   explanation, a state) as one quiet plate ruled into cells. The tiles become
   that: ONE panel on the band's own ground, hairline dividers instead of gaps,
   and the pathway hue carried by a bare glyph rather than a chip.

   The colour maths runs the opposite way to v14/v16 and is easier for it. The
   ground here is rgb(65,41,19); a 0.34 dark film takes the plate DOWN to about
   rgb(52,33,15), so the light type the band already uses for its headline and
   lede â€” #fffdf8 and cream â€” comes back, at 14:1 and 11:1 rather than the 5s
   the tan card could reach. The status pills lose their fills and become mono
   micro-labels in the same rhythm as the panel's TARGET / LIVE.

   No hover lift and no hover fill: one cell rising out of a ruled plate breaks
   the plate, and a filled cell would square the panel's rounded corners. These
   cards are not links â€” brightening the glyph and the heading is feedback
   enough.
   ========================================================================= */
.paths-grid {
  --hair: rgba(253, 249, 239, 0.15);
  gap: 0;
  border-radius: 22px;
  background: rgba(26, 17, 8, 0.34);
  border: 1px solid rgba(253, 249, 239, 0.16);
  box-shadow: 0 20px 50px rgba(28, 18, 6, 0.3);
}

.pathc {
  display: flex; flex-direction: column; align-items: flex-start;
  padding: 21px 20px 20px;
  background: none;
  border: 0;
  border-radius: 0;
  box-shadow: none;
  transition: none;
}
.pathc + .pathc { border-left: 1px solid var(--hair); }
.pathc:hover { background: none; translate: none; }

/* the glyph keeps the per-pathway hue, without the chip that made it a badge */
.pathc-ico {
  width: auto; height: auto; margin-bottom: 11px;
  /* flex, not block: as an inline SVG in a block span the glyph carried a
     descender's worth of line box under it and the gap to the heading
     measured 40px for a 15px margin */
  display: flex; background: none; border: 0; border-radius: 0;
  opacity: 0.92; transition: opacity 0.3s var(--ease);
}
.pathc-ico svg { width: 17px; height: 17px; display: block; }
.pathc:hover .pathc-ico { opacity: 1; }

.pathc h4 {
  font-size: 15px; color: #fffdf8; margin: 0 0 5px;
  transition: color 0.3s var(--ease);
}
.pathc p { color: rgba(253, 249, 239, 0.86); font-size: 10px; }

/* the pill becomes the panel's micro-label, and moves under the copy â€” same
   value â†’ line â†’ state rhythm the metrics panel reads in */
.pathc-status {
  position: static; order: 3; margin-top: 12px;
  padding: 0; border: 0; border-radius: 0; background: none;
  font-family: var(--mono); font-size: 9.5px; font-weight: 700;
  letter-spacing: 0.11em;
}
.pathc-status.live { background: none; border: 0; color: #a8dcbb; }
.pathc-status.next { background: none; border: 0; color: rgba(253, 249, 239, 0.58); }

/* the copy block takes the space so the label sits on the panel's baseline
   rather than floating under a one-line description */
.pathc p { flex: 1 0 auto; }

@media (max-width: 1000px) {
  .pathc + .pathc { border-left: 0; }
  .paths-grid .pathc:nth-child(2n) { border-left: 1px solid var(--hair); }
  .paths-grid .pathc:nth-child(n + 3) { border-top: 1px solid var(--hair); }
}
@media (max-width: 620px) {
  .paths-grid .pathc:nth-child(2n) { border-left: 0; }
  .paths-grid .pathc + .pathc { border-top: 1px solid var(--hair); }
  .pathc { padding: 17px 15px 15px; }
  .pathc p { flex: 0 1 auto; }
}


/* ============================================================================
   v19 â€” the metrics panel leaves the green for the band's own red.
         (supersedes v18, whose note is kept below the rules for the record)

   Why the green had to go. It was never a legibility problem â€” it was that the
   plate had no tonal separation to stand on. Against the dusk band at the depth
   the panel sits, hsl(31,37%,36%), the v18 green measures 1.20:1. With nothing
   in lightness to distinguish it, it could only assert itself through hue, and
   it spent 117 degrees doing so. That is why it read as an object dropped onto
   the section rather than a part of it.

   The replacement moves the plate into the band's warm range and buys the
   separation back in lightness instead. The hue and saturation are sampled off
   the chosen swatch, hsl(5,36%,46%) â€” 26 degrees from the band, against the
   green's 117.

   The lightness is NOT the swatch's. At its literal 46% the plate measures
   1.09:1 off this ground â€” a worse float than the green it replaces â€” and
   cream on it reaches only 5.22:1. Held at the same hue and taken down to 26%
   it clears both: cream 8.75:1, 1.54:1 off ground.

   Translucent, and this time it earns it. v18's note is right that alphas over
   this warm ground composite badly â€” but that applied to a GREEN film, which
   the band's warmth drags to olive. A red film has nowhere unpleasant to land;
   blending only pulls it further into the family it already belongs to. So the
   band's gradient runs through the plate and the panel keeps shifting with the
   section as it descends, instead of sitting at one fixed value on a ground
   that is still moving underneath it.

   That is also why the base has to start darker than the finished surface
   should look: compositing pulls any plate toward its ground, so 26% at 0.82
   arrives where a solid ~29% would. Checked at the bottom of the band too
   (hsl(31,38%,31%)), where a veil is most at risk of dissolving â€” still
   1.56:1, so the separation holds for the panel's whole height.

   Type is unchanged from v18. The light set clears comfortably on this plate â€”
   #fffdf8 numerals at 8.8:1, cream copy at 7.5, mono labels at 6.9 â€” all
   better than they measured on the green, so nothing below needed to move.
   ========================================================================= */
.mission-panel {
  /* load-bearing: the base rule sets an opaque background-color (#a68561, the
     pre-v18 tan). A translucent plate must composite over the SECTION BAND, so
     that colour has to be cleared â€” if it survives, the veil blends against tan
     and every ratio in the note above is wrong. Declared explicitly rather than
     relying on the `background` shorthand's implicit reset, so a later edit to
     background-image alone cannot silently bring the tan back. */
  /* the same crossed hairlines the planner board uses, warmed to the plate so
     the grid reads as ruling ON the surface rather than chalk over it */
  background-image:
    repeating-linear-gradient(to right,
      rgba(255, 226, 214, 0.075) 0, rgba(255, 226, 214, 0.075) 1px,
      transparent 1px, transparent 44px),
    repeating-linear-gradient(to bottom,
      rgba(255, 226, 214, 0.075) 0, rgba(255, 226, 214, 0.075) 1px,
      transparent 1px, transparent 44px),
    /* 0.82 â€” the band is legible through it without the plate going sheer */
    linear-gradient(160deg,
      hsla(6, 37%, 29%, 0.82),
      hsla(5, 38%, 26%, 0.82) 58%,
      hsla(4, 39%, 23%, 0.82));
  background-color: transparent;
  border-color: rgba(255, 214, 200, 0.17);
  /* the shadow warms with the plate; the v18 value was neutral-brown */
  box-shadow: 0 20px 50px rgba(28, 10, 6, 0.3);
}
.mission-panel-lbl {
  color: rgba(255, 226, 214, 0.92);
  border-bottom-color: rgba(255, 214, 200, 0.26);
  padding-left: 14px;
}
.mission-stat b { color: #fffdf8; }
.mission-stat > span { color: rgba(253, 249, 239, 0.86); }
.mission-stat u { color: rgba(255, 214, 200, 0.8); }

/* --- v18, superseded by the rules above -----------------------------------
   The metrics panel takes the planner board's green.

   Sampled, not eyeballed: the .uidemo board composites to rgb(84,109,87) up
   top and rgb(67,92,71) toward the bottom (median of the rendered pixels, so
   the 40px chalk grid does not pull it). The panel sits between those two.

   Opaque, not a film. The board gets its colour from a green glass over a
   photographed classroom; the same alphas over this band's warm rgb(120,91,52)
   ground composite to olive â€” the exact failure the v14 note records. With no
   footage to read through there is nothing for translucency to buy here.
   [v19: still true of green. A red film has no such failure mode.]

   The type has to flip with it. Against this green the tan panel's near-black
   measures 2.7:1; the light set the band already uses clears comfortably â€”
   #fffdf8 numerals at 6.4:1, cream copy at 5.0, mono labels at 4.6. Those are
   the same colours the coverage panel below now uses, so the two panels read
   as one family.
   ------------------------------------------------------------------------ */


/* ============================================================================
   v19 â€” the coverage panel takes the seam tan; the metrics panel keeps green.

   Form stays exactly as v17 left it â€” one panel, hairline cells, bare glyphs,
   mono micro-labels. Only the plate and everything that had to follow it move.

   Everything follows, because tan is LIGHT. On #a68561 the v17 light set
   collapses: cream copy 3.2:1, white headings 3.4:1 (the number the v16 note
   already records), and the four glyph hues â€” apricot, sky, sage, pale slate,
   all mixed for a dark ground â€” land between 1.4:1 and 1.8:1, effectively
   invisible. So the panel inverts wholesale:

   Â· type goes to the v16 pair, --ink for headings (5.6:1) and #241e15 for the
     body (5.0:1; --ink-2 alone measured 3.9)
   Â· each pathway hue is re-cut as an INK version of itself â€” same four app
     colours, taken down to where a 23px glyph clears the 3:1 that non-text UI
     needs: 3.5 / 3.3 / 3.2 / 3.7
   Â· LIVE keeps its green meaning but has to go almost to black to hold 4.6:1
     at 9.5px; --green-deep on this tan is 2.2:1. NEXT is warm ink at 0.85,
     which is the lowest alpha that still clears 4.5.
   ========================================================================= */
.paths-grid {
  --hair: rgba(20, 14, 8, 0.17);
  background: #86674B;
  border-color: rgba(188, 154, 122, 0.14);
}

/* the same four app hues, re-cut for a light ground */
.pathc-cam { --ic: 95, 37, 16; }    /* terracotta ink */
.pathc-low { --ic: 30, 61, 97; }    /* maths blue ink */
.pathc-pyp { --ic: 26, 64, 41; }    /* science green ink */
.pathc-myp { --ic: 38, 51, 74; }    /* library navy ink */

.pathc h4 { color: var(--ink); }
.pathc p { color: #241e15; }
.pathc-status.live { color: #0f2817; }
.pathc-status.next { color: rgba(20, 14, 8, 0.85); }


/* ============================================================================
   v20 â€” the coverage panel copies the reference exactly: tan plate, WHITE type.

   This reverses v19's inversion on request. Stated plainly because the file
   records the opposite decision twice: white on #a68561 is 3.4:1 and cream
   copy 3.2:1, so the headings clear the 3.0 that large text needs and the
   13.5px body sits under the 4.5 AA asks for. Asked for and applied; if the
   copy ever needs to pass, the plate has to darken to about #8a6a4a rather
   than the type going lighter â€” white tops out where it is.

   Everything the tan pulled dark comes back light with it: the cell rules and
   the panel border (the reference's own hairline under its label is light on
   the same tan), the glyphs, and both micro-labels.
   ========================================================================= */
.paths-grid {
  --hair: rgba(255, 255, 255, 0.24);
  border-color: rgba(255, 255, 255, 0.22);
}

/* the four pathway hues cannot survive here â€” apricot, sky, sage and pale
   slate all measure under 1.8:1 on this tan, and their ink re-cuts belong to
   dark type. The glyph goes cream with the headings. */
.pathc-cam, .pathc-low, .pathc-pyp, .pathc-myp { --ic: 253, 249, 239; }
.pathc-ico { opacity: 0.96; }

.pathc h4 { color: #ffffff; }
.pathc p { color: rgba(253, 249, 239, 0.95); }
.pathc-status.live { color: #ffffff; }
.pathc-status.next { color: rgba(253, 249, 239, 0.78); }

/* =========================================================================
   INSIDE LANTERN (#features) â€” the feature switcher.
   A pill row picks a feature; a one-line caption + proof chips swap with it;
   the stage below plays a coded scene of the real app flow (no screenshots,
   no app chrome â€” just the feature). Choreography: site.js, FP_SCENES.
   ========================================================================= */
.fp { margin-top: 4px; }

/* The head is heading-only here â€” no deck paragraph â€” so drop the 20px the h2
   reserves for one, otherwise the gap to the pills reads as 80px. */
#features .sec-head h2 { margin-bottom: 0; }

/* â”€â”€ pills â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€ */
.fp-pills {
  display: flex; flex-wrap: wrap; justify-content: center;
  gap: 11px; max-width: 860px; margin: 0 auto 26px;
}
.fp-pill {
  display: inline-flex; align-items: center; gap: 9px;
  background: var(--white); color: var(--ink);
  border: 1px solid var(--line-2); border-radius: var(--r-pill);
  padding: 12px 20px; font-family: inherit;
  font-size: 15px; font-weight: 600; letter-spacing: -0.005em;
  cursor: pointer;
  transition: border-color 0.18s var(--ease), transform 0.18s var(--ease),
    background 0.18s var(--ease), color 0.18s var(--ease),
    box-shadow 0.18s var(--ease);
}
.fp-pill svg { width: 17px; height: 17px; flex: none; }
.fp-pill:hover { border-color: var(--ink-2); transform: translateY(-1px); }
.fp-pill:focus-visible { outline: 2.5px solid var(--accent-deep); outline-offset: 3px; }
.fp-pill[aria-pressed="true"] {
  background: var(--accent); border-color: var(--accent); color: #fff;
  box-shadow: 0 10px 22px -12px rgba(160, 27, 18, 0.55);
}

/* â”€â”€ caption + proof chips â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€ */
.fp-info { display: none; }
.fp-info.on { display: block; animation: fpIn 0.32s var(--ease); }
@keyframes fpIn { from { opacity: 0; transform: translateY(5px); } }
.fp-caption {
  text-align: center; color: var(--muted);
  font-size: 16.5px; line-height: 1.6;
  max-width: 62ch; margin: 0 auto;
}
.fp-receipts {
  display: flex; flex-wrap: wrap; justify-content: center;
  gap: 9px; margin: 15px auto 0; max-width: 760px;
}
.fp-rcp {
  display: inline-flex; align-items: center; gap: 8px;
  background: var(--white); border: 1px solid var(--line);
  border-radius: 12px; padding: 8px 14px;
  font-size: 13px; font-weight: 600; color: var(--ink-2);
}
.fp-rcp svg { width: 13px; height: 13px; color: var(--green-deep); flex: none; }
.fp-rcp b { color: var(--ink); }
.fp-rcp.code {
  font-family: var(--mono); letter-spacing: 0.13em; font-size: 12.5px;
  background: var(--ink); color: var(--paper); border-color: var(--ink);
}
.fp-rcp.chat {
  border-radius: var(--r-pill);
  background: var(--accent-tint); border-color: transparent;
  color: var(--accent-deep);
}
.fp-rcp.chat svg { color: var(--accent); }
.fp-rcp.kbd { font-family: var(--mono); font-size: 12px; letter-spacing: 0.02em; }
.fp-rcp.mix { gap: 6px; }
.fp .dot { width: 8px; height: 8px; border-radius: 50%; flex: none; }
.fp .dot.recall { background: var(--app-good); }
.fp .dot.apply { background: var(--app-blue); }
.fp .dot.stretch { background: var(--app-orange); }
.fp-rcp.mix .dot:not(:first-child) { margin-left: 4px; }

/* â”€â”€ the stage â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€ */
.fp-stage {
  /* the app's own palette (lantern-teach-ui globals.css), so the scenes read
     as the product, not as the landing page: ink primary buttons, caramel
     accent, science-green doc accent, and the report's muted band hues */
  --app-primary: #23231f;
  --app-accent: #c8895a; --app-accent-soft: #f2e6d7; --app-accent-ink: #a2673a;
  --app-good: #3f7d54; --app-good-soft: #e2eee2;
  --app-warn: #c79226; --app-warn-soft: #f5eccd; --app-warn-ink: #9a7418;
  --app-bad: #c56048;
  --app-blue: #4f7fb8; --app-orange: #d07a2e;
  --app-secure: #dfeadf; --app-secure-ink: #38614a;
  --app-developing: #f0e6cf; --app-developing-ink: #7d6222;
  --app-emerging: #f0dedc; --app-emerging-ink: #8d4a44;
  position: relative; max-width: 880px; height: 566px;
  margin: 30px auto 0;
  background: var(--card); border: 1px solid var(--line-2);
  border-radius: var(--r-lg); box-shadow: var(--sh-2);
  overflow: hidden;
}
.fp-scene {
  position: absolute; inset: 0; display: none;
  padding: 26px 30px;
  align-items: center; justify-content: center;
}
.fp-scene.on { display: flex; animation: fpIn 0.32s var(--ease); }

/* shared mini-UI vocabulary */
.fp-lbl {
  font-family: var(--mono); font-size: 10.5px; font-weight: 700;
  letter-spacing: 0.14em; text-transform: uppercase;
  color: var(--muted); margin: 0;
}
.fp-note { font-size: 11.5px; color: var(--muted); font-weight: 500; }
.fp-ghost {
  display: inline-flex; align-items: center; gap: 6px;
  padding: 5px 11px; border-radius: var(--r-pill);
  border: 1px dashed var(--line-2);
  font-size: 11.5px; font-weight: 600; color: var(--muted);
  white-space: nowrap; position: relative;
}
.fp-ghost svg { width: 12px; height: 12px; color: var(--app-accent-ink); flex: none; }
.fp-tag {
  display: inline-flex; align-items: center; gap: 6px;
  padding: 3px 10px; border-radius: var(--r-pill);
  background: var(--white); border: 1px solid var(--line);
  font-size: 11px; font-weight: 600; color: var(--ink-2); white-space: nowrap;
}
.fp-tag.subject i { width: 7px; height: 7px; border-radius: 50%; background: var(--green); }
.fp-tag.quiet { color: var(--muted); }
.fp-state {
  font-style: normal; flex: none; padding: 3px 10px;
  border-radius: var(--r-pill); font-size: 10px; font-weight: 700;
  opacity: 0; transform: scale(0.82);
  transition: opacity 0.3s var(--ease), transform 0.3s var(--ease);
}
.fp-state.secure { background: var(--app-secure); color: var(--app-secure-ink); }
.fp-state.developing { background: var(--app-developing); color: var(--app-developing-ink); }
.fp-state.emerging { background: var(--app-emerging); color: var(--app-emerging-ink); }
.fp-toastc {
  position: absolute; left: 50%; bottom: 16px;
  transform: translate(-50%, 12px); opacity: 0;
  background: var(--ink); color: var(--paper);
  font-size: 12.5px; font-weight: 600;
  padding: 9px 16px; border-radius: var(--r-pill);
  box-shadow: var(--sh-2); white-space: nowrap; pointer-events: none;
  transition: opacity 0.3s var(--ease), transform 0.3s var(--ease);
}
.fp-toastc.show { opacity: 1; transform: translate(-50%, 0); }
.fp-busy {
  display: flex; align-items: center; gap: 8px; margin: 0;
  font-size: 12px; font-weight: 600; color: var(--muted);
  opacity: 0; transition: opacity 0.3s var(--ease);
}
.fp-busy.show { opacity: 1; }
.fp-busy i {
  width: 10px; height: 10px; border-radius: 50%; flex: none;
  border: 2px solid var(--app-accent-soft); border-top-color: var(--app-accent-ink);
  animation: fpSpin 0.8s linear infinite;
}
@keyframes fpSpin { to { transform: rotate(1turn); } }
/* generic enter helper + pointer taps on non-buttons */
.fp .rise {
  opacity: 0; transform: translateY(10px);
  transition: opacity 0.45s var(--ease), transform 0.45s var(--ease);
}
.fp .rise.in { opacity: 1; transform: none; }
.fp .aim .wz-ptr { opacity: 1; transform: translate(0, 0); }
.fp .press:not(.wz-btn) { transform: scale(0.95); }
.fp .wz-btn.sm { padding: 7px 13px; font-size: 11.5px; }
/* in the app every one of these is .btn-primary: near-black ink, never red */
.fp .wz-btn {
  background: var(--app-primary);
  box-shadow: 0 8px 20px rgba(26, 26, 24, 0.24);
}
.fp .wz-btn.press { filter: brightness(1.6); }

/* â”€â”€ scene Â· plans â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€ */
.fp-plan {
  width: 100%; max-width: 640px;
  display: flex; flex-direction: column; gap: 9px;
}
.fp-doc-h {
  font-family: var(--display); font-size: 19px; font-weight: 600;
  letter-spacing: -0.015em; margin: 0; color: var(--ink);
}
.fp-chiprow { display: flex; gap: 6px; margin-top: 7px; flex-wrap: wrap; }
.fp-sect {
  position: relative; background: var(--white);
  border: 1px solid var(--line); border-radius: 14px;
  padding: 12px 14px;
}
.fp-sect h5 { margin: 0 0 7px; font-size: 13px; font-weight: 700; color: var(--ink); }
.fp-sect ul { margin: 0; padding-left: 16px; display: flex; flex-direction: column; gap: 4px; }
.fp-sect li { font-size: 12.5px; line-height: 1.5; color: var(--ink-2); }
.fp-sect li b { color: var(--ink); font-weight: 700; }
.fp-sect h5 { display: flex; align-items: center; gap: 8px; }
.fp-selpick {
  font-size: 10px; font-weight: 700; color: var(--muted);
  border: 1px dashed var(--line-2); border-radius: var(--r-pill);
  padding: 2px 8px; white-space: nowrap;
}
/* the card's own refine row, under a dashed rule â€” exactly the app's layout */
.fp-sect-foot {
  display: flex; align-items: center; gap: 6px; flex-wrap: wrap;
  margin-top: 10px; padding-top: 9px; border-top: 1px dashed var(--line);
  opacity: 0; transition: opacity 0.4s var(--ease) 0.25s;
}
.fp-sect.in .fp-sect-foot { opacity: 1; }
.fp-chipbtn {
  padding: 4px 10px; border-radius: var(--r-pill);
  border: 1px solid var(--line); background: var(--white);
  font-size: 10.5px; font-weight: 600; color: var(--muted); white-space: nowrap;
}
.fp-planbtn { margin-left: auto; }
.fp-viewbar { display: flex; align-items: center; gap: 12px; flex-wrap: wrap; }
.fp-seg {
  display: inline-flex; gap: 3px; padding: 3px;
  background: var(--paper-2); border-radius: 11px; flex: none;
}
.fp-seg span {
  font-size: 11.5px; font-weight: 650; color: var(--ink-2);
  padding: 5px 12px; border-radius: 8px; white-space: nowrap;
}
.fp-seg span.on { background: var(--app-primary); color: #fff; }
.fp-note.center { text-align: center; }
.fp-grouplab {
  font-family: var(--mono); font-size: 9.5px; font-weight: 700;
  letter-spacing: 0.14em; text-transform: uppercase; color: var(--muted);
  margin: 0 0 -2px;
}

/* â”€â”€ scene Â· worksheets â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€ */
.fp-ws {
  width: 100%; max-width: 700px;
  display: grid; grid-template-columns: 1fr 228px; gap: 16px;
  align-items: start;
}
.fp-ws-cards { display: flex; flex-direction: column; gap: 10px; min-width: 0; }
.fp-qc {
  background: var(--white); border: 1px solid var(--line);
  border-radius: 14px; padding: 12px 14px;
  transition: border-color 0.3s var(--ease), box-shadow 0.3s var(--ease);
}
.fp-qc header { display: flex; align-items: center; gap: 8px; margin-bottom: 6px; }
.fp-qn {
  width: 20px; height: 20px; border-radius: 7px; flex: none;
  background: var(--ink); color: var(--paper);
  font-size: 11px; font-weight: 700; font-style: normal;
  display: grid; place-items: center;
}
.fp-qk {
  font-family: var(--mono); font-size: 9.5px; font-weight: 700;
  letter-spacing: 0.12em; text-transform: uppercase; color: var(--muted);
  white-space: nowrap;
}
.fp-qm { margin-left: auto; font-size: 11px; font-weight: 700; color: var(--ink-2); white-space: nowrap; }
.fp-qc > p { margin: 0; font-size: 12.5px; line-height: 1.5; color: var(--ink-2); }
.fp-qacts {
  display: flex; gap: 6px; flex-wrap: wrap;
  max-height: 0; opacity: 0; margin-top: 0; overflow: hidden;
  transition: max-height 0.4s var(--ease), opacity 0.4s var(--ease), margin-top 0.4s var(--ease);
}
.fp-qacts.show { max-height: 64px; opacity: 1; margin-top: 10px; }
.fp-qacts span {
  display: inline-flex; align-items: center; gap: 5px;
  padding: 5px 10px; border-radius: var(--r-pill);
  border: 1px solid var(--line-2); background: var(--white);
  font-size: 11px; font-weight: 600; color: var(--ink-2); white-space: nowrap;
}
.fp-qacts svg { width: 11px; height: 11px; color: var(--app-accent-ink); }
.fp-rail {
  background: #e2eee2; /* the app's --dd-accent-soft for a Science doc */
  border: 1px solid color-mix(in srgb, var(--app-good) 16%, var(--line));
  border-radius: 16px; padding: 13px; align-self: start;
  display: flex; flex-direction: column; gap: 10px;
}
.fp-railmix { padding-bottom: 11px; border-bottom: 1px solid color-mix(in srgb, var(--app-good) 14%, var(--line)); }
.fp-railtabs {
  display: flex; padding: 3px; border-radius: 12px;
  background: color-mix(in srgb, var(--app-good) 7%, #fdfcf8);
}
.fp-railtab {
  flex: 1 1 auto; display: inline-flex; align-items: center; justify-content: center;
  gap: 5px; padding: 7px 6px; border-radius: 9px;
  font-size: 11.5px; font-weight: 650; color: var(--muted); white-space: nowrap;
}
.fp-railtab svg { width: 13px; height: 13px; flex: none; }
.fp-railtab b { font-size: 10px; font-weight: 700; color: var(--muted); }
.fp-railtab.on { background: #fdfcf8; color: var(--ink); box-shadow: 0 1px 3px rgba(26, 26, 24, 0.1); }
.fp-railtab.on svg { color: var(--app-good); }
.fp-selchip {
  display: none; align-items: center; gap: 6px; align-self: flex-start;
  font-size: 11px; font-weight: 650; color: var(--app-good);
  background: #fdfcf8; border-radius: var(--r-pill); padding: 4px 8px 4px 10px;
  white-space: nowrap;
}
.fp-selchip.show { display: inline-flex; animation: fpIn 0.3s var(--ease); }
.fp-selchip svg { width: 12px; height: 12px; flex: none; }
.fp-selchip b { font-weight: 700; color: var(--muted); margin-left: 2px; }
.fp-refbox {
  position: relative; background: #fdfcf8;
  border: 1px solid var(--line-2); border-radius: 14px;
  padding: 11px 13px; min-height: 74px;
}
.fp-refbox.slim { min-height: 0; padding: 9px 12px; }
.fp-refph { margin: 0; font-size: 12px; line-height: 1.5; color: var(--muted); padding-right: 34px; }
.fp-reftyped { display: none; margin: 0; font-size: 12px; line-height: 1.5; color: var(--ink); padding-right: 34px; }
.fp-refbox.typing .fp-refph { display: none; }
.fp-refbox.typing .fp-reftyped { display: block; }
.fp-send {
  position: absolute; right: 8px; bottom: 8px;
  width: 28px; height: 28px; border-radius: 9px;
  background: var(--app-good); color: #fff;
  display: grid; place-items: center;
}
.fp-send svg { width: 14px; height: 14px; }
.fp-send.warm { background: var(--app-accent-ink); }
.fp-send.press { transform: scale(0.92); filter: brightness(1.2); }
.fp-hint { margin: 0; font-size: 10.5px; line-height: 1.5; color: var(--muted); }
.fp-working {
  display: none; align-items: center; gap: 10px;
  background: #fdfcf8; border: 1px solid var(--line-2);
  border-radius: 13px; padding: 11px 13px;
}
.fp-working.show { display: flex; animation: fpIn 0.3s var(--ease); }
.fp-working > div { display: flex; flex-direction: column; gap: 1px; }
.fp-working b { font-size: 12px; color: var(--ink); }
.fp-working span { font-size: 10.5px; color: var(--muted); }
.fp-orb {
  width: 20px; height: 20px; border-radius: 50%; flex: none;
  background: conic-gradient(from 0deg, var(--app-good), color-mix(in srgb, var(--app-good) 10%, transparent), var(--app-good));
  animation: fpSpin 1.1s linear infinite;
}
/* while she works, the prompt gives way to the working card â€” like the rail */
.fp-rail.busy .fp-refbox, .fp-rail.busy .fp-quick, .fp-rail.busy .fp-selchip { display: none; }
.fp-mix {
  display: flex; height: 10px; margin-top: 10px;
  border-radius: var(--r-pill); overflow: hidden;
  background: var(--paper); border: 1px solid var(--line);
}
.fp-mix i { width: 0%; transition: width 0.6s var(--ease); }
.fp-mix i.recall { background: var(--app-good); }
.fp-mix i.apply { background: var(--app-blue); transition-delay: 0.12s; }
.fp-mix i.stretch { background: var(--app-orange); transition-delay: 0.24s; }
/* marks-weighted, like the app: 3 + 6 + 3 of 12 marks */
.fp-mix.fill i.recall { width: 25%; }
.fp-mix.fill i.apply { width: 50%; }
.fp-mix.fill i.stretch { width: 25%; }
.fp-mixleg {
  list-style: none; margin: 11px 0 0; padding: 0;
  display: flex; flex-direction: column; gap: 6px;
}
.fp-mixleg li { display: flex; align-items: center; gap: 8px; font-size: 12px; font-weight: 600; color: var(--ink-2); }
.fp-mixleg b { margin-left: auto; font-variant-numeric: tabular-nums; color: var(--ink); }
.fp-total {
  margin: 12px 0 0; padding-top: 10px; border-top: 1px dashed var(--line);
  font-size: 12px; color: var(--muted);
}
.fp-total b { color: var(--ink); font-variant-numeric: tabular-nums; }

/* â”€â”€ scene Â· ai refine â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€ */
.fp-rf { width: 100%; max-width: 680px; }
.fp-rf-grid {
  display: grid; grid-template-columns: 1fr 236px; gap: 16px;
  align-items: start;
}
.fp-rf-doc { display: flex; flex-direction: column; gap: 10px; }
/* the app's DocDetail accent for a Science doc is the science green */
.fp-qc.rf.sel { border-color: var(--app-good); box-shadow: 0 0 0 3px var(--app-good-soft); }
.fp-quick { display: flex; gap: 6px; flex-wrap: wrap; }
.fp-quick span {
  padding: 5px 10px; border-radius: var(--r-pill);
  border: 1px solid var(--line-2); background: var(--white);
  font-size: 11px; font-weight: 600; color: var(--ink-2); white-space: nowrap;
}
.fp-rv {
  position: absolute; left: 24px; right: 24px; bottom: 18px;
  background: var(--white); border: 1px solid var(--line-2);
  border-radius: 16px; box-shadow: var(--sh-2);
  padding: 14px 16px;
  opacity: 0; transform: translateY(18px); pointer-events: none;
  transition: opacity 0.4s var(--ease), transform 0.4s var(--ease);
}
.fp-rv.show { opacity: 1; transform: none; }
.fp-rv-head { display: flex; align-items: flex-start; gap: 9px; margin-bottom: 10px; }
.fp-rv-head svg { width: 15px; height: 15px; color: var(--app-accent-ink); flex: none; margin-top: 2px; }
.fp-rv-head span { display: flex; flex-direction: column; gap: 1px; }
.fp-rv-head b { font-size: 13px; color: var(--ink); }
.fp-rv-head em { font-style: normal; font-size: 11px; color: var(--muted); }
.fp-rv-cols { display: grid; grid-template-columns: 1fr 1fr; gap: 10px; }
.fp-rv-col {
  border: 1px solid var(--line); border-radius: 10px;
  padding: 10px 12px; background: var(--paper);
}
.fp-rv-col.rev { background: var(--white); border-color: var(--line-2); }
.fp-rv-colhead {
  display: flex; align-items: center; gap: 6px; margin-bottom: 6px;
  font-family: var(--mono); font-size: 9.5px; font-weight: 700;
  letter-spacing: 0.12em; text-transform: uppercase; color: var(--muted);
}
.fp-rv-colhead i { width: 7px; height: 7px; border-radius: 50%; background: currentColor; }
.fp-rv-col.rev .fp-rv-colhead { color: var(--app-good); }
.fp-rv-col p { margin: 0; font-size: 12px; line-height: 1.5; color: var(--ink-2); }
.fp-rv-note {
  display: flex; align-items: flex-start; gap: 6px; margin-top: 8px;
  font-size: 11px; font-weight: 600; color: var(--app-accent-ink);
}
.fp-rv-note svg { width: 12px; height: 12px; flex: none; margin-top: 1px; }
.fp-rv-foot { display: flex; justify-content: flex-end; align-items: center; gap: 8px; margin-top: 12px; }

/* â”€â”€ scene Â· marking â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€ */
.fp-mk { position: relative; width: 100%; max-width: 520px; height: 100%; }
.fp-mk-pane {
  position: absolute; inset: 0;
  display: flex; flex-direction: column; gap: 12px; justify-content: center;
  opacity: 0; transform: translateX(16px); pointer-events: none;
  transition: opacity 0.4s var(--ease), transform 0.4s var(--ease);
}
.fp-mk-pane.on { opacity: 1; transform: none; }
.fp-mk-h {
  margin: 0; font-family: var(--display); font-size: 20px;
  font-weight: 600; letter-spacing: -0.015em; color: var(--ink);
}
.fp-mk-h .fp-note { font-size: 12px; }
.fp-prog {
  height: 8px; border-radius: var(--r-pill);
  background: var(--paper); border: 1px solid var(--line); overflow: hidden;
}
.fp-prog i {
  display: block; height: 100%; width: 0%;
  background: var(--app-good); border-radius: var(--r-pill);
  transition: width 1.9s linear;
}
.fp-prog i.done { width: 100%; }
.fp-mk-rows { display: flex; flex-direction: column; gap: 7px; }
.fp-mk-row {
  display: flex; align-items: center; gap: 9px;
  background: var(--white); border: 1px solid var(--line);
  border-radius: 11px; padding: 9px 12px;
  font-size: 12.5px; font-weight: 600; color: var(--ink-2);
}
.fp-mk-row svg { width: 14px; height: 14px; color: var(--app-good); flex: none; }
.fp-mk-row span { margin-left: auto; font-size: 11px; color: var(--muted); font-weight: 500; white-space: nowrap; }
.fp-mk-row.unmatched {
  background: color-mix(in srgb, var(--app-warn-soft) 55%, var(--white));
  border-left: 3px solid var(--app-warn);
}
.fp-mk-row.unmatched b { color: var(--app-warn-ink); font-size: 13px; width: 14px; text-align: center; flex: none; }
.fp-agree { display: flex; gap: 2px; height: 12px; }
.fp-agree i { width: 0; border-radius: 3px; transition: width 0.55s var(--ease); }
.fp-agree i.agree { background: var(--app-good); }
.fp-agree i.partial { background: var(--app-warn); transition-delay: 0.15s; }
.fp-agree i.disagree { background: var(--app-bad); transition-delay: 0.3s; }
.fp-agree.fill i.agree { width: 75%; }
.fp-agree.fill i.partial { width: 16.6%; }
.fp-agree.fill i.disagree { width: 8.4%; }
.fp-flag {
  background: var(--white); border: 1px solid var(--line-2);
  border-radius: 13px; padding: 12px 14px;
}
.fp-flag header { display: flex; justify-content: space-between; align-items: baseline; gap: 8px; margin-bottom: 5px; }
.fp-flag header b { font-size: 12.5px; color: var(--ink); }
.fp-flag > p { margin: 0 0 9px; font-size: 12px; font-style: italic; color: var(--ink-2); line-height: 1.5; }
.fp-markbtns { display: flex; gap: 7px; flex-wrap: wrap; }
.fp-markbtns span {
  padding: 6px 12px; border-radius: var(--r-pill);
  border: 1px solid var(--line-2); background: var(--white);
  font-size: 11.5px; font-weight: 700; color: var(--ink-2); white-space: nowrap;
  transition: background 0.25s var(--ease), border-color 0.25s var(--ease),
    color 0.25s var(--ease), transform 0.25s var(--ease);
}
.fp-markbtns .give.on {
  background: var(--app-good); border-color: var(--app-good); color: #fff;
  transform: scale(1.04);
}
.fp-mk .wz-btn { align-self: center; margin-top: 2px; }

/* â”€â”€ scene Â· reports â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€ */
.fp-rp {
  width: 100%; max-width: 560px;
  display: flex; flex-direction: column; gap: 12px;
  /* one row's height + the column gap â€” the sort animation moves by this */
  --shift: 50px;
}
.fp-objs { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: 8px; }
.fp-objs li {
  display: flex; align-items: center; gap: 10px;
  height: 42px; box-sizing: border-box;
  background: var(--white); border: 1px solid var(--line);
  border-radius: 12px; padding: 0 14px;
  font-size: 12.5px;
  transition: transform 0.55s var(--ease);
}
.fp-objs .code {
  font-family: var(--mono); font-size: 11px; font-weight: 700;
  letter-spacing: 0.02em; color: var(--app-good); flex: none;
}
.fp-objs .t {
  flex: 1; min-width: 0; font-weight: 600; color: var(--ink);
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.fp-objs .n { font-size: 11px; color: var(--muted); white-space: nowrap; }
.fp-objs li.tagged .fp-state { opacity: 1; transform: none; }
/* weakest first: the emerging row rises past two, the secure row gives way */
.fp-objs.sorted [data-rp-row="a"] { transform: translateY(calc(2 * var(--shift))); }
.fp-objs.sorted [data-rp-row="c"] { transform: translateY(calc(-2 * var(--shift))); }
.fp-rp-insight {
  display: flex; align-items: center; gap: 8px; flex-wrap: wrap;
  background: var(--app-accent-soft); border-radius: 12px;
  padding: 11px 14px; font-size: 12.5px; color: var(--ink-2); font-weight: 600;
}
.fp-rp-insight b { font-family: var(--mono); font-size: 11.5px; color: var(--app-accent-ink); }
.fp-rp-insight .fp-ghost { margin-left: auto; border-color: color-mix(in srgb, var(--app-accent-ink) 45%, transparent); color: var(--app-accent-ink); }

/* â”€â”€ scene Â· ask lantern â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€ */
.fp-ak { position: relative; width: 100%; max-width: 640px; height: 100%; }
/* v2: the stage is taller now â€” tighten the drawer's stage so the panel is not
   marooned beside half an empty card */
.fp-ak { max-width: 520px; }
.fp-fab {
  position: absolute; right: 8px; bottom: 4px;
  display: flex; flex-direction: column; align-items: center; gap: 5px;
  background: none; border: none; padding: 10px; cursor: default;
  font-family: inherit; font-size: 11px; font-weight: 700; color: var(--ink-2);
  transition: opacity 0.3s var(--ease), transform 0.3s var(--ease);
}
.fp-fab svg { width: 34px; height: 34px; color: var(--app-warn-ink); position: relative; }
.fp-fab .fp-halo {
  position: absolute; top: 2px; left: 50%; margin-left: -23px;
  width: 46px; height: 46px; border-radius: 50%;
  background: radial-gradient(closest-side, rgba(224, 167, 60, 0.5), transparent 72%);
  animation: fpHalo 2.2s ease-in-out infinite;
}
@keyframes fpHalo { 50% { transform: scale(1.3); opacity: 0.6; } }
.fp-fab.gone { opacity: 0; transform: scale(0.8); }
.fp-ak-panel {
  position: absolute; top: 4px; right: 0; bottom: 4px;
  width: min(370px, 82%);
  background: var(--white); border: 1px solid var(--line-2);
  border-radius: 16px; box-shadow: var(--sh-2);
  padding: 14px 16px; display: flex; flex-direction: column;
  opacity: 0; transform: translateX(26px); pointer-events: none;
  transition: opacity 0.45s var(--ease), transform 0.45s var(--ease);
}
.fp-ak-panel.show { opacity: 1; transform: none; }
.fp-ak-panel > header {
  display: flex; align-items: center; gap: 8px;
}
.fp-ak-hbtns { display: flex; gap: 4px; margin-left: auto; }
.fp-ak-hbtns i {
  width: 22px; height: 22px; border-radius: 7px; display: grid; place-items: center;
  color: var(--muted); background: var(--card);
}
.fp-ak-hbtns svg { width: 12px; height: 12px; }
.fp-ak-seeing {
  display: flex; align-items: center; gap: 6px; margin: 6px 0 10px;
  padding-bottom: 9px; border-bottom: 1px solid var(--line);
  font-size: 10.5px;
}
.fp-ak-seeing span {
  font-family: var(--mono); font-weight: 700; letter-spacing: 0.1em;
  text-transform: uppercase; color: var(--muted);
}
.fp-ak-seeing b { font-weight: 650; color: var(--ink-2); }
.fp-ak-foot {
  margin-top: auto; display: flex; flex-direction: column; gap: 8px;
  padding-top: 10px; border-top: 1px solid var(--line);
}
.fp-ak-panel > header svg { width: 16px; height: 16px; color: var(--app-warn-ink); }
.fp-ak-panel > header b { font-size: 13px; color: var(--ink); }
.fp-ak-thread { display: flex; flex-direction: column; gap: 10px; min-height: 0; }
/* the 'doing' line only takes space while it is actually doing something */
.fp-ak-thread .fp-busy { display: none; }
.fp-ak-thread .fp-busy.show { display: flex; animation: fpIn 0.3s var(--ease); }
.fp-ak-starters { display: flex; flex-direction: column; gap: 6px; align-items: flex-start; }
.fp-ak-starters.gone { display: none; }
.fp-ak-starters span {
  position: relative;
  background: var(--card); border: 1px solid var(--line-2);
  border-radius: var(--r-pill); padding: 8px 13px;
  font-size: 12px; font-weight: 600; color: var(--ink-2);
  transition: transform 0.15s var(--ease);
}
.fp-ak-msg {
  border-radius: 13px; padding: 9px 13px;
  font-size: 12.5px; line-height: 1.55;
  opacity: 0; transform: translateY(8px);
  transition: opacity 0.35s var(--ease), transform 0.35s var(--ease);
}
.fp-ak-msg.in { opacity: 1; transform: none; }
.fp-ak-msg.user { align-self: flex-end; background: var(--app-accent-soft); color: var(--ink); }
.fp-ak-msg.bot { align-self: stretch; background: var(--paper); border: 1px solid var(--line); color: var(--ink-2); }
.fp-ak-msg.bot [data-ak-nav] { display: inline-flex; margin-top: 8px; opacity: 0; transition: opacity 0.3s var(--ease); }
.fp-ak-msg.bot [data-ak-nav].show { opacity: 1; }
.fp .wz-caret {
  display: inline-block; width: 2px; height: 13px; vertical-align: -2px;
  background: var(--app-accent-ink); margin-left: 2px;
  animation: fpBlink 1s steps(1) infinite;
}
.fp .wz-caret.off { display: none; }
@keyframes fpBlink { 50% { opacity: 0; } }

/* â”€â”€ smaller screens â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€ */
@media (max-width: 940px) {
  .fp-stage { height: 592px; }
}
@media (max-width: 700px) {
  .fp-ws { grid-template-columns: 1fr; }
  .fp-ws .fp-rail .fp-railtabs, .fp-ws .fp-rail .fp-refbox,
  .fp-ws .fp-rail .fp-quick, .fp-ws .fp-rail .fp-hint { display: none; }
  .fp-ws .fp-railmix { padding-bottom: 0; border-bottom: 0; }
  .fp-mixleg { flex-direction: row; gap: 12px; }
  .fp-mixleg b { margin-left: 6px; }
  .fp-rf-grid { grid-template-columns: 1fr; }
  .fp-rf .fp-note.center, .fp-rf .fp-quick, .fp-rf .fp-railtabs { display: none; }
  .fp-viewbar .fp-note { display: none; }
  .fp-sect-foot .fp-chipbtn { display: none; }
  .fp-stage { height: 628px; }
  .fp-scene { padding: 20px 18px; }
  .fp-rv { left: 12px; right: 12px; bottom: 12px; }
}
@media (max-width: 560px) {
  .fp-pill { padding: 10px 15px; font-size: 13.5px; gap: 7px; }
  .fp-pill svg { width: 15px; height: 15px; }
  .fp-caption { font-size: 15px; }
  .fp-stage { height: 668px; }
  .fp-rv-cols { grid-template-columns: 1fr; }
  .fp-rv-col:not(.rev) { display: none; }
  .fp-ws .fp-qc[data-q="c"] { display: none; }
  .fp-ak-panel { width: 94%; }
}

/* =========================================================================
   INSIDE LANTERN Â· v2 â€” the deeper scenes.

   The first pass showed each feature's headline move. This one shows the work
   around it, because that is where the product actually is: a worksheet gets
   a question added, re-ordered, read as contents and checked as a printed
   page; a refine redraws a picture, not only a sentence; marking runs the
   app's four real states; and Reports walks class â†’ learner â†’ parent report
   rather than stopping at one sorted list.

   Two rules hold everything together and are worth stating once:

   â€¢ Colour only ever encodes data. The three attainment bands (secure /
     developing / emerging) and the three subject hues below are the app's own
     â€” English clay, Maths slate-blue, Science forest â€” and nothing else in
     these scenes is allowed to borrow them. Chrome stays bone and cream.
   â€¢ Every number shown is internally consistent. A paper that gains a
     question gains its marks, its contents count and its total; a script
     that gets a third mark changes the tally beside it. The scenes are
     small, so an inconsistency here reads as carelessness in the product.
   ========================================================================= */
.fp-stage {
  /* the three bands as marks, not as tints â€” the ring arcs and the roster
     bars need a fill that survives at 6px, which the pale chip grounds don't */
  --band-secure: #7fa78d; --band-developing: #c9a94e; --band-emerging: #c98a80;
  /* the app's SUBJECT_HUE, verbatim */
  --subj-eng: #b0785c; --subj-mat: #5b7c99; --subj-sci: #1f6f4a;
}

/* â”€â”€ shared vocabulary added by v2 â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€ */
.fp-seg span { position: relative; }
.fp-seg span i {
  font-style: normal; margin-left: 5px;
  font-size: 9.5px; font-weight: 700; color: var(--muted);
}
.fp-seg span.on i { color: rgba(255, 255, 255, 0.7); }
.fp-chipbtn.lg {
  display: inline-flex; align-items: center; gap: 6px;
  padding: 6px 12px; font-size: 11.5px; color: var(--ink-2);
}
.fp-chipbtn.lg svg { width: 12px; height: 12px; color: var(--app-good); flex: none; }
.fp-chipbtn.quiet { color: var(--muted); }
.fp-card2 {
  background: var(--white); border: 1px solid var(--line);
  border-radius: 14px; padding: 11px 13px;
}
.fp-card2-head { display: flex; align-items: baseline; gap: 8px; margin-bottom: 9px; }
.fp-card2-head .fp-lbl { margin-right: auto; }
.fp-minis { display: inline-flex; gap: 3px; padding: 2px; background: var(--paper-2); border-radius: 9px; }
.fp-minis b {
  font-size: 10px; font-weight: 700; color: var(--muted);
  padding: 3px 8px; border-radius: 6px; white-space: nowrap;
}
.fp-minis b.on { background: var(--white); color: var(--ink); box-shadow: 0 1px 2px rgba(26, 26, 24, 0.1); }
.fp-badge { padding: 2px 8px; border-radius: var(--r-pill); font-size: 9.5px; font-weight: 700; }
.fp-badge.warn { background: var(--app-warn-soft); color: var(--app-warn-ink); }
.fp-av {
  width: 22px; height: 22px; border-radius: 50%; flex: none;
  display: grid; place-items: center; background: var(--paper-2);
  font-size: 9px; font-weight: 700; color: var(--ink-2);
}
.fp-state.none { background: var(--paper-2); color: var(--muted); }

/* â”€â”€ the circuit figure â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€
   One drawing, two states. `.lit` closes the switch and lights the bulb â€”
   that is the whole payload of the picture refine, so it has to be legible
   at 140px inside a review column. */
.fp-figwrap { margin-top: 8px; display: flex; justify-content: center; }
.fp-fig { width: min(100%, 176px); height: auto; }
.fp-figwrap.sm .fp-fig { width: min(100%, 158px); }
.fp-figwrap.tiny .fp-fig { width: min(100%, 132px); }
.fp-figwrap.onpage .fp-fig { width: min(100%, 168px); }
.fp-fig .w {
  fill: none; stroke: var(--ink-2); stroke-width: 2.4;
  stroke-linecap: round; stroke-linejoin: round;
}
.fp-fig .d { fill: var(--ink-2); }
.fp-fig .bulb { transition: fill 0.4s var(--ease); }
.fp-fig .flab { font-family: var(--mono); font-size: 10px; fill: var(--muted); text-anchor: middle; }
.fp-fig .rays {
  fill: none; stroke: var(--app-warn); stroke-width: 2.4; stroke-linecap: round;
  opacity: 0; transition: opacity 0.4s var(--ease);
}
.fp-fig .lever { transition: opacity 0.35s var(--ease); }
.fp-fig .lever.shut { opacity: 0; }
.fp-figwrap.lit .fp-fig .lever.open { opacity: 0; }
.fp-figwrap.lit .fp-fig .lever.shut { opacity: 1; }
.fp-figwrap.lit .fp-fig .rays { opacity: 1; }
.fp-figwrap.lit .fp-fig .bulb { fill: color-mix(in srgb, var(--app-warn) 34%, transparent); }

/* â”€â”€ scene Â· worksheets, v2 â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€ */
.fp-ws-cards { min-width: 0; }
.fp-qlist { display: flex; flex-direction: column; gap: 9px; }
.fp-ws .fp-qc { padding: 11px 13px; }
.fp-qlist.off { display: none; }
.fp-qc.lift {
  position: relative; z-index: 3;
  border-color: var(--line-2);
  box-shadow: 0 16px 30px -16px rgba(26, 26, 24, 0.5);
}
.fp-qc.moving { transition: transform 0.55s var(--ease), box-shadow 0.3s var(--ease); }
.fp-qc[data-q="new"] { display: none; }
.fp-qc[data-q="new"].in-list { display: block; }
.fp-newtag {
  padding: 2px 7px; border-radius: var(--r-pill);
  background: var(--app-good-soft); color: var(--app-good);
  font-size: 8.5px; font-weight: 700; letter-spacing: 0.06em; text-transform: uppercase;
}
.fp-qacts span.add { position: relative; }
.fp-orderbar { display: none; align-items: center; gap: 6px; margin-left: auto; }
.fp-orderbar.show { display: inline-flex; animation: fpIn 0.3s var(--ease); }
.fp-orderbar b {
  font-size: 10px; font-weight: 700; color: var(--app-accent-ink);
  background: var(--app-accent-soft); padding: 3px 9px; border-radius: var(--r-pill);
  white-space: nowrap;
}

/* the printed page â€” clipped, and it scrolls itself */
.fp-paper { display: none; flex-direction: column; gap: 7px; }
.fp-paper.on { display: flex; animation: fpIn 0.35s var(--ease); }
.fp-paper-scroll {
  position: relative; height: 348px; overflow: hidden;
  background: #fff; border: 1px solid var(--line);
  border-radius: 12px; padding: 14px 16px; box-shadow: var(--sh-1);
}
.fp-paper-scroll::after {
  content: ""; position: absolute; left: 0; right: 0; bottom: 0; height: 42px;
  background: linear-gradient(transparent, #fff 78%); pointer-events: none;
}
.fp-paper-roll { transition: transform 3.6s cubic-bezier(0.4, 0, 0.3, 1); }
.fp-paper.roll .fp-paper-roll { transform: translateY(-300px); }
.fp-paper-h { margin: 0 0 3px; font-family: var(--display); font-size: 13.5px; font-weight: 600; color: var(--ink); }
.fp-paper-meta { margin: 0 0 13px; font-size: 10px; color: var(--muted); }
.fp-paper-q { margin: 0 0 4px; font-size: 11.5px; line-height: 1.55; color: var(--ink-2); }
.fp-paper-q b { color: var(--ink); margin-right: 3px; }
.fp-paper-q em { font-style: normal; color: var(--muted); font-size: 10.5px; }
.fp-paper-opt { margin: 0 0 13px; font-size: 11px; color: var(--muted); }
.fp-paper-rule {
  margin: 2px 0 13px; height: 35px;
  background: repeating-linear-gradient(to bottom, transparent 0 16px, var(--line) 16px 17px);
}
.fp-hint.center { text-align: center; }

/* the rail's two panes */
.fp-railtab { position: relative; }
.fp-railpane { display: none; flex-direction: column; gap: 10px; }
.fp-railpane.on { display: flex; animation: fpIn 0.28s var(--ease); }
.fp-toc { display: flex; flex-direction: column; gap: 4px; }
.fp-toc-row {
  display: flex; align-items: center; gap: 7px;
  background: #fdfcf8; border-radius: 9px; padding: 6px 9px; font-size: 11px;
}
.fp-toc-row b { font-family: var(--mono); font-size: 9.5px; font-weight: 700; color: var(--app-good); flex: none; }
.fp-toc-row span {
  flex: 1; min-width: 0; font-weight: 600; color: var(--ink-2);
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.fp-toc-row i { font-style: normal; font-size: 10px; color: var(--muted); flex: none; }
.fp-toc-row.new { box-shadow: inset 2px 0 0 var(--app-good); }

/* â”€â”€ scene Â· ai refine, v2 â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€ */
/* the doc sits at the top: the review overlay owns the bottom of the stage */
.fp-rf { align-self: flex-start; }
.fp-rf-doc { gap: 8px; }
.fp-rv.pic .fp-rv-col:not(.rev) { justify-content: center; }
.fp-rv.pic .fp-rv-col { display: flex; flex-direction: column; }
.fp-rv.pic .fp-figwrap { margin-top: 4px; }

/* â”€â”€ scene Â· marking, v2 â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€ */
.fp-mk { max-width: 640px; }
.fp-mk-pane { justify-content: flex-start; gap: 9px; }
.fp-mk-h { font-size: 19px; }
.fp-mk-sub {
  margin: 3px 0 -2px; font-family: var(--mono); font-size: 9.5px; font-weight: 700;
  letter-spacing: 0.13em; text-transform: uppercase; color: var(--muted);
}
.fp-mk-row em {
  font-style: normal; margin-left: 7px; padding: 2px 8px; border-radius: var(--r-pill);
  background: #fff; font-size: 10px; color: var(--app-warn-ink);
}
.fp-mk-foot { display: flex; align-items: center; gap: 12px; }
.fp-mk-foot .fp-note { flex: 1; min-width: 0; }
.fp-mk-leg { display: flex; gap: 15px; font-size: 10.5px; font-weight: 600; color: var(--muted); }
.fp-mk-leg i { display: inline-block; width: 8px; height: 8px; border-radius: 2px; margin-right: 5px; }
.fp-mk-leg i.agree { background: var(--app-good); }
.fp-mk-leg i.partial { background: var(--app-warn); }
.fp-mk-leg i.disagree { background: var(--app-bad); }
.fp-mk-acts { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; }
.fp-mk-after { display: none; }
.fp-mk-after.show { display: block; animation: fpIn 0.35s var(--ease); }
.fp-mk-returned {
  display: flex; align-items: center; gap: 7px; margin: 0;
  background: var(--app-good-soft); border-radius: 10px; padding: 8px 12px;
  font-size: 11.5px; font-weight: 600; color: var(--app-secure-ink);
}
.fp-mk-returned svg { width: 13px; height: 13px; flex: none; }
.fp-mk-order { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; }
.fp-mk-order .fp-note { flex: 1; min-width: 140px; }
.fp-mk-group { background: var(--white); border: 1px solid var(--line); border-radius: 14px; padding: 10px 12px; }
.fp-mk-group > header { display: flex; align-items: baseline; gap: 8px; margin-bottom: 8px; }
.fp-mk-group > header b { font-size: 12px; color: var(--ink); }
.fp-mk-group > header .fp-note { margin-left: auto; }
.fp-mk-insight { background: var(--app-accent-soft); border-radius: 11px; padding: 9px 11px; margin-bottom: 9px; }
.fp-mk-insight p { margin: 0; font-size: 11.5px; line-height: 1.5; color: var(--ink-2); }
.fp-mk-insight .fp-mk-seed {
  display: flex; align-items: center; gap: 7px; margin-top: 6px;
  font-size: 10.5px; color: var(--muted);
}
.fp-mk-code {
  font-family: var(--mono); font-size: 9.5px; font-weight: 700;
  background: #fff; color: var(--app-accent-ink);
  padding: 2px 7px; border-radius: var(--r-pill);
}
.fp-mk-links { display: flex; gap: 14px; margin-top: 7px; }
.fp-mk-links em { font-style: normal; font-size: 10.5px; font-weight: 700; color: var(--app-accent-ink); }
.fp-mk-cards { display: grid; grid-template-columns: repeat(3, 1fr); gap: 7px; }
.fp-mk-card {
  position: relative; display: flex; flex-wrap: wrap; align-items: baseline; gap: 4px;
  background: var(--paper); border: 1px solid var(--line); border-radius: 10px;
  padding: 8px 10px;
}
.fp-mk-card b { font-size: 11.5px; color: var(--ink); }
.fp-mk-card > i:not(.wz-ptr) {
  margin-left: auto; font-style: normal; font-size: 10px; font-weight: 700; color: var(--muted);
}
.fp-mk-card em { flex: 1 0 100%; font-style: normal; font-size: 10px; color: var(--muted); }
.fp-mk-card.partial { border-left: 2px solid var(--app-warn); }
.fp-mk-card.disagree { border-left: 2px solid var(--app-bad); }
.fp-mk-meter {
  flex: 1 0 100%; height: 3px; margin-top: 4px;
  border-radius: 2px; background: var(--line); overflow: hidden;
}
.fp-mk-meter i { display: block; height: 100%; }
.fp-mk-meter i.partial { background: var(--app-warn); }
.fp-mk-meter i.disagree { background: var(--app-bad); }

/* one script, one answer */
.fp-mk-rvhead { display: flex; align-items: flex-start; gap: 10px; }
.fp-mk-rvhead .fp-chipbtn { margin-left: auto; }
.fp-mk-paper { color: var(--app-good); }
.fp-mk-rv { display: grid; grid-template-columns: 1fr 246px; gap: 12px; align-items: start; }
.fp-mk-scan {
  background: var(--white); border: 1px solid var(--line); border-radius: 13px;
  padding: 10px 11px; display: flex; flex-direction: column; gap: 6px;
}
.fp-mk-scanh {
  margin: 0; display: flex; align-items: baseline; gap: 8px; flex-wrap: wrap;
  font-family: var(--mono); font-size: 9.5px; font-weight: 700;
  letter-spacing: 0.11em; text-transform: uppercase; color: var(--muted);
}
.fp-mk-scanh span {
  font-family: var(--font); font-size: 10px; font-weight: 500;
  letter-spacing: 0; text-transform: none;
}
.fp-mk-tq {
  display: block; background: var(--paper);
  border: 1px solid var(--line); border-left: 2px solid var(--line-2);
  border-radius: 9px; padding: 7px 9px;
}
.fp-mk-tq b { font-family: var(--mono); font-size: 9.5px; font-weight: 700; color: var(--muted); }
.fp-mk-tq em { display: block; font-style: normal; font-size: 10px; color: var(--muted); margin: 2px 0 3px; }
.fp-mk-tq span { display: block; font-size: 11.5px; font-style: italic; color: var(--ink-2); }
.fp-mk-tq.agree { border-left-color: var(--app-good); }
.fp-mk-tq.partial { border-left-color: var(--app-warn); }
.fp-mk-tq.sel { background: #fff; border-color: var(--app-good); box-shadow: 0 0 0 3px var(--app-good-soft); }
.fp-mk-panel { display: flex; flex-direction: column; gap: 7px; }
.fp-mk-row2 {
  display: flex; align-items: center; gap: 8px; font-size: 11px;
  background: var(--white); border: 1px solid var(--line);
  border-left: 2px solid var(--app-good); border-radius: 9px; padding: 7px 10px;
}
.fp-mk-row2 b { color: var(--ink); font-weight: 650; }
.fp-mk-row2 i { font-style: normal; margin-left: auto; font-weight: 700; color: var(--ink-2); }
.fp-mk-row2 em { font-style: normal; font-size: 10px; color: var(--muted); }
.fp-mk-open {
  display: flex; flex-direction: column; gap: 8px;
  background: var(--white); border: 1px solid var(--app-warn);
  border-radius: 12px; padding: 10px 11px;
}
.fp-mk-open > header { display: flex; align-items: center; gap: 8px; }
.fp-mk-open > header b { font-size: 12px; color: var(--ink); }
.fp-mk-open > header .fp-badge { margin-left: auto; }
.fp-mk-markrow { display: flex; gap: 10px; align-items: flex-start; }
.fp-mk-mark {
  flex: none; font-family: var(--display); font-size: 25px; font-weight: 600;
  line-height: 1; color: var(--ink);
}
.fp-mk-mark i { font-style: normal; font-size: 13px; color: var(--muted); }
.fp-mk-scheme p + p { margin: 2px 0 0; font-size: 10.5px; line-height: 1.5; color: var(--ink-2); }
.fp-mk-answer { background: var(--paper); border-radius: 9px; padding: 7px 9px; }
.fp-mk-answer p + p { margin: 2px 0 0; font-size: 11px; font-style: italic; color: var(--ink-2); }
.fp-mk-fb {
  display: flex; align-items: center; gap: 6px; margin: 0;
  font-size: 10.5px; font-weight: 600; color: var(--app-good);
  opacity: 0; transition: opacity 0.35s var(--ease);
}
.fp-mk-fb.show { opacity: 1; }
.fp-mk-fb svg { width: 12px; height: 12px; flex: none; }
.fp-mk-panel .wz-btn { align-self: stretch; justify-content: center; margin-top: 2px; }

/* â”€â”€ scene Â· reports, v2 â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€ */
.fp-rp { position: relative; width: 100%; max-width: 100%; height: 100%; gap: 0; }
.fp-rp-pane {
  position: absolute; inset: 0; display: flex; flex-direction: column; gap: 10px;
  opacity: 0; transform: translateX(16px); pointer-events: none;
  transition: opacity 0.4s var(--ease), transform 0.4s var(--ease);
}
.fp-rp-pane.on { opacity: 1; transform: none; }
.fp-rp-head { display: flex; align-items: flex-start; gap: 10px; }
.fp-rp-head .fp-chiprow { margin: 0 0 0 auto; align-items: center; }
/* the header button sits hard against the stage's clipped right edge, so its
   pointer is tucked inside rather than hanging off the corner */
.fp-rp-head .wz-btn .wz-ptr { right: 2px; bottom: -5px; }
.fp-rp-scroll { position: relative; flex: 1; min-height: 0; overflow: hidden; }
.fp-rp-scroll::after {
  content: ""; position: absolute; left: 0; right: 0; bottom: 0; height: 30px; z-index: 2;
  background: linear-gradient(transparent, var(--card) 84%); pointer-events: none;
}
.fp-rp-roll { display: flex; flex-direction: column; gap: 10px; transition: transform 1.5s var(--ease); }
.fp-rp-filters { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; }

/* attainment rings â€” three segments of the class, weakest first clockwise */
.fp-rings { display: flex; gap: 18px; justify-content: center; }
.fp-ring-card { display: flex; flex-direction: column; align-items: center; }
.fp-ring { width: 86px; height: 86px; }
.fp-rings.sm .fp-ring { width: 74px; height: 74px; }
.fp-ring .track { fill: none; stroke: var(--paper-2); stroke-width: 9; }
.fp-ring .seg {
  fill: none; stroke-width: 9;
  stroke-dasharray: 0 289; stroke-dashoffset: var(--do, 0);
  transform: rotate(-90deg); transform-origin: 50% 50%;
  transition: stroke-dasharray 0.85s var(--ease);
}
.fp-rings.fill .seg { stroke-dasharray: var(--da); }
.fp-ring .seg.developing { transition-delay: 0.1s; }
.fp-ring .seg.secure { transition-delay: 0.2s; }
.fp-ring .seg.emerging { stroke: var(--band-emerging); }
.fp-ring .seg.developing { stroke: var(--band-developing); }
.fp-ring .seg.secure { stroke: var(--band-secure); }
.fp-ring .seg.one.eng { stroke: var(--subj-eng); }
.fp-ring .seg.one.mat { stroke: var(--subj-mat); }
.fp-ring .seg.one.sci { stroke: var(--subj-sci); }
.fp-ring .num {
  font-family: var(--display); font-size: 21px; font-weight: 600; fill: var(--ink);
  text-anchor: middle; dominant-baseline: central;
}
.fp-ring .pct { font-size: 11px; fill: var(--muted); }
.fp-ring-sub { margin: 3px 0 0; font-size: 11px; font-weight: 700; }
.fp-ring-sub.eng { color: var(--subj-eng); }
.fp-ring-sub.mat { color: var(--subj-mat); }
.fp-ring-sub.sci { color: var(--subj-sci); }
.fp-ring-cap { margin: 0; font-size: 9.5px; color: var(--muted); }
.fp-statelegend {
  display: flex; gap: 13px; justify-content: center; flex-wrap: wrap; margin-top: 9px;
  font-size: 10px; font-weight: 600; color: var(--muted);
}
.fp-statelegend i { display: inline-block; width: 8px; height: 8px; border-radius: 50%; margin-right: 5px; }
.fp-statelegend i.secure { background: var(--band-secure); }
.fp-statelegend i.developing { background: var(--band-developing); }
.fp-statelegend i.emerging { background: var(--band-emerging); }
.fp-statelegend i.eng { background: var(--subj-eng); }
.fp-statelegend i.mat { background: var(--subj-mat); }
.fp-statelegend i.sci { background: var(--subj-sci); }
.fp-statelegend .fp-note { font-weight: 500; }

/* the roster */
.fp-roster { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: 6px; }
.fp-roster li {
  position: relative; display: flex; align-items: center; gap: 9px;
  background: var(--paper); border: 1px solid var(--line);
  border-radius: 10px; padding: 7px 10px;
}
.fp-roster .fp-state { opacity: 1; transform: none; }
.fp-lr-main { flex: 1; min-width: 0; display: flex; flex-direction: column; }
.fp-lr-main b { font-size: 11.5px; color: var(--ink); }
.fp-lr-main em { font-style: normal; font-size: 10px; color: var(--muted); }
.fp-lr-bars { flex: none; width: 74px; }
.fp-lr-bars .mb { display: flex; height: 6px; border-radius: 3px; overflow: hidden; background: var(--paper-2); }
.fp-lr-bars .mb b { display: block; height: 100%; }
.fp-lr-bars .mb b.secure, .fp-objs .sb i.secure { background: var(--band-secure); }
.fp-lr-bars .mb b.developing, .fp-objs .sb i.developing { background: var(--band-developing); }
.fp-lr-bars .mb b.emerging, .fp-objs .sb i.emerging { background: var(--band-emerging); }

/* the objective rows gain the class's own spread */
.fp-objs .sb {
  flex: none; width: 84px; height: 6px; display: flex;
  border-radius: 3px; overflow: hidden; background: var(--paper-2);
}
.fp-objs .sb i { display: block; height: 100%; }
.fp-objs.learner li { height: auto; padding: 9px 12px; align-items: center; }
.fp-objs.learner .t { display: flex; flex-direction: column; white-space: normal; overflow: visible; }
.fp-objs.learner .t em { font-style: normal; font-size: 10px; font-weight: 500; color: var(--muted); }
.fp-objs.learner .fp-state { opacity: 1; transform: none; }

/* progress over time */
.fp-chart { display: block; width: 100%; height: auto; }
.fp-chart .g { stroke: var(--line); stroke-width: 1; }
.fp-chart .gl { font-family: var(--mono); font-size: 9px; fill: var(--muted); text-anchor: end; }
.fp-chart .ln { fill: none; stroke-width: 2.4; stroke-linecap: round; stroke-linejoin: round; }
.fp-chart .ln.eng { stroke: var(--subj-eng); }
.fp-chart .ln.mat { stroke: var(--subj-mat); }
.fp-chart .ln.sci { stroke: var(--subj-sci); }

/* home practice â€” the tutor's reading, kept beside the marking, never inside it */
.fp-card2.home {
  background: color-mix(in srgb, var(--app-warn-soft) 38%, var(--white));
  border-color: color-mix(in srgb, var(--app-warn) 24%, var(--line));
}
.fp-home-l { margin: 0 0 4px; font-size: 11px; color: var(--ink-2); }
.fp-home-l:last-child { margin-bottom: 0; }
.fp-home-l b { font-family: var(--mono); font-size: 9.5px; color: var(--app-warn-ink); margin-right: 5px; }
.fp-home-l span { color: var(--muted); }

/* the parent report writer */
.fp-pw { flex: 1; min-height: 0; display: grid; grid-template-columns: 166px 1fr; gap: 12px; }
.fp-pw-queue {
  display: flex; flex-direction: column; gap: 4px;
  background: var(--white); border: 1px solid var(--line); border-radius: 13px; padding: 9px;
}
.fp-minis.full { display: flex; margin-bottom: 4px; }
.fp-minis.full b { flex: 1; text-align: center; padding: 3px 4px; }
.fp-pw-row {
  display: flex; align-items: center; gap: 7px; padding: 5px 7px;
  border-radius: 8px; font-size: 11px; font-weight: 600; color: var(--ink-2);
}
.fp-pw-row.on { background: var(--paper-2); }
.fp-pw-queue .dotq {
  width: 8px; height: 8px; border-radius: 50%; margin-left: auto; flex: none;
  display: grid; place-items: center;
  transition: background 0.3s var(--ease), width 0.3s var(--ease), height 0.3s var(--ease);
}
.fp-pw-queue .dotq.none { background: var(--line-2); }
.fp-pw-queue .dotq.drafted { background: var(--app-warn); }
.fp-pw-queue .dotq.approved { background: var(--app-good); width: 15px; height: 15px; }
.fp-pw-queue .dotq svg { width: 9px; height: 9px; color: #fff; opacity: 0; transition: opacity 0.3s var(--ease); }
.fp-pw-queue .dotq.approved svg { opacity: 1; }
.fp-pw-doc {
  display: flex; flex-direction: column; gap: 8px; min-width: 0;
  background: var(--white); border: 1px solid var(--line);
  border-radius: 14px; padding: 12px 14px;
}
.fp-pw-dochead { display: flex; align-items: center; gap: 10px; }
.fp-pw-dochead b { display: block; font-size: 13px; color: var(--ink); }
.fp-pw-dochead em { font-style: normal; font-size: 10px; color: var(--muted); }
.fp-pw-minirings { display: flex; gap: 6px; margin-left: auto; }
.fp-pw-minirings i {
  position: relative; width: 26px; height: 26px; border-radius: 50%;
  background: conic-gradient(currentColor calc(var(--p) * 1%), var(--paper-2) 0);
}
.fp-pw-minirings i::after {
  content: ""; position: absolute; inset: 5px; border-radius: 50%; background: var(--white);
}
.fp-pw-minirings i.eng { color: var(--subj-eng); }
.fp-pw-minirings i.mat { color: var(--subj-mat); }
.fp-pw-minirings i.sci { color: var(--subj-sci); }
.fp-pw-text { font-size: 11.5px; line-height: 1.7; color: var(--ink-2); }
.fp-pw-text p { margin: 0 0 7px; }
.fp-pw-text p:last-child { margin-bottom: 0; }
/* the underline IS the claim's provenance â€” three bands, one per state */
.fp-pw-text u { position: relative; text-decoration: none; padding-bottom: 1px; }
.fp-pw-text u.secure { box-shadow: inset 0 -2px 0 var(--band-secure); }
.fp-pw-text u.dev { box-shadow: inset 0 -2px 0 var(--band-developing); }
.fp-pw-text u.emg { box-shadow: inset 0 -2px 0 var(--band-emerging); }
.fp-ph {
  background: var(--app-accent-soft); color: var(--app-accent-ink);
  border-radius: 5px; padding: 1px 5px; font-weight: 600;
}
.fp-evipop {
  position: absolute; left: 0; top: calc(100% + 9px); z-index: 5;
  display: flex; flex-direction: column; gap: 2px; width: 208px;
  background: var(--ink); color: var(--paper); border-radius: 10px;
  padding: 8px 10px; box-shadow: var(--sh-2); pointer-events: none;
  opacity: 0; transform: translateY(5px);
  transition: opacity 0.3s var(--ease), transform 0.3s var(--ease);
}
.fp-pw-text u.show .fp-evipop { opacity: 1; transform: none; }
.fp-mk-card.marked { border-left-color: var(--app-good); }
.fp-mk-card.marked > i:not(.wz-ptr) { color: var(--app-good); }
.fp-mk-card.marked .fp-mk-meter i { background: var(--app-good); }
.fp-evipop b { font-family: var(--mono); font-size: 9.5px; letter-spacing: 0.04em; }
.fp-evipop em { font-style: normal; font-size: 10px; opacity: 0.82; }
.fp-evipop i { font-style: normal; font-size: 10px; opacity: 0.6; }
.fp-prov { margin: 0; padding-top: 7px; border-top: 1px dashed var(--line); font-size: 10px; color: var(--muted); }
.fp-pw-tone { display: flex; align-items: center; gap: 5px; flex-wrap: wrap; }
.fp-pw-tone b {
  font-size: 10.5px; font-weight: 700; color: var(--muted);
  border: 1px solid var(--line); border-radius: var(--r-pill); padding: 3px 9px;
}
.fp-pw-tone b.on { background: var(--app-primary); border-color: var(--app-primary); color: #fff; }
.fp-pw-bar {
  display: flex; align-items: center; gap: 7px; margin-top: auto;
  padding-top: 9px; border-top: 1px solid var(--line);
}
.fp-pw-bar .wz-btn { margin-left: auto; }
.fp-pwprog { display: flex; flex-direction: column; gap: 4px; }
.fp-pwprog em { font-style: normal; font-size: 10px; color: var(--muted); }
.fp-pwprog i { display: block; width: 112px; height: 5px; border-radius: 3px; background: var(--paper-2); overflow: hidden; }
.fp-pwprog i b { display: block; height: 100%; width: 39%; background: var(--app-good); transition: width 0.6s var(--ease); }

/* a question added is a paper re-weighted: 3 + 8 + 3 of 14 marks */
.fp-mix.fill.plus i.recall { width: 21.4%; }
.fp-mix.fill.plus i.apply { width: 57.2%; }
.fp-mix.fill.plus i.stretch { width: 21.4%; }
.fp-markbtns span { position: relative; }
.fp-mk-acts.gone { display: none; }
.fp-chart .ln { transition: stroke-dashoffset 1.5s var(--ease); }

/* â”€â”€ scene Â· ask lantern, v2 â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€ */
.fp-ak-thread { flex: 1; min-height: 0; overflow: hidden; }
.fp-ak-roll { display: flex; flex-direction: column; gap: 9px; transition: transform 0.6s var(--ease); }
.fp-ak-msg { display: none; }
.fp-ak-msg.live { display: block; }
 /* the "take me there" is its own line, never trailing the sentence â€” and it
   takes no room at all until the answer has finished arriving */
.fp-ak-msg.bot .fp-ghost { display: none; }
.fp-ak-msg.bot .fp-ghost.show {
  display: flex; width: fit-content; margin-top: 8px;
  animation: fpIn 0.3s var(--ease);
}
/* the thread is scrolled, so its top edge has to read as "there is more above" */
.fp-ak-thread::before {
  content: ""; position: absolute; left: 0; right: 0; top: 0; height: 22px; z-index: 2;
  background: linear-gradient(var(--white), transparent); pointer-events: none;
}
.fp-ak-thread { position: relative; }
.fp-ak-msg.bot .fp-ghost.show { opacity: 1; }

/* =========================================================================
   INSIDE LANTERN - v3, the two scenes #features keeps, given their context.

   Both were composed as a single move against an empty stage: a rail refining
   one sentence, a drawer answering one question. Both moves are real, and
   both were unreadable alone - a refine with no paper around it cannot show
   what a revision COSTS (nothing: the marks and the objective are held), and
   a drawer with no app behind it drops half its own claim, the half about
   being taken to the right screen.

   So v3 draws the surroundings, and the surroundings are the animation: the
   paper's draft number moves when a revision is saved, the rail keeps a
   version history that grows a row per save, and the app under the drawer
   actually changes screen when a "take me there" is pressed.
   ========================================================================= */

/* -- scene - ai refine, v3 ------------------------------------------------
   A document header and a third question card fill the frame the review
   sheet used to rise into, so the stage is never half empty; the sheet now
   rises OVER the paper behind a scrim, which is what the app does. */
.fp-rf { display: flex; flex-direction: column; gap: 11px; }
.fp-rf-head { display: flex; align-items: flex-start; gap: 12px; }
.fp-rf-title h4 {
  margin: 0; font-family: var(--display); font-size: 15.5px; font-weight: 600;
  letter-spacing: -0.01em; color: var(--ink);
}
.fp-rf-meta {
  margin: 3px 0 0; display: flex; align-items: center; gap: 6px;
  font-size: 10.5px; font-weight: 600; color: var(--muted);
}
.fp-rf-meta i { width: 3px; height: 3px; border-radius: 50%; background: var(--line-2); flex: none; }
.fp-rf-meta b { font-weight: 700; color: var(--ink-2); }
.fp-draft {
  margin-left: auto; flex: none;
  display: flex; flex-direction: column; align-items: flex-end;
  background: #fdfcf8; border: 1px solid var(--line-2); border-radius: 12px;
  padding: 6px 11px;
}
.fp-draft b { font-size: 11.5px; color: var(--ink); }
.fp-draft em { font-style: normal; font-size: 9.5px; color: var(--muted); }
.fp-draft.bump { animation: fpBump 0.55s var(--ease); }
@keyframes fpBump {
  40% { transform: translateY(-3px) scale(1.05); box-shadow: 0 0 0 4px var(--app-good-soft); }
}
/* the objective a question is marked against, and an MCQ's options: two
   things a teacher reads on a paper, and neither of them moves in a refine */
.fp-qobj {
  margin: 7px 0 0; display: flex; align-items: center; gap: 6px;
  font-size: 10.5px; font-weight: 600; color: var(--muted);
}
.fp-qobj svg { width: 12px; height: 12px; color: var(--app-good); flex: none; }
.fp-qopts { margin: 7px 0 0; display: flex; gap: 6px; flex-wrap: wrap; }
.fp-qopts span {
  padding: 3px 9px; border-radius: var(--r-pill);
  background: var(--paper); border: 1px solid var(--line);
  font-size: 10.5px; color: var(--muted); white-space: nowrap;
}
.fp-qc.rf.quiet { opacity: 0.7; }
.fp-qc.rf { transition: border-color 0.3s var(--ease), box-shadow 0.3s var(--ease), transform 0.18s var(--ease); }
.fp-qc.rf header { position: relative; }
/* the shared pointer is a pale arrow, drawn to sit on the ink buttons; on a
   white question card it needs the opposite treatment to be seen at all */
.fp-qc.rf .wz-ptr {
  right: -3px; top: 13px; bottom: auto;
  background: var(--ink); filter: drop-shadow(0 0 2px rgba(255, 255, 255, 0.9));
}
.fp-qc.rf.tap { transform: scale(0.98); box-shadow: 0 0 0 3px var(--app-good-soft); }
/* the revision lands ON the paper: a green wash sweeps the new wording once,
   left to right, then clears - the app's own "this line just changed" */
.fp-qc.rf > p { position: relative; }
.fp-qc.rf > p.swept::after {
  content: ""; position: absolute; inset: -4px -6px; border-radius: 7px;
  background: color-mix(in srgb, var(--app-good) 20%, transparent);
  pointer-events: none; animation: fpSwept 1.8s var(--ease) forwards;
}
@keyframes fpSwept {
  0% { clip-path: inset(0 100% 0 0); opacity: 1; }
  38% { clip-path: inset(0 0 0 0); opacity: 1; }
  100% { clip-path: inset(0 0 0 0); opacity: 0; }
}

/* the review sheet dims the paper it is asking about */
.fp-rf-scrim {
  position: absolute; inset: 0;
  background: color-mix(in srgb, var(--ink) 13%, transparent);
  opacity: 0; pointer-events: none;
  transition: opacity 0.4s var(--ease);
}
.fp-rf-scrim.show { opacity: 1; }

/* Current | Revised as a diff, not as two paragraphs. The strike is drawn by
   animating text-decoration-color, which survives a line wrap where a ruled
   pseudo-element does not; the insert is washed in left to right by growing a
   background - the same gesture the paper gets on save. */
.fp-del, .fp-ins { font-style: normal; }
.fp-del {
  text-decoration: line-through; text-decoration-thickness: 1.5px;
  text-decoration-color: transparent;
  transition: text-decoration-color 0.45s var(--ease), color 0.45s var(--ease);
}
.fp-del.on { text-decoration-color: color-mix(in srgb, var(--app-bad) 80%, transparent); color: var(--muted); }
.fp-ins {
  background-image: linear-gradient(color-mix(in srgb, var(--app-good) 18%, transparent),
                                    color-mix(in srgb, var(--app-good) 18%, transparent));
  background-repeat: no-repeat; background-size: 0 100%;
  -webkit-box-decoration-break: clone; box-decoration-break: clone;
  padding: 1px 2px; margin: 0 -2px; border-radius: 3px; color: var(--muted);
  transition: background-size 0.5s var(--ease), color 0.5s var(--ease);
}
.fp-ins.on { background-size: 100% 100%; color: var(--ink); }
.fp-rv-when { margin-left: auto; font-family: var(--mono); font-size: 9.5px; color: var(--muted); white-space: nowrap; }
/* what did NOT move - the quiet half of a refine, and the half a teacher
   actually worries about */
.fp-keeps {
  display: flex; gap: 6px; flex-wrap: wrap; margin-top: 10px;
  opacity: 0; transform: translateY(4px);
  transition: opacity 0.4s var(--ease), transform 0.4s var(--ease);
}
.fp-keeps.on { opacity: 1; transform: none; }
.fp-keep {
  display: inline-flex; align-items: center; gap: 5px;
  padding: 4px 9px; border-radius: var(--r-pill);
  background: var(--app-good-soft); color: var(--app-good);
  font-size: 10.5px; font-weight: 650; white-space: nowrap;
}
.fp-keep svg { width: 11px; height: 11px; flex: none; }
.fp-keep.warn { background: var(--app-warn-soft); color: var(--app-warn-ink); }
.fp-rv-foot .fp-note { margin-right: auto; }
/* both figures hang from the top of their column, so the redraw reads as the
   same picture twice rather than two pictures at two heights */
.fp-rv.pic .fp-rv-col:not(.rev) { justify-content: flex-start; }
.fp-figcap {
  margin: 4px 0 0; text-align: center;
  font-family: var(--mono); font-size: 9px; letter-spacing: 0.08em;
  text-transform: uppercase; color: var(--muted);
}

/* what she is doing while the orb spins - the refine route's own three steps */
.fp-steps { display: none; list-style: none; margin: 0; padding: 0; flex-direction: column; gap: 7px; }
.fp-rail.busy .fp-steps { display: flex; animation: fpIn 0.3s var(--ease); }
.fp-steps li {
  display: flex; align-items: center; gap: 8px;
  font-size: 11px; font-weight: 600; color: var(--muted); opacity: 0.45;
  transition: opacity 0.3s var(--ease), color 0.3s var(--ease);
}
.fp-steps li.on, .fp-steps li.done { opacity: 1; }
.fp-steps li.done { color: var(--ink-2); }
.fp-steps i {
  position: relative; width: 13px; height: 13px; border-radius: 50%; flex: none;
  border: 1.6px solid var(--line-2); background: #fdfcf8;
}
.fp-steps li.on i { border-color: var(--app-good); }
.fp-steps li.on i::after {
  content: ""; position: absolute; inset: 2px; border-radius: 50%;
  background: var(--app-good); animation: fpPip 1s var(--ease) infinite;
}
@keyframes fpPip { 50% { transform: scale(0.4); opacity: 0.45; } }
.fp-steps li.done i { background: var(--app-good); border-color: var(--app-good); }
.fp-steps li.done i::after {
  content: ""; position: absolute; left: 3.4px; top: 0.8px;
  width: 3px; height: 6.5px; border: 1.8px solid #fdfcf8;
  border-top: 0; border-left: 0; transform: rotate(42deg);
}

/* version history - one row per save, newest first. The receipt behind the
   section's "every draft, recoverable" chip, which until now was a claim. */
.fp-hist { margin-top: 4px; padding-top: 10px; border-top: 1px solid color-mix(in srgb, var(--app-good) 14%, var(--line)); }
.fp-hist .fp-lbl { font-size: 9px; margin-bottom: 7px; }
.fp-hist ul { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: 5px; }
.fp-hist li { display: flex; align-items: center; gap: 7px; font-size: 10.5px; color: var(--muted); }
.fp-hist li.gone { display: none; }
.fp-hist li.in { animation: fpIn 0.4s var(--ease); }
.fp-hist li i { width: 6px; height: 6px; border-radius: 50%; background: var(--line-2); flex: none; }
.fp-hist li b { font-weight: 700; color: var(--ink-2); flex: none; }
.fp-hist li span { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.fp-hist li em { font-style: normal; font-size: 9.5px; flex: none; }
.fp-hist li.live i { background: var(--app-good); box-shadow: 0 0 0 3px var(--app-good-soft); }
.fp-hist li.live b { color: var(--ink); }
.fp-restore { padding: 2px 7px; border-radius: var(--r-pill); border: 1px dashed var(--line-2); }

/* the redrawn picture arrives stroke by stroke. pathLength="1" is what makes
   that a pure CSS animation: every stroke dashes on the same 0-1 scale, so
   nothing has to be measured in JS. */
.fp-fig.redraw .w { stroke-dasharray: 1; stroke-dashoffset: 1; }
.fp-fig.redraw .d, .fp-fig.redraw .flab { opacity: 0; transition: opacity 0.35s var(--ease) 0.5s; }
.fp-figwrap.drawn .fp-fig.redraw .w { stroke-dashoffset: 0; transition: stroke-dashoffset 0.85s var(--ease); }
.fp-figwrap.drawn .fp-fig.redraw .w:nth-child(2) { transition-delay: 0.1s; }
.fp-figwrap.drawn .fp-fig.redraw .w:nth-child(3) { transition-delay: 0.24s; }
.fp-figwrap.drawn .fp-fig.redraw .w:nth-child(4) { transition-delay: 0.32s; }
.fp-figwrap.drawn .fp-fig.redraw .lever { transition-delay: 0.48s; }
.fp-figwrap.drawn .fp-fig.redraw .d, .fp-figwrap.drawn .fp-fig.redraw .flab { opacity: 1; }
/* on the paper itself the switch SWINGS shut rather than cutting to shut */
.fp-fig .lever.open { transform-origin: 56px 68px; transition: opacity 0.4s var(--ease), transform 0.55s var(--ease); }
.fp-figwrap.lit .fp-fig .lever.open { transform: rotate(38deg); }
.fp-figwrap.just { border-radius: 10px; animation: fpJust 1.7s var(--ease); }
@keyframes fpJust {
  0%, 100% { box-shadow: 0 0 0 0 transparent; }
  35% { box-shadow: 0 0 0 7px var(--app-good-soft); }
}

/* -- scene - ask lantern, v3 ----------------------------------------------
   The app under the drawer. It has one job: when a "take me there" is
   pressed the screen behind actually changes, so "she takes you to the right
   screen" is shown rather than said. */
.fp-ak { max-width: 820px; }
.fp-akapp {
  position: absolute; inset: 0;
  display: flex; flex-direction: column; overflow: hidden;
  background: var(--white); border: 1px solid var(--line-2);
  border-radius: 16px; box-shadow: var(--sh-1);
  transition: opacity 0.4s var(--ease), filter 0.4s var(--ease);
}
.fp-akapp.dim { opacity: 0.84; filter: saturate(0.9); }
.fp-akapp-top {
  display: flex; align-items: center; gap: 8px;
  padding: 10px 14px; background: var(--card); border-bottom: 1px solid var(--line);
}
.fp-akapp-mark { width: 15px; height: 15px; color: var(--app-warn-ink); flex: none; }
.fp-akapp-top b { font-size: 11.5px; color: var(--ink); }
.fp-akapp-kbd {
  margin-left: auto; font-family: var(--mono); font-size: 10px; font-weight: 700;
  color: var(--muted); background: #fdfcf8;
  border: 1px solid var(--line-2); border-radius: 7px; padding: 3px 8px;
  transition: color 0.2s var(--ease), box-shadow 0.2s var(--ease), transform 0.2s var(--ease);
}
.fp-akapp-kbd.press {
  color: var(--app-warn-ink); transform: translateY(1px);
  box-shadow: 0 0 0 4px var(--app-warn-soft);
}
.fp-akapp-body { position: relative; flex: 1; min-height: 0; }
/* the arriving screen flashes its own edge once - the app's route change */
.fp-akapp-body.jump::after {
  content: ""; position: absolute; inset: 0; pointer-events: none;
  box-shadow: inset 0 0 0 2px var(--app-accent); opacity: 0;
  animation: fpJump 1.1s var(--ease);
}
@keyframes fpJump { 22% { opacity: 0.7; } 100% { opacity: 0; } }
.fp-aks { position: absolute; inset: 0; display: none; flex-direction: column; gap: 9px; padding: 14px 16px; }
.fp-aks.on { display: flex; animation: fpIn 0.4s var(--ease); }
.fp-aks-day {
  margin: 0; font-family: var(--mono); font-size: 9.5px; font-weight: 700;
  letter-spacing: 0.13em; text-transform: uppercase; color: var(--muted);
}
.fp-aks h5 { margin: 0; font-family: var(--display); font-size: 16px; font-weight: 600; color: var(--ink); }
.fp-aks-sub { margin: -4px 0 0; font-size: 11px; font-weight: 500; color: var(--muted); }
.fp-aks-tiles { display: flex; gap: 8px; }
.fp-aks-tiles span {
  flex: 1; display: flex; flex-direction: column; gap: 1px;
  padding: 9px 11px; border-radius: 12px;
  background: var(--paper); border: 1px solid var(--line);
  font-size: 10px; font-weight: 600; color: var(--muted); line-height: 1.35;
}
.fp-aks-tiles b { font-family: var(--display); font-size: 19px; font-weight: 600; color: var(--ink); }
.fp-aks-rows { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: 5px; }
.fp-aks-rows li {
  display: flex; align-items: center; gap: 8px;
  padding: 8px 11px; border-radius: 11px;
  background: #fdfcf8; border: 1px solid var(--line);
  font-size: 11px; color: var(--muted);
}
.fp-aks-rows b { flex: none; font-size: 11.5px; font-weight: 650; color: var(--ink-2); }
.fp-aks-rows em { flex: none; font-style: normal; }
.fp-aks-rows span { margin-left: auto; font-size: 10px; white-space: nowrap; }
.fp-aks-rows i { width: 7px; height: 7px; border-radius: 50%; flex: none; background: var(--app-good); }
.fp-aks-rows i.mat { background: var(--app-blue); }
.fp-aks-rows i.warn { background: var(--app-warn); }
.fp-aks-rows i.bad { background: var(--app-bad); }
.fp-aks-rows i.bem { background: var(--app-emerging-ink); }
.fp-aks-rows i.bdev { background: var(--app-developing-ink); }
.fp-aks-rows i.bsec { background: var(--app-secure-ink); }
.fp-aks-rows li.pick { border-color: var(--app-accent); box-shadow: 0 0 0 3px var(--app-accent-soft); }
.fp-aks-bands { display: flex; gap: 3px; height: 10px; }
.fp-aks-bands i { border-radius: 3px; }
.fp-aks-bands .bem { background: var(--app-emerging-ink); }
.fp-aks-bands .bdev { background: var(--app-developing-ink); }
.fp-aks-bands .bsec { background: var(--app-secure-ink); }
.fp-aks-leg { margin: -3px 0 0; display: flex; gap: 11px; font-size: 10.5px; font-weight: 600; color: var(--muted); }
.fp-aks-leg b { color: var(--ink-2); margin-right: 4px; }
.fp-aks-steps { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: 6px; }
.fp-aks-steps li {
  display: flex; gap: 10px; align-items: baseline;
  padding: 8px 11px; border-radius: 11px;
  background: #fdfcf8; border: 1px solid var(--line);
  font-size: 11px; color: var(--muted);
}
.fp-aks-steps b {
  flex: none; width: 50px; font-family: var(--mono); font-size: 9px; font-weight: 700;
  letter-spacing: 0.1em; text-transform: uppercase; color: var(--app-good);
}
.fp-aks-note { margin: auto 0 0; display: flex; align-items: center; gap: 7px; font-size: 10.5px; color: var(--muted); }
.fp-aks-note svg { width: 12px; height: 12px; flex: none; color: var(--app-good); }
.fp-aks-note.warn { color: var(--app-warn-ink); }
.fp-aks-note.warn svg { color: var(--app-warn-ink); }

/* the drawer floats over that app rather than sitting beside a blank */
.fp-ak-panel { top: 10px; right: 10px; bottom: 10px; box-shadow: 0 26px 60px -24px rgba(26, 26, 24, 0.5); }
.fp-fab { right: 14px; bottom: 10px; }
/* an answer carries its sources - the classes and the marking it was read from */
.fp-akev { display: none; flex-wrap: wrap; align-items: center; gap: 5px; margin-top: 8px; }
.fp-ak-msg.bot .fp-akev.show { display: flex; animation: fpIn 0.3s var(--ease); }
.fp-akev em {
  font-style: normal; font-size: 9.5px; font-weight: 650; color: var(--muted);
  background: var(--white); border: 1px solid var(--line);
  border-radius: var(--r-pill); padding: 3px 8px; white-space: nowrap;
}
.fp-akev em.warn { color: var(--app-warn-ink); background: var(--app-warn-soft); border-color: transparent; }
.fp-akbar { display: flex; width: 100%; height: 7px; gap: 2px; margin-bottom: 2px; }
.fp-akbar i { border-radius: 3px; }
.fp-akbar .bem { background: var(--app-emerging-ink); }
.fp-akbar .bdev { background: var(--app-developing-ink); }
.fp-akbar .bsec { background: var(--app-secure-ink); }
.fp-ak-seeing b { transition: opacity 0.25s var(--ease); }
.fp-ak-seeing.turn b { opacity: 0.15; }
/* a thread grows from the bottom of its frame, the way every chat does: with
   two turns in it the panel should not read as three-quarters empty */
.fp-ak-roll { margin-top: auto; }

/* -- v3 - smaller screens ------------------------------------------------
   Below 700 the rail drops under the paper, so everything the rail carries
   is off the bottom of a clipped stage anyway; the third question card goes
   with it, since the review sheet lands where it used to sit. */
@media (max-width: 700px) {
  .fp-rf .fp-qc.rf.quiet, .fp-rf .fp-hist { display: none; }
  .fp-rf-title h4 { font-size: 14.5px; }
  .fp-keeps { margin-top: 8px; }
}
@media (max-width: 560px) {
  .fp-draft, .fp-keep:nth-child(n + 3), .fp-rv-foot .fp-note { display: none; }
  .fp-akapp-kbd { display: none; }
  .fp-aks-tiles { display: none; }
  .fp-aks h5 { font-size: 14.5px; }
}

/* â”€â”€ v2 Â· smaller screens â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€ */
@media (max-width: 940px) {
  .fp-mk-rv { grid-template-columns: 1fr 220px; }
  .fp-pw { grid-template-columns: 148px 1fr; }
}
@media (max-width: 700px) {
  .fp-ws .fp-railpane, .fp-ws .fp-railtabs { display: none; }
  .fp-mk-rv { grid-template-columns: 1fr; }
  .fp-mk-scan { display: none; }
  .fp-mk-cards { grid-template-columns: 1fr; }
  .fp-mk-card:nth-child(n + 3) { display: none; }
  .fp-rings { gap: 12px; }
  .fp-ring { width: 70px; height: 70px; }
  .fp-lr-bars { display: none; }
  .fp-pw { grid-template-columns: 1fr; }
  .fp-pw-queue { display: none; }
  .fp-paper-scroll { height: 300px; }
}
@media (max-width: 700px) {
  /* the stage clips, and on a phone every button is near an edge â€” so the
     pointer tucks fully inside rather than hanging off the corner */
  .fp .wz-ptr { right: 2px; bottom: -4px; }
  .fp-mk-foot { flex-wrap: wrap; }
  .fp-mk-foot .wz-btn { flex: 1 1 100%; justify-content: center; }
  /* the evidence hover has nowhere to go beside a phone-width column */
  .fp-evipop { display: none; }
}
@media (max-width: 560px) {
  .fp-mk-order .fp-note, .fp-mk-leg { display: none; }
  .fp-ring-cap { display: none; }
  .fp-objs .sb { display: none; }
  .fp-rp-head .fp-tag.quiet { display: none; }
  .fp-pw-tone b:nth-child(n + 4) { display: none; }
}

/* reduced motion: transitions off; site.js parks every scene on its payload */
@media (prefers-reduced-motion: reduce) {
  .fp *, .fp *::before, .fp *::after { transition: none !important; animation: none !important; }
}

/* â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â• tutor Â· exercise-book primitives â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•
   The tutor page's visual spine. Three primitives, used from here down by the
   revamped /tutor sections; nothing on the teach page reaches for these.

   .ruled      a surface that is a page from an exercise book: feint horizontal
               rules on the card ground, and a DOUBLE red margin rule at
               --rule-x (two 1px strokes 4px apart â€” the double line is what
               makes it read unmistakably as a margin and not a border).
               Feint lines run through the margin, as they do on real paper.
               Override --rule-x / --rule-gap / background-color per use.
   .hand       the child's voice: Caveat in graphite. Only ever for marks a
               child would make â€” a tick, a scrawled word, a "?". Never UI,
               never headlines, never the app speaking.
   .hand-tick  a drawn tick for the margin: an inline SVG path whose stroke
               draws itself in (dasharray/offset flipped by adding .in), using
               --write. Under reduced motion it ships drawn. */

body.tutor .ruled {
  --rule-x: 76px;
  background-color: var(--card);
  background-image:
    linear-gradient(
      to right,
      transparent 0,
      transparent calc(var(--rule-x) - 6px),
      var(--rule-red) calc(var(--rule-x) - 6px),
      var(--rule-red) calc(var(--rule-x) - 5px),
      transparent calc(var(--rule-x) - 5px),
      transparent calc(var(--rule-x) - 1px),
      var(--rule-red) calc(var(--rule-x) - 1px),
      var(--rule-red) var(--rule-x),
      transparent var(--rule-x)
    ),
    repeating-linear-gradient(
      to bottom,
      transparent 0,
      transparent calc(var(--rule-gap) - 1px),
      var(--rule-feint) calc(var(--rule-gap) - 1px),
      var(--rule-feint) var(--rule-gap)
    );
}

/* The lightbox page hardcodes #fff for teach's A4 worksheets; here it frames
   app screenshots, which are warm bone â€” keep the frame in the same paper. */
body.tutor .lb-page { background: var(--card); }

body.tutor .hand {
  font-family: var(--hand);
  font-weight: 500;
  color: var(--pencil);
  /* Caveat runs small on the x-height; sections size it up ~1.25Ã— against
     the Jakarta text it sits beside so the two voices feel the same size */
}

body.tutor .hand-tick {
  display: inline-block;
  width: 22px;
  height: 22px;
  color: var(--pencil);
}
body.tutor .hand-tick path {
  fill: none;
  stroke: currentColor;
  stroke-width: 2.4;
  stroke-linecap: round;
  stroke-linejoin: round;
  stroke-dasharray: 1;
  stroke-dashoffset: 1;
  transition: stroke-dashoffset 0.5s var(--write);
}
body.tutor .hand-tick.in path,
body.tutor .in .hand-tick path {
  stroke-dashoffset: 0;
}
@media (prefers-reduced-motion: reduce) {
  body.tutor .hand-tick path { stroke-dashoffset: 0; transition: none; }
}

/* â”€â”€ hero Â· the written exchange â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€
   Replaces the old .hf-card float: a page from tonight's exercise book, on
   which one exchange writes itself â€” the child asks in pencil, Lantern
   answers from the book (cited), asks a question back, the child gets it,
   and a tick lands in the margin. The whole product in six lines of paper.

   Choreography is pure CSS keyed off .in ([data-ink] in site.js). Beats,
   in seconds after .in â€” the first waits out the data-fade entrance:
     0.9  child's question writes itself      (clip-path wipe, 1.1s)
     2.2  Lantern's answer settles in
     3.0  the citation chip pops
     3.6  the teach-back question settles in
     4.3  the child's answer writes itself    (0.9s)
     5.4  the tick draws in the margin
     5.9  the "see how" link fades up
   Everything decelerates (--write); nothing bounces on a page. */
body.tutor .hero-page {
  --rule-x: 58px;
  position: relative;
  border: 1px solid var(--line);
  border-radius: var(--r-lg);
  box-shadow: var(--sh-2);
  padding: 34px 26px 18px 80px;
  rotate: -1.2deg;
  font-size: 15px;
  line-height: var(--rule-gap);   /* text rides the feint rules */
  color: var(--ink-2);
}
body.tutor .pg-when {
  position: absolute;
  top: 9px; right: 16px;
  font-family: var(--mono);
  font-size: 11px;
  letter-spacing: 0.04em;
  color: var(--muted);
}
body.tutor .hero-page .hand { font-size: 21.5px; line-height: var(--rule-gap); }
body.tutor .hero-page p { margin: 0; }
body.tutor .pg-kid { position: relative; }
body.tutor .pg-kid .hand-tick {
  position: absolute;
  left: -58px;                     /* into the margin, left of the red rule */
  top: 3px;
  color: var(--rule-red-deep);     /* the tick is the marker's, not pencil */
}
body.tutor .pg-cite {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  height: 22px;
  padding: 0 9px;
  border-radius: 999px;
  background: var(--accent-tint);
  color: var(--accent-deep);
  font-family: var(--mono);
  font-size: 11px;
  letter-spacing: 0.03em;
  vertical-align: -5px;            /* sits inside its 28px rule line */
}
body.tutor .pg-cite svg { width: 12px; height: 12px; }
body.tutor .pg-more {
  display: inline-block;
  margin-top: var(--rule-gap);
  font-family: var(--mono);
  font-size: 11.5px;
  letter-spacing: 0.05em;
  text-transform: uppercase;
  color: var(--muted);
  text-decoration: none;
}
body.tutor .pg-more:hover { color: var(--accent-deep); }
body.tutor .pg-more svg { width: 12px; height: 12px; vertical-align: -2px; }

/* beats â€” resting states */
body.tutor .hero-page .pg-ask > span,
body.tutor .hero-page .pg-kid > span {
  display: inline-block;
  /* negative top/bottom so Caveat's ascenders and tails aren't clipped */
  clip-path: inset(-12% 102% -16% -2%);
  transition: clip-path 1.1s var(--write);
}
body.tutor .hero-page .pg-ans p,
body.tutor .hero-page .pg-more {
  opacity: 0;
  translate: 0 8px;
  transition: opacity 0.6s var(--write), translate 0.6s var(--write);
}
body.tutor .hero-page .hand-tick path { transition-duration: 0.55s; }

/* beats â€” played once .in lands */
body.tutor .hero-page.in .pg-ask > span { clip-path: inset(-12% -2% -16% -2%); transition-delay: 0.9s; }
body.tutor .hero-page.in .pg-ans p:first-of-type { opacity: 1; translate: none; transition-delay: 2.2s; }
body.tutor .hero-page.in .pg-ans .pg-cite-line { opacity: 1; translate: none; transition-delay: 3s; }
body.tutor .hero-page.in .pg-ans .pg-q { opacity: 1; translate: none; transition-delay: 3.6s; }
body.tutor .hero-page.in .pg-kid > span { clip-path: inset(-12% -2% -16% -2%); transition-delay: 4.3s; transition-duration: 0.9s; }
body.tutor .hero-page.in .hand-tick path { transition-delay: 5.4s; }
body.tutor .hero-page.in .pg-more { opacity: 1; translate: none; transition-delay: 5.9s; }

@media (prefers-reduced-motion: reduce) {
  body.tutor .hero-page .pg-ask > span,
  body.tutor .hero-page .pg-kid > span { clip-path: none; transition: none; }
  body.tutor .hero-page .pg-ans p,
  body.tutor .hero-page .pg-more { opacity: 1; translate: none; transition: none; }
}
@media (max-width: 720px) {
  body.tutor .hero-page { rotate: none; padding-left: 72px; --rule-x: 52px; }
  body.tutor .pg-kid .hand-tick { left: -52px; }
}

/* â”€â”€ the promise Â· night band â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€
   The tutor page's first dark band. It borrows the teach Link band's night
   ground and star canvas wholesale â€” deliberately: the two bands depict the
   SAME evening (Tue 19:42 at home / Tue 7:42 pm on the hero page), so they
   must be recognisably the same sky. Everything ON the sky is this page's
   own: the dusk-band anatomy (statement â†’ promise + panel), and the child's
   questions drifting past dim at the bottom where teach has nothing.
   Palette rule inherited from the Link band and kept: GOLD is home/tutor.
   Ticks and soft affirmatives here are SAGE â€” green stays the school's. */
body.tutor .night-home { padding: 104px 0 0; }
/* no seam blend at the top: the hero's floor is a pale wash, and dayâ†’night
   should be a decision, not a dissolve â€” same hard cut the teach page makes
   into its own night band */
body.tutor .nh-state {
  font-size: clamp(38px, 5.6vw, 72px);
  line-height: 1.06;
  letter-spacing: -0.02em;
  color: #f6f1e5;
  text-align: center;
  max-width: 17ch;
  margin: 0 auto 92px;
}
body.tutor .nh-state em { font-style: italic; font-weight: 500; color: #e0a73c; }

body.tutor .nh-grid {
  display: grid;
  /* the panel column is deliberately narrower than the mission's â€” the board
     sits smaller and further right, hard against the wrap's right edge. */
  grid-template-columns: 1.05fr 0.776fr;
  gap: 64px;
  align-items: start;
}
body.tutor .nh-mission h3 {
  font-size: clamp(25px, 2.9vw, 33px);
  line-height: 1.18;
  color: #f2efe5;
  margin: 0 0 14px;
}
body.tutor .nh-mission h3 em { font-style: italic; font-weight: 500; color: #e0a73c; }
body.tutor .nh-lede {
  font-size: 16.5px;
  line-height: 1.6;
  color: rgba(233, 239, 234, 0.78);
  max-width: 50ch;
  margin: 0 0 30px;
}
body.tutor .nh-pledges {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 20px 26px;
}
body.tutor .nh-pledge { display: flex; gap: 12px; align-items: flex-start; }
body.tutor .nh-pledge svg {
  width: 16px; height: 16px; flex: none;
  margin-top: 3px;
  color: #a8cabe;                    /* sage tick â€” green stays the school's */
}
body.tutor .nh-pledge b { display: block; font-size: 15px; color: #f2efe5; margin-bottom: 3px; }
body.tutor .nh-pledge span { display: block; font-size: 13.5px; line-height: 1.5; color: rgba(233, 239, 234, 0.64); }

body.tutor .nh-panel {
  background: rgba(253, 249, 239, 0.07);
  border: 1px solid rgba(233, 239, 234, 0.15);
  border-radius: var(--r-lg);
  padding: 24px 28px 21px;
  /* Held to the right edge of its column, then pushed out into the page
     gutter. The wrap is a centred max-width column, so on wide screens
     there is dead space to its right that the panel would otherwise never
     reach â€” that gutter is (100vw - wrap)/2, and we spend part of it,
     keeping 90px of breathing room at the window edge so the board sits
     out to the right without crowding it. Clamped at 0 so narrow
     viewports (where there is no gutter) get no shift at all. */
  margin-left: auto;
  width: 100%;
  transform: translateX(max(0px, min((100vw - var(--wrap)) / 2 - 90px, 170px)));
}
body.tutor .nh-k {
  display: block;
  font-family: var(--mono);
  font-size: 12px;
  font-weight: 700;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: rgba(233, 239, 234, 0.66);
  border-bottom: 1px solid rgba(233, 239, 234, 0.16);
  padding-bottom: 13px;
  margin-bottom: 21px;
}
body.tutor .nh-stats {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 23px 24px;
}
body.tutor .nh-stat b {
  display: block;
  font-family: var(--display);
  font-weight: 600;
  font-size: clamp(33px, 2.93vw, 46px);
  letter-spacing: -0.03em;
  line-height: 1;
  color: #f6f1e5;
  font-variant-numeric: tabular-nums;
  margin-bottom: 7px;
}
body.tutor .nh-stat b em { font-style: normal; color: #e0a73c; }
body.tutor .nh-stat b small {
  font-family: var(--mono);
  font-size: 13px;
  font-weight: 600;
  color: #e0a73c;
  margin-left: 3.5px;
}
body.tutor .nh-stat > span {
  display: block;
  font-size: 13.8px;
  line-height: 1.45;
  color: rgba(233, 239, 234, 0.62);
  max-width: 22ch;
}
body.tutor .nh-cov {
  margin: 23px 0 0;
  border-top: 1px solid rgba(233, 239, 234, 0.16);
  padding-top: 14px;
  font-family: var(--mono);
  font-size: 12px;
  line-height: 1.6;
  color: rgba(233, 239, 234, 0.55);
}

/* the marquee, gone nocturnal: same track, voices heard from another room */
body.tutor .night-home .marquee {
  position: relative;
  z-index: 1;
  margin-top: 96px;
  padding: 17px 0;
  border-block: 0;
  border-top: 1px solid rgba(233, 239, 234, 0.1);
  background: transparent;
}
body.tutor .night-home .mq-half span { color: rgba(236, 230, 214, 0.42); font-weight: 500; }
body.tutor .night-home .mq-half span i { color: #e0a73c; background: rgba(224, 167, 60, 0.13); }
body.tutor .night-home .mq-half span::after { color: rgba(236, 230, 214, 0.22); }

@media (max-width: 1000px) {
  body.tutor .nh-grid { grid-template-columns: 1fr; gap: 48px; }
  /* stacked: the nudge would only push the panel off the page */
  body.tutor .nh-panel { transform: none; }
  body.tutor .nh-state { margin-bottom: 64px; }
  body.tutor .night-home .marquee { margin-top: 64px; }
}
@media (max-width: 640px) {
  body.tutor .nh-pledges { grid-template-columns: 1fr; }
  body.tutor .nh-stats { gap: 22px 20px; }
}

/* â”€â”€ the margin rule Â· walkthrough â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€
   The whole band is ONE page of the exercise book: white ground, feint rules
   full-bleed, the double margin rule down the left. The section-scoped
   --rule-red goes pale â€” the printed, un-inked rule â€” and .mr-fill lays a
   ribbon bookmark down it to wherever the reader has got to.
   Cousin logic vs the teach trail: their marker swoops (60px serpentine,
   confident, the teacher's whiteboard); our ribbon is ruled and obedient â€” it
   lies straight down a child's page. Nothing here overshoots. */
body.tutor .mrule {
  /* the section's .ruled background supplies ONLY the feint lines â€” its
     margin-rule stripes are blanked (transparent) because a background
     can't be trusted to line up with wrap-relative elements once .wrap's
     28px padding is in play. The printed rule is .mr-rail instead: an
     element in the same coordinate system as .mr-fill (the ribbon), so the
     two are aligned by construction. */
  --rule-red: transparent;
  position: relative;
  overflow: hidden;
  padding: 112px 0 140px;
  border-block: 1px solid var(--line);
}
body.tutor .mr-rail {
  /* the printed (un-inked) margin rule, running the full page height â€”
     up through the head and down past the last entry. The ribbon lies ON
     this page, so the printed rule stays visible below where the ribbon
     ends: that hairline is what the pending rings hang off. */
  position: absolute;
  left: 72px;
  top: -190px;
  bottom: -150px;
  width: 6px;
  background: linear-gradient(
    to right,
    #eac3b8 0 1px,
    transparent 1px 5px,
    #eac3b8 5px 6px
  );
  pointer-events: none;
}
body.tutor .mr-head { padding-left: 150px; max-width: 880px; margin-bottom: 56px; }
body.tutor .mr-head h2 {
  font-size: clamp(34px, 4.6vw, 54px);
  line-height: 56px;                  /* two rules exactly */
  letter-spacing: -0.02em;
  color: var(--ink);
  margin: 0 0 12px;
}
body.tutor .mr-head h2 em { font-style: italic; font-weight: 500; color: var(--accent); }
body.tutor .mr-lede {
  font-size: 16.5px;
  line-height: var(--rule-gap);
  color: var(--muted);
  max-width: 54ch;
  margin: 0;
}

body.tutor .mr-steps { position: relative; padding-left: 150px; }
/* â”€â”€ the ribbon â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€
   Supersedes the inked double-rule + pen nib. Same mechanism (one height
   set by the scrub in motion.js), different material: instead of the page
   being written ON, a ribbon bookmark is laid INTO it. The read is
   physical rather than clerical â€” you have got this far in the book â€” so
   it carries no timestamps and needs no nib: the ribbon's own cut tail IS
   the progress head, and where it stops is where the reader stopped.

   It is centred on the printed rule (72â€“78px) rather than replacing it:
   the hairline still shows above and below, so the ribbon reads as an
   object resting on the page instead of a stripe painted over it. */
body.tutor .mr-fill {
  position: absolute;
  left: 63px; top: 0;
  width: 24px; height: 0;
  background:
    /* the fold-shadow down the left edge â€” one ribbon, two planes of light */
    linear-gradient(to right,
      rgba(0,0,0,0.13) 0 2px,
      rgba(255,255,255,0.07) 2px 6px,
      transparent 6px),
    var(--ribbon, #9d3b28);
  pointer-events: none;
}
body.tutor .mr-tail {
  /* the cut end: a notched V, the way a grosgrain bookmark is cut so it
     will not fray. Drawn as the ribbon's own colour clipped to a
     downward chevron, sitting directly under the band. */
  position: absolute;
  left: 0; top: 100%;
  width: 24px; height: 18px;
  background: inherit;
  clip-path: polygon(0 0, 100% 0, 100% 100%, 50% 52%, 0 100%);
}

body.tutor .mr-step {
  position: relative;
  display: grid;
  grid-template-columns: minmax(280px, 5fr) 7fr;
  gap: 56px;
  align-items: start;
  padding: 42px 0 84px;
  opacity: 0.45;
  transition: opacity 0.5s var(--write);
}
body.tutor .mr-step.on { opacity: 1; }
/* the last entry keeps the rhythm above it but not the gap below: the ribbon
   is measured to the foot of .mr-steps, so 84px of trailing padding would put
   its cut tail in empty page below the final scene. The section's own bottom
   padding still separates the band from what follows. */
body.tutor .mr-step:last-child { padding-bottom: 24px; }
/* Grid blowout guard. A grid item's automatic minimum is its MIN-CONTENT
   width, and the scenes contain nowrap runs (the chip row, the composer
   placeholder) that measure ~460px â€” which dragged the whole column past
   the viewport at phone widths and let .mrule's overflow:hidden crop the
   copy. The items must be allowed to shrink; .ap already clips its own
   interior, so the chip row simply runs off the edge of the app frame the
   way it does in the app itself. */
body.tutor .mr-step > * { min-width: 0; }
body.tutor .mr-step h3 { font-size: clamp(23px, 2.4vw, 29px); line-height: 1.2; margin: 8px 0 12px; }
body.tutor .mr-step .mr-copy p { font-size: 15.5px; line-height: var(--rule-gap); color: var(--ink-2); margin: 0 0 16px; }
/* the outcome chip: .ws-out's look is scoped to the teach trail (.tstep),
   so restate it for this section */
body.tutor .mr-copy .ws-out {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  font-size: 13.5px;
  font-weight: 600;
  color: var(--green);
  background: var(--green-tint);
  border-radius: var(--r-pill);
  padding: 8px 15px;
}
body.tutor .mr-copy .ws-out svg { width: 15px; height: 15px; flex: none; }

/* the marks now sit ON the rule rather than out in the margin: a white
   tick struck into the ribbon once the step is reached, and until then a
   hollow ring threaded on the bare hairline. Both occupy the same 18px
   column the ribbon runs down, so a step's state is read at the rule
   itself â€” no second column of margin furniture. */
body.tutor .mr-mark {
  position: absolute;
  left: -87px;                        /* steps are padded 150px; ribbon is at 63px */
  top: 46px;
  width: 24px;
  height: 24px;
  display: grid;
  place-items: center;
}
body.tutor .mr-mark .hand-tick {
  width: 16px; height: 16px;
  color: #fff;                        /* struck into the ribbon, not beside it */
  position: relative; z-index: 1;
}
body.tutor .mr-mark .hand-tick path {
  stroke-width: 3.2;                  /* thicker: it is carrying white on red */
  transition-duration: 0.5s;
  transition-delay: 0.15s;
}
body.tutor .mr-step.on .mr-mark .hand-tick path { stroke-dashoffset: 0; }
body.tutor .mr-ring {
  /* the not-yet: an unfilled bead on the printed rule. Fades out as the
     ribbon arrives, so the tick is never drawn over a visible ring. */
  position: absolute;
  width: 9px; height: 9px;
  border: 1.5px solid #e5b3a6;
  border-radius: 50%;
  background: var(--paper, #faf6ef);
  transition: opacity 0.35s var(--write);
}
body.tutor .mr-step.on .mr-ring { opacity: 0; }

/* â”€â”€ scenes: things written INTO the page â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€ */
body.tutor .mr-scene {
  margin: 0;
  max-width: 540px;
  font-size: 15px;
  line-height: var(--rule-gap);
  color: var(--ink-2);
}
body.tutor .mr-scene .hand { font-size: 21px; line-height: var(--rule-gap); }
body.tutor .mr-scene p { margin: 0; }

/* reveal verbs: .wr = written (pencil, clip wipe) Â· .fr = settles (app) */
body.tutor .mr-scene .wr {
  clip-path: inset(-12% 102% -16% -2%);
  transition: clip-path 0.9s var(--write);
}
body.tutor .mr-step.on .wr { clip-path: inset(-12% -2% -16% -2%); }
body.tutor .mr-scene .fr {
  opacity: 0;
  translate: 0 8px;
  transition: opacity 0.55s var(--write), translate 0.55s var(--write);
}
body.tutor .mr-step.on .fr { opacity: 1; translate: none; }
/* beat clocks */
body.tutor .mr-step.on .b1 { transition-delay: 0.3s; }
body.tutor .mr-step.on .b2 { transition-delay: 1.1s; }
body.tutor .mr-step.on .b3 { transition-delay: 1.9s; }
body.tutor .mr-step.on .b4 { transition-delay: 2.6s; }

body.tutor .sc-chip {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  height: 22px;
  padding: 0 10px;
  border-radius: 999px;
  background: var(--cream);
  color: var(--ink-2);
  font-family: var(--mono);
  font-size: 11px;
  letter-spacing: 0.02em;
  white-space: nowrap;
}
body.tutor .sc-chip svg { width: 12px; height: 12px; }
body.tutor .sc-chip.lock { background: var(--accent-tint); color: var(--accent-deep); }
body.tutor .sc-chip.ok { background: var(--green-tint); color: var(--green-deep); }
body.tutor .sc-chip.warn { background: var(--accent-tint); color: var(--accent-deep); }

/* â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â• the scroll Â· walkthrough scenes v2 â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•
   Each of the four beats is now a working piece of the app, unrolled on a
   scroll of paper. Two rules govern the whole block:

   1 Â· INSIDE the scroll is the app, at the app's own register â€” warm bone
       panels, caramel accents, Fraunces for what Lantern says, Jakarta for
       chrome, Caveat ONLY for what the child wrote. Everything here is
       traced from the real screens (shots/tutor-*.png): the rail order, the
       chip row under the composer, "None found yet" on the locked Weak spot
       card, the Concept panel's EXPLORE tag.
   2 Â· OUTSIDE the app panel is paper, and paper takes pencil â€” the scroll's
       bottom margin carries the child's note in Caveat, the one voice the
       app itself never speaks in.

   Motion is driven from site.js ([data-tscene]), not from .on: the steps are
   long enough that a single scrub-triggered cascade would be over before the
   step is halfway up the screen. Each scroll runs its own loop while it is
   in view, and parks on its payload under reduced motion.

   The paper is a separate <i class="scr-paper"> layer so the deckle
   (clip-path) and its drop shadow never touch a glyph â€” a displaced or
   clipped text node is the one thing that gives this trick away. Its shadow
   is filter: drop-shadow, NOT box-shadow, so it follows the torn silhouette
   instead of squaring it off.                                            */

body.tutor .scr {
  position: relative;
  isolation: isolate;                 /* paper layer stays inside the scroll */
  width: 100%;
  max-width: 540px;
}
body.tutor .scr-body {
  position: relative;
  padding: 24px 22px 26px;
}
body.tutor .scr-paper {
  position: absolute;
  inset: 0;
  z-index: 0;
  background:
    /* foxing â€” six spots is the ceiling; more reads as dirty, not old */
    radial-gradient(7px 5px at 14% 24%, rgba(150, 108, 44, 0.13), transparent 70%),
    radial-gradient(5px 4px at 82% 17%, rgba(150, 108, 44, 0.11), transparent 70%),
    radial-gradient(9px 6px at 66% 78%, rgba(150, 108, 44, 0.10), transparent 72%),
    radial-gradient(4px 4px at 33% 62%, rgba(150, 108, 44, 0.12), transparent 70%),
    /* the sheet: lit from the upper left, browner at the lower right */
    radial-gradient(120% 90% at 20% 8%, rgba(255, 251, 235, 0.9), transparent 62%),
    radial-gradient(100% 80% at 95% 98%, rgba(146, 104, 44, 0.18), transparent 66%),
    linear-gradient(177deg, #f4e8ca, #eddfb7 46%, #e6d4a8);
  box-shadow: inset 0 0 44px rgba(112, 78, 32, 0.2);
  /* the deckle: 2â€“6px of irregularity down each long edge only. The rolled
     ends cover the short edges, so those stay straight. */
  clip-path: polygon(
    0 0, 100% 0,
    calc(100% - 3px) 7%, 100% 15%, calc(100% - 5px) 25%, 100% 34%,
    calc(100% - 2px) 44%, 100% 53%, calc(100% - 6px) 63%, 100% 72%,
    calc(100% - 3px) 82%, 100% 91%, 100% 100%,
    0 100%,
    3px 92%, 0 84%, 5px 74%, 0 65%, 2px 55%, 0 46%,
    6px 36%, 0 27%, 3px 17%, 0 8%
  );
  filter: drop-shadow(0 16px 22px rgba(20, 48, 44, 0.16));
}
/* grain rides its own layer above the tone so it never gets averaged out */
body.tutor .scr-paper::after {
  content: "";
  position: absolute;
  inset: 0;
  opacity: 0.32;
  mix-blend-mode: multiply;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='150' height='150'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='.9' numOctaves='3'/%3E%3C/filter%3E%3Crect width='150' height='150' filter='url(%23n)' opacity='.42'/%3E%3C/svg%3E");
}
body.tutor .scr-in { position: relative; z-index: 1; }

/* the rolled ends â€” a cylinder seen edge-on, wider than the sheet, with the
   curl of the roll showing at each end */
body.tutor .scr-roll {
  position: relative;
  z-index: 2;
  display: block;
  height: 19px;
  margin: 0 -13px;
  border-radius: 10px;
  background: linear-gradient(
    180deg,
    #d9c194 0 2px,
    #eddcb4 4px,
    #f7edd2 8px,
    #e4d0a2 14px,
    #cdb180 100%
  );
}
body.tutor .scr-roll b {
  position: absolute;
  left: 22px; right: 22px; top: 6px;
  height: 1px;
  background: rgba(255, 253, 240, 0.66);
}
body.tutor .scr-roll::before,
body.tutor .scr-roll::after {
  content: "";
  position: absolute;
  top: -3px;
  width: 25px; height: 25px;
  border-radius: 50%;
  background: radial-gradient(circle at 38% 32%, #faf1d9, #e6d2a4 52%, #c9ac7a);
  box-shadow: inset 0 0 0 1px rgba(140, 104, 50, 0.35);
}
body.tutor .scr-roll::before { left: -7px; }
body.tutor .scr-roll::after { right: -7px; }
body.tutor .scr-roll.t { margin-bottom: -3px; box-shadow: 0 7px 11px -5px rgba(112, 78, 32, 0.42); }
body.tutor .scr-roll.b { margin-top: -3px; box-shadow: 0 -7px 11px -5px rgba(112, 78, 32, 0.34); }

/* the child's note, written on the paper below the app */
body.tutor .scr-note {
  position: relative;
  z-index: 1;
  margin: 9px 0 0 8px;
  font-family: var(--hand);
  font-size: 19px;
  line-height: 1.1;
  color: var(--pencil);
  clip-path: inset(-14% 102% -20% -2%);
  transition: clip-path 0.85s var(--write);
}
body.tutor .scr-note.in { clip-path: inset(-14% -2% -20% -2%); }

/* â”€â”€ shared app chrome â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€ */
body.tutor .ap {
  display: flex;
  /* fixed, not min-: three of the four scenes swap panes of different
     natural heights, and a growing frame would walk the whole scroll down
     the page mid-beat */
  height: 374px;
  border-radius: 13px;
  background: #fbf8f1;
  border: 1px solid rgba(150, 116, 60, 0.22);
  box-shadow: 0 1px 0 rgba(255, 255, 255, 0.7) inset, 0 10px 22px rgba(78, 56, 22, 0.13);
  overflow: hidden;
  font-family: var(--font);
  font-size: 12.5px;
  line-height: 1.5;
  color: var(--ink-2);
}
body.tutor .ap-rail {
  flex: none;
  width: 35px;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 9px;
  padding: 8px 0 10px;
  background: rgba(244, 236, 220, 0.72);
  border-right: 1px solid rgba(150, 116, 60, 0.16);
}
body.tutor .ap-rail i {
  display: grid;
  place-items: center;
  width: 21px; height: 21px;
  border-radius: 7px;
  color: #9a8b74;
}
body.tutor .ap-rail i svg { width: 14px; height: 14px; }
body.tutor .ap-rail i.on { background: var(--accent-tint); color: var(--accent-deep); }
body.tutor .ap-rail i.lamp { color: var(--accent); margin-bottom: 3px; }
body.tutor .ap-rail i.lamp svg { width: 16px; height: 16px; }
body.tutor .ap-rail i.me {
  margin-top: auto;
  background: #b4713a;
  color: #fff;
  font-size: 9.5px;
  font-weight: 700;
  font-style: normal;
  border-radius: 50%;
}
body.tutor .ap-main {
  flex: 1;
  min-width: 0;
  display: flex;
  flex-direction: column;
  padding: 10px 12px 11px;
}

/* the app's segmented pills */
body.tutor .ap-seg { display: inline-flex; gap: 3px; }
body.tutor .ap-seg i {
  padding: 3px 9px;
  border-radius: 999px;
  font-size: 10.5px;
  font-weight: 600;
  font-style: normal;
  color: #8d8171;
  white-space: nowrap;
  transition: background 0.3s var(--write), color 0.3s var(--write);
}
body.tutor .ap-seg i.on { background: #fff; color: var(--accent-deep); box-shadow: 0 1px 3px rgba(80, 56, 20, 0.14); }
body.tutor .ap-seg.two i { flex: 1; text-align: center; }
body.tutor .ap-seg.two { background: rgba(214, 196, 164, 0.4); border-radius: 999px; padding: 2px; }
body.tutor .ap-seg.two i.on { background: #c08349; color: #fff; }

/* the typing caret, shared by every input in these scenes */
body.tutor .cp-cur {
  display: inline-block;
  width: 1.5px;
  height: 11px;
  margin-left: 1px;
  vertical-align: -2px;
  background: var(--accent);
  opacity: 0;
}
body.tutor .cp-cur.on { animation: tcBlink 1s steps(1, end) infinite; }
@keyframes tcBlink { 0%, 55% { opacity: 1; } 56%, 100% { opacity: 0; } }

/* avatars */
body.tutor .ap-av {
  flex: none;
  display: grid;
  place-items: center;
  width: 20px; height: 20px;
  border-radius: 50%;
  background: #b4713a;
  color: #fff;
  font-size: 9px;
  font-weight: 700;
  font-style: normal;
}
body.tutor .ap-av.bot { background: var(--accent-tint); color: var(--accent-deep); }
body.tutor .ap-av.bot svg { width: 12px; height: 12px; }

/* reveal verbs, scoped to the scroll so nothing leaks into the page */
body.tutor .scr .rise {
  opacity: 0;
  translate: 0 9px;
  transition: opacity 0.45s var(--write), translate 0.45s var(--write);
}
body.tutor .scr .rise.in { opacity: 1; translate: none; }

/* messages inside a rolling thread: a not-yet-shown turn must leave NO
   layout footprint, or rollEnd() reads its height into scrollHeight and
   scrolls the thread to accommodate turns nobody can see yet â€” the whole
   visible conversation snaps off the top of the frame. display:none (not
   just opacity) is what the app's own .fp-ak-msg{display:none} rule does
   for the same reason; .in swaps it back in with a fade. */
body.tutor .ak-turn.rise,
body.tutor .rd-ans.rise,
body.tutor .rd-u.rise { display: none; }
body.tutor .ak-turn.rise.in { display: flex; animation: tcMsgIn 0.4s var(--write); }
body.tutor .rd-ans.rise.in,
body.tutor .rd-u.rise.in { display: block; animation: tcMsgIn 0.4s var(--write); }
@keyframes tcMsgIn { from { opacity: 0; translate: 0 9px; } }

/* a pressed control â€” the same aim/press pair the teach scenes use */
body.tutor .scr .aim { box-shadow: 0 0 0 3px rgba(207, 133, 23, 0.22); }
body.tutor .scr .press { scale: 0.965; }
body.tutor .scr .aim,
body.tutor .scr .press { transition: box-shadow 0.25s var(--write), scale 0.18s var(--write); }

/* â”€â”€ 01 Â· ask â€” the Chats screen, in the app's night skin â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€
   Traced from the real screen: cream Fraunces on a near-black forest
   ground, the child's turn in the app's speech-gold, a read-aloud key
   beside the answer, and generated pictures arriving in their own labelled
   card with the alt text underneath. Dark is not a liberty â€” it is what the
   app looks like at 7:05pm, and on parchment the panel reads as a lit
   window rather than a pasted screenshot.                                */
body.tutor .ap-chat {
  --d-bg: #0e1c16;
  --d-panel: #1c3327;
  --d-ink: #f4eee1;
  --d-dim: #a3b3a7;
  --d-gold: #e9b44c;
  --d-line: rgba(244, 238, 225, 0.14);
  position: relative;
  background: var(--d-bg);
  border-color: rgba(8, 20, 15, 0.72);
  color: var(--d-ink);
}
body.tutor .ap-chat .ap-rail {
  background: rgba(255, 255, 255, 0.035);
  border-right-color: var(--d-line);
}
body.tutor .ap-chat .ap-rail i { color: #7f9186; }
body.tutor .ap-chat .ap-rail i.on { background: rgba(233, 180, 76, 0.16); color: var(--d-gold); }
body.tutor .ap-chat .ap-rail i.lamp { color: var(--d-gold); }

body.tutor .ak-brand {
  display: flex;
  align-items: center;
  gap: 7px;
  padding-bottom: 7px;
  font-family: var(--display);
  font-size: 18px;
  color: var(--d-ink);
}
body.tutor .ak-brand svg { width: 16px; height: 16px; color: var(--d-gold); }

body.tutor .ap-thread {
  flex: 1;
  position: relative;
  overflow: hidden;
  margin: 0 -4px;
  padding: 2px 4px 0;
}
body.tutor .ap-roll {
  display: flex;
  flex-direction: column;
  gap: 10px;
  transition: transform 0.75s var(--write);
}
body.tutor .ak-turn { display: flex; gap: 8px; align-items: flex-start; }
body.tutor .ak-turn.u { justify-content: flex-end; }
body.tutor .ak-turn.u p {
  max-width: 76%;
  margin: 0;
  padding: 6px 11px;
  border-radius: 11px;
  background: var(--d-gold);
  color: #23200f;
  font-size: 12px;
  font-weight: 500;
  line-height: 1.4;
}
body.tutor .ap-chat .ap-av { background: #f0e6d2; color: #7a5a2a; }
body.tutor .ap-chat .ap-av.bot { background: #f2e7d0; color: #8a5f22; }
body.tutor .ak-say { position: relative; min-width: 0; padding-right: 30px; }
body.tutor .ak-say p {
  margin: 0 0 6px;
  font-family: var(--display);
  font-weight: 500;
  font-size: 12.4px;
  line-height: 1.62;
  color: var(--d-ink);
}
body.tutor .ak-say p:last-of-type { margin-bottom: 0; }
/* curriculum vocabulary â€” the app underlines the words worth keeping */
body.tutor .ak-say u {
  text-decoration: none;
  font-weight: 700;
  color: var(--d-gold);
  border-bottom: 1.5px dotted rgba(233, 180, 76, 0.55);
}
body.tutor .ak-think {
  display: inline-flex;
  align-items: center;
  gap: 4px;
  padding: 4px 11px;
  border-radius: 999px;
  background: rgba(255, 255, 255, 0.06);
  font-family: var(--mono);
  font-size: 10px;
  color: var(--d-dim);
}
body.tutor .ak-think i {
  width: 4px; height: 4px;
  border-radius: 50%;
  background: currentColor;
  animation: tcDot 1.1s var(--write) infinite;
}
body.tutor .ak-think i:nth-child(2) { animation-delay: 0.16s; }
body.tutor .ak-think i:nth-child(3) { animation-delay: 0.32s; }
@keyframes tcDot { 0%, 100% { opacity: 0.28; } 45% { opacity: 1; } }

/* read-aloud â€” the app keeps it beside the answer, not in the toolbar */
body.tutor .ak-spk {
  position: absolute;
  right: 0; top: 2px;
  display: grid;
  place-items: center;
  width: 24px; height: 24px;
  padding: 0;
  border: 0;
  border-radius: 8px;
  background: var(--d-gold);
  color: #23200f;
  cursor: default;
  opacity: 0;
  scale: 0.8;
  transition: opacity 0.35s var(--write), scale 0.35s var(--write);
}
body.tutor .ak-spk svg { width: 13px; height: 13px; fill: currentColor; }
body.tutor .ak-spk svg path.w1,
body.tutor .ak-spk svg path.w2 { fill: none; stroke: currentColor; stroke-width: 1.9; stroke-linecap: round; }
body.tutor .ak-spk.in { opacity: 1; scale: 1; }
body.tutor .ak-spk.talk svg path.w1 { animation: tcWave 1.1s var(--write) infinite; }
body.tutor .ak-spk.talk svg path.w2 { animation: tcWave 1.1s var(--write) 0.18s infinite; }
@keyframes tcWave { 0%, 100% { opacity: 0.25; } 40% { opacity: 1; } }

/* the generated-picture card */
body.tutor .ak-gen {
  display: none;                      /* not just invisible â€” see tcMsgIn note above */
  margin: 9px 0 0;
  padding: 8px;
  border-radius: 11px;
  background: var(--d-panel);
  border: 1px solid rgba(233, 180, 76, 0.2);
}
body.tutor .ak-gen.in { display: block; animation: tcMsgIn 0.5s var(--write); }
body.tutor .ak-gen header { display: flex; align-items: center; gap: 7px; margin-bottom: 6px; }
body.tutor .ak-gtag {
  flex: none;
  padding: 1px 7px;
  border-radius: 999px;
  border: 1px solid rgba(233, 180, 76, 0.55);
  color: var(--d-gold);
  font-family: var(--mono);
  font-size: 7.5px;
  letter-spacing: 0.07em;
}
body.tutor .ak-gen header b {
  font-family: var(--display);
  font-size: 12px;
  color: var(--d-ink);
}
body.tutor .ak-genart {
  /* a fixed height, not an aspect ratio: at the panel's full width a 13:10
     box is 210px tall and the answer it illustrates scrolls off the top.
     The ground stays transparent so the card shows either side of the
     picture â€” in the app the image is a panel, not a full-bleed band. */
  display: grid;
  place-items: center;
  height: 132px;
}
body.tutor .ak-genart svg {
  display: block;
  width: auto;
  height: 100%;
  border-radius: 8px;
}
body.tutor .ak-genart .pg { fill: #fdfaf2; }
body.tutor .ak-genart .soil { fill: #c58a58; opacity: 0; transition: opacity 0.7s var(--write); }
body.tutor .ak-genart .crumbs ellipse { fill: #8c5a2c; opacity: 0; transition: opacity 0.6s var(--write) 0.35s; }
body.tutor .ak-genart.draw .soil { opacity: 1; }
body.tutor .ak-genart.draw .crumbs ellipse { opacity: 0.5; }
/* the growth sequence â€” soil, then each stage on its own delay, so the
   picture builds left to right in the ~2.8s the caption takes to type */
body.tutor .ak-genart .seed {
  fill: #ecd3a3;
  stroke: #96682c;
  stroke-width: 2;
  opacity: 0;
  transform-box: fill-box;
  transform-origin: center;
  scale: 0.5;
  transition: opacity 0.4s var(--write) 0.45s, scale 0.5s var(--write) 0.45s;
}
body.tutor .ak-genart.draw .seed { opacity: 1; scale: 1; }
body.tutor .ak-genart .stem,
body.tutor .ak-genart .root {
  fill: none;
  stroke-linecap: round;
  stroke-dasharray: 1;
  stroke-dashoffset: 1;
  /* a fully offset dash still paints its round cap as a dot, which floats in
     the sky where the flower stalks start â€” so the stroke only exists once
     its own delay is up. Both properties share the per-stage delay below. */
  stroke-opacity: 0;
  transition: stroke-dashoffset 0.75s var(--write), stroke-opacity 0.01s var(--write);
}
body.tutor .ak-genart .stem { stroke: #6f9a52; stroke-width: 3.6; }
body.tutor .ak-genart .root { stroke: #efe2c8; stroke-width: 2.6; }
body.tutor .ak-genart.draw .stem,
body.tutor .ak-genart.draw .root { stroke-dashoffset: 0; stroke-opacity: 1; }
body.tutor .ak-genart .leaf,
body.tutor .ak-genart .bloom > * {
  opacity: 0;
  transform-box: fill-box;
  transform-origin: center;
  scale: 0.55;
  transition: opacity 0.4s var(--write), scale 0.45s var(--write);
}
body.tutor .ak-genart.draw .leaf,
body.tutor .ak-genart.draw .bloom > * { opacity: 1; scale: 1; }
body.tutor .ak-genart .leaf { fill: #7fb05f; }
body.tutor .ak-genart .leaf.d { fill: #669346; }
body.tutor .ak-genart .bloom .pet { fill: #e98d6b; }
body.tutor .ak-genart .bloom .mid { fill: #f0c04c; }

body.tutor .ak-genart .s2 .stem,
body.tutor .ak-genart .s2 .root { transition-delay: 1s; }
body.tutor .ak-genart .s2 .leaf { transition-delay: 1.45s; }
body.tutor .ak-genart .s3 .stem,
body.tutor .ak-genart .s3 .root { transition-delay: 1.7s; }
body.tutor .ak-genart .s3 .leaf { transition-delay: 2.05s; }
body.tutor .ak-genart .bloom > * { transition-delay: 2.35s; }

/* the two arrows between the stages */
body.tutor .ak-genart .arw path {
  fill: none;
  stroke: #e2955c;
  stroke-width: 3;
  stroke-linecap: round;
}
body.tutor .ak-genart .arw-l {
  stroke-dasharray: 1;
  stroke-dashoffset: 1;
  stroke-opacity: 0;                  /* same cap-dot dodge as the stems */
  transition: stroke-dashoffset 0.45s var(--write), stroke-opacity 0.01s var(--write);
}
body.tutor .ak-genart .arw-h { opacity: 0; transition: opacity 0.25s var(--write); }
body.tutor .ak-genart.draw .arw-l { stroke-dashoffset: 0; stroke-opacity: 1; }
body.tutor .ak-genart.draw .arw-h { opacity: 1; }
body.tutor .ak-genart .a1 .arw-l { transition-delay: 0.85s; }
body.tutor .ak-genart .a1 .arw-h { transition-delay: 1.25s; }
body.tutor .ak-genart .a2 .arw-l { transition-delay: 1.55s; }
body.tutor .ak-genart .a2 .arw-h { transition-delay: 1.95s; }
/* the activity card â€” the same generated panel, carrying set work instead of
   a picture. Marking colours the reasoning: what was picked, what was missed. */
body.tutor .ak-gen header b { display: inline-flex; align-items: center; gap: 5px; }
body.tutor .ak-gen header b svg {
  width: 12px; height: 12px;
  flex: none;
  color: var(--d-gold);
}
/* .ak-say p already claims the display face at 12.4px, so these have to
   out-specify it or the card renders at answer size and overruns the thread */
body.tutor .ak-say .ak-actp {
  margin: 0 0 5px;
  font-family: var(--font);
  font-size: 10.2px;
  line-height: 1.45;
  color: var(--d-dim);
}
body.tutor .ak-actq {
  display: block;
  margin-bottom: 5px;
  font-family: var(--display);
  font-size: 11.4px;
  font-weight: 600;
  color: var(--d-ink);
}
body.tutor .ak-opts { display: grid; gap: 2px; }
body.tutor .ak-opts i {
  display: flex;
  align-items: center;
  gap: 7px;
  padding: 2px 7px;
  border-radius: 7px;
  border: 1px solid transparent;
  font-style: normal;
  font-size: 10.2px;
  line-height: 1.3;
  color: var(--d-ink);
  transition: background 0.3s var(--write), border-color 0.3s var(--write), color 0.3s var(--write);
}
body.tutor .ak-opts i em {
  position: relative;
  flex: none;
  width: 10px; height: 10px;
  border-radius: 3px;
  border: 1.4px solid rgba(233, 224, 200, 0.42);
  transition: background 0.25s var(--write), border-color 0.25s var(--write);
}
/* the tick is drawn, not a glyph, so it lands with the pick */
body.tutor .ak-opts i em::after {
  content: "";
  position: absolute;
  left: 2px; top: 0.5px;
  width: 3px; height: 6px;
  border: solid #23200f;
  border-width: 0 1.8px 1.8px 0;
  rotate: 42deg;
  opacity: 0;
  transition: opacity 0.2s var(--write);
}
body.tutor .ak-opts i.pick em { background: var(--d-gold); border-color: var(--d-gold); }
body.tutor .ak-opts i.pick em::after { opacity: 1; }
body.tutor .ak-opts i.no {
  background: rgba(199, 84, 62, 0.14);
  border-color: rgba(199, 84, 62, 0.4);
  color: #f0a08c;
}
body.tutor .ak-opts i.yes {
  background: rgba(122, 168, 106, 0.16);
  border-color: rgba(122, 168, 106, 0.45);
  color: #a6d093;
}
body.tutor .ak-opts i.yes em { border-color: rgba(166, 208, 147, 0.7); }
body.tutor .ak-opts i.yes::after,
body.tutor .ak-opts i.no::after {
  margin-left: auto;
  font-size: 9px;
  opacity: 0.9;
}
body.tutor .ak-opts i.yes::after { content: "missed this one"; }
body.tutor .ak-opts i.no::after { content: "your answer"; }
body.tutor .ak-check {
  display: block;
  width: 100%;
  margin-top: 6px;
  padding: 4px 0;
  border: 0;
  border-radius: 999px;
  background: var(--d-gold);
  color: #23200f;
  font-family: var(--font);
  font-size: 10.4px;
  font-weight: 700;
  cursor: default;
}
/* once it has been marked the button has done its job â€” and the room it gives
   back is what keeps the whole card inside the thread */
body.tutor .ak-check.done { display: none; }
body.tutor .ak-mark {
  display: none;
  margin-top: 7px;
  padding-top: 6px;
  border-top: 1px solid rgba(233, 224, 200, 0.14);
}
body.tutor .ak-mark.in { display: block; animation: tcMsgIn 0.45s var(--write); }
body.tutor .ak-mark b {
  display: block;
  margin-bottom: 3px;
  font-family: var(--display);
  font-size: 11.4px;
  color: var(--d-ink);
}
body.tutor .ak-say .ak-mark p {
  margin: 0;
  font-family: var(--font);
  font-size: 10.2px;
  line-height: 1.45;
  color: var(--d-dim);
}

/* "Want to go further?" â€” the answer's own follow-up row */
body.tutor .ak-more {
  display: none;
  flex-wrap: wrap;
  align-items: center;
  gap: 5px;
  margin-top: 9px;
}
body.tutor .ak-more.in { display: flex; animation: tcMsgIn 0.4s var(--write); }
body.tutor .ak-more em {
  font-family: var(--display);
  font-style: italic;
  font-size: 10.4px;
  color: var(--d-dim);
}
body.tutor .ak-more i {
  display: inline-flex;
  align-items: center;
  gap: 4px;
  padding: 3px 8px;
  border-radius: 999px;
  background: rgba(255, 255, 255, 0.05);
  border: 1px solid rgba(233, 224, 200, 0.14);
  font-style: normal;
  font-size: 9.6px;
  font-weight: 600;
  color: var(--d-ink);
}
body.tutor .ak-more i svg { width: 10px; height: 10px; }
body.tutor .ak-more i[data-ak-try] {
  border-color: rgba(233, 180, 76, 0.45);
  color: var(--d-gold);
}

body.tutor .ak-gen figcaption {
  margin-top: 6px;
  min-height: 24px;
  font-size: 9.5px;
  line-height: 1.4;
  color: var(--d-dim);
}

/* chips + composer, night skin */
body.tutor .ap-chips { display: flex; gap: 5px; margin: 9px 0 7px; min-width: 0; overflow: hidden; }
body.tutor .ap-chip {
  display: inline-flex;
  align-items: center;
  gap: 4px;
  padding: 4px 9px;
  border-radius: 999px;
  border: 1px solid rgba(150, 116, 60, 0.28);
  background: #fffdf7;
  color: #6f5f4a;
  font: inherit;
  font-size: 10px;
  font-weight: 600;
  white-space: nowrap;
  cursor: default;
}
body.tutor .ap-chip svg { width: 11px; height: 11px; color: var(--accent); }
body.tutor .ap-chat .ap-chip {
  background: rgba(255, 255, 255, 0.05);
  border-color: var(--d-line);
  color: var(--d-ink);
}
body.tutor .ap-chat .ap-chip svg { color: var(--d-gold); }
body.tutor .ap-comp {
  display: flex;
  align-items: center;
  gap: 7px;
  padding: 5px 5px 5px 9px;
  border-radius: 999px;
  background: #fffdf7;
  border: 1px solid rgba(150, 116, 60, 0.26);
}
body.tutor .ap-chat .ap-comp { background: rgba(255, 255, 255, 0.05); border-color: var(--d-line); }
body.tutor .ap-comp .cp-ic { display: grid; place-items: center; color: #a4937b; }
body.tutor .ap-chat .ap-comp .cp-ic { color: #8fa094; }
body.tutor .ap-comp .cp-ic svg { width: 13px; height: 13px; }
body.tutor .cp-txt {
  position: relative;
  flex: 1;
  min-width: 0;
  font-size: 11.5px;
  color: var(--ink-2);
  white-space: nowrap;
  overflow: hidden;
}
body.tutor .ap-chat .cp-txt { color: var(--d-ink); }
body.tutor .cp-txt b { font-weight: 400; }
body.tutor .cp-ph {
  position: absolute;
  left: 0; top: 0;
  font-style: normal;
  color: #8fa094;
  transition: opacity 0.25s var(--write);
}
body.tutor .cp-txt.typed .cp-ph { opacity: 0; }
body.tutor .cp-send {
  flex: none;
  display: grid;
  place-items: center;
  width: 23px; height: 23px;
  border: 0;
  border-radius: 50%;
  background: #c98a53;
  color: #fff;
  cursor: default;
}
body.tutor .ap-chat .cp-send { background: var(--d-gold); color: #23200f; }
body.tutor .cp-send svg { width: 12px; height: 12px; }
/* â”€â”€ 02 Â· read â€” the Library, the book, and the highlight â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€
   Three states of one real flow: the shelf â†’ the reader (TOC left, the
   spread right, page counter under it) â†’ select a sentence and the app's
   "Ask about this" pill appears over the highlight, opening the Ask Lantern
   side panel with the quoted passage, the question it was turned into, and
   an answer whose key terms carry the app's dotted underline.

   Panes swap on display, not opacity â€” the frame is a fixed height so
   nothing reflows, and a display swap can't leave a dead pane catching
   the eye at 1% opacity.                                                 */
body.tutor .ap-lib .ap-main { position: relative; padding: 0; }
body.tutor .li-pane { display: none; flex-direction: column; padding: 10px 12px; }
/* min-height:0 â€” the reader pane holds a page taller than the frame, and a
   flex item that keeps its auto minimum would push the pager out of the app
   instead of letting the sheet scroll inside its window */
body.tutor .li-pane.on { display: flex; flex: 1; min-height: 0; animation: tcPane 0.5s var(--write); }
@keyframes tcPane { from { opacity: 0; translate: 0 10px; } }

body.tutor .li-head { margin-bottom: 8px; }
body.tutor .li-head b {
  display: flex;
  align-items: center;
  gap: 6px;
  font-family: var(--display);
  font-size: 17px;
  color: var(--ink);
}
body.tutor .li-head b svg { width: 15px; height: 15px; color: var(--accent); }
body.tutor .li-head span { display: block; margin-top: 2px; font-size: 9.5px; line-height: 1.45; color: #8a7860; }
body.tutor .li-search {
  position: relative;
  display: flex;
  align-items: center;
  gap: 7px;
  padding: 6px 10px;
  border-radius: 999px;
  background: #f4ecdc;
  border: 1px solid rgba(150, 116, 60, 0.22);
  transition: box-shadow 0.3s var(--write);
}
body.tutor .li-search.aim { box-shadow: 0 0 0 3px rgba(207, 133, 23, 0.18); }
body.tutor .li-search svg { width: 13px; height: 13px; color: #a4937b; flex: none; }
body.tutor .li-q { font-size: 11px; color: var(--ink-2); }
body.tutor .li-ph {
  position: absolute;
  left: 30px;
  font-size: 11px;
  color: #ab9a82;
  transition: opacity 0.25s var(--write);
}
body.tutor .li-search.typed .li-ph { opacity: 0; }
body.tutor .ap-seg.tabs {
  margin: 9px 0 7px;
  padding-bottom: 6px;
  border-bottom: 1px solid rgba(150, 116, 60, 0.14);
}
body.tutor .li-sec { display: block; margin: 2px 0 5px; font-size: 11px; font-weight: 700; color: var(--ink); }

body.tutor .li-grid { display: flex; gap: 10px; }
body.tutor .li-bk, body.tutor .li-st { margin: 0; }
body.tutor .li-bk { width: 62px; }
body.tutor .li-cov {
  display: grid;
  place-items: center;
  aspect-ratio: 3 / 4;
  border-radius: 3px 6px 6px 3px;
  box-shadow: 0 4px 10px rgba(78, 56, 22, 0.22), inset 3px 0 0 rgba(0, 0, 0, 0.14);
  transition: translate 0.4s var(--write), box-shadow 0.4s var(--write);
}
body.tutor .li-bk.sci .li-cov { background: linear-gradient(150deg, #3f9155, #24693c); }
body.tutor .li-bk.eng .li-cov { background: linear-gradient(150deg, #4d9e5c, #2f7c45); }
body.tutor .li-cov svg {
  width: 66%;
  fill: none;
  stroke: rgba(255, 250, 235, 0.9);
  stroke-width: 2.2;
  stroke-linecap: round;
  stroke-linejoin: round;
}
body.tutor .li-cov svg .core { fill: rgba(255, 250, 235, 0.9); stroke: none; }
body.tutor .li-bk.lift .li-cov { translate: 0 -4px; box-shadow: 0 11px 18px rgba(78, 56, 22, 0.32); }
body.tutor .li-bk figcaption { margin-top: 5px; font-size: 10px; font-weight: 700; color: var(--ink); }
body.tutor .li-bk figcaption i { display: block; font-style: normal; font-weight: 500; font-size: 9px; color: #9a8870; }

body.tutor .li-strip { display: flex; gap: 7px; margin-top: 2px; }
body.tutor .li-st {
  position: relative;
  flex: 1;
  aspect-ratio: 4 / 5;
  border-radius: 6px;
  padding: 5px;
  display: flex;
  align-items: flex-end;
  box-shadow: 0 3px 8px rgba(78, 56, 22, 0.2);
}
body.tutor .li-st.s1 { background: linear-gradient(160deg, #f2c15e, #d98b32); }
body.tutor .li-st.s2 { background: linear-gradient(160deg, #9fc4d6, #5f8ea6); }
body.tutor .li-st.s3 { background: linear-gradient(160deg, #efb08a, #cf7448); }
body.tutor .li-st.s4 { background: linear-gradient(160deg, #b6c9a0, #7f9b68); }
body.tutor .li-tag {
  position: absolute;
  top: 4px; left: 4px;
  padding: 1px 5px;
  border-radius: 3px;
  background: rgba(38, 26, 12, 0.58);
  color: #fff;
  font-family: var(--mono);
  font-size: 6px;
  letter-spacing: 0.06em;
}
body.tutor .li-st i {
  font-style: normal;
  font-size: 8px;
  font-weight: 700;
  line-height: 1.15;
  color: #fffdf7;
  text-shadow: 0 1px 2px rgba(60, 38, 10, 0.55);
}

body.tutor .li-back {
  display: inline-flex;
  align-items: center;
  gap: 5px;
  font-style: normal;
  font-size: 10px;
  font-weight: 600;
  color: #8a7860;
}
body.tutor .li-back svg { width: 12px; height: 12px; }
body.tutor .li-back.top { margin-bottom: 7px; }

/* the reader */
body.tutor .rd-top {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 8px;
  padding-bottom: 7px;
  border-bottom: 1px solid rgba(150, 116, 60, 0.16);
}
body.tutor .rd-top > b { font-size: 11.5px; color: var(--ink); }
body.tutor .rd-x {
  display: grid;
  place-items: center;
  width: 17px; height: 17px;
  border-radius: 50%;
  border: 1px solid rgba(150, 116, 60, 0.3);
  color: #8a7860;
}
body.tutor .rd-x svg { width: 9px; height: 9px; }
/* the reader shows the book's own page, scanned â€” so the sheet scrolls inside
   a window the way it does in the app, and everything the child does to it
   (the highlighter, the pill) is an overlay placed in % of that scan. */
body.tutor .rd-view {
  position: relative;
  container-type: size;
  flex: 1;
  min-height: 0;
  margin-top: 8px;
  overflow: hidden;
  border-radius: 6px;
  background: #fff;
  border: 1px solid rgba(150, 116, 60, 0.16);
  box-shadow: inset 0 1px 6px rgba(78, 56, 22, 0.07);
}
body.tutor .rd-sheet {
  position: relative;
  width: 100%;
  /* on a narrow frame the page would otherwise shrink to a thumbnail with
     dead space under it. Sized off the window's own height (0.88 = the
     scan's w/h), the sheet always fills it and the reader just crops the
     right margin â€” the question, which is what gets highlighted, stays in. */
  width: max(100%, calc(100cqh * 0.88));
  transition: transform 1.05s var(--write);
}
body.tutor .rd-sheet img { display: block; width: 100%; height: auto; }

/* the highlighter â€” one bar per line of type, each swept left to right.
   Width is what animates, so the nib (::after, pinned to the right edge)
   rides along with the stroke instead of having to be keyframed after it.
   multiply, because a highlighter tints the paper, it doesn't cover it. */
body.tutor .rd-hl { position: absolute; inset: 0; pointer-events: none; }
body.tutor .rd-hl b {
  position: absolute;
  left: var(--l);
  top: var(--t);
  height: var(--h);
  width: 0;
  border-radius: 1.5px;
  transform: skewX(-1.7deg);
  mix-blend-mode: multiply;
  background:
    /* the streaks a felt nib leaves â€” px stops, so they stay put as the bar grows */
    repeating-linear-gradient(93deg, rgba(255, 255, 255, 0.22) 0 2px, rgba(255, 255, 255, 0) 2px 9px),
    linear-gradient(180deg, rgba(252, 220, 124, 0.5), rgba(240, 192, 74, 0.72) 56%, rgba(206, 152, 40, 0.6));
  /* slower than it looks like it needs to be: a stroke that lands in a
     quarter-second reads as a rectangle appearing, not as a pen moving */
  transition: width 0.62s cubic-bezier(0.34, 0.68, 0.22, 1);
  transition-delay: var(--d);
}
body.tutor .rd-hl.lit b { width: var(--w); }
/* where the pen lands and where it lifts, the ink pools a little */
body.tutor .rd-hl b::before {
  content: "";
  position: absolute;
  left: -1.5px; top: 0; bottom: 0;
  width: 7px;
  border-radius: 2px;
  background: linear-gradient(90deg, rgba(198, 143, 34, 0.34), rgba(198, 143, 34, 0));
}
body.tutor .rd-hl b::after {
  content: "";
  position: absolute;
  right: -3px; top: -16%; bottom: -16%;
  width: 8px;
  border-radius: 50%;
  background: radial-gradient(closest-side, rgba(206, 149, 34, 0.8), rgba(206, 149, 34, 0));
  opacity: 0;
}
body.tutor .rd-hl.lit b::after { animation: tcNib 0.62s var(--write) var(--d) both; }
@keyframes tcNib {
  0% { opacity: 0; }
  14% { opacity: 0.95; }
  76% { opacity: 0.9; }
  100% { opacity: 0; }
}

/* the app's own pill, next to the heading it belongs to */
body.tutor .rd-ask {
  position: absolute;
  left: 23.5%; top: 89.9%;
  padding: 3px 8px;
  border-radius: 999px;
  background: #b57a45;
  color: #fff8ec;
  font-size: 8.5px;
  font-weight: 700;
  white-space: nowrap;
  box-shadow: 0 4px 10px rgba(78, 56, 22, 0.3);
  opacity: 0;
}
body.tutor .rd-ask.in { animation: tcAsk 0.42s var(--write) both; }
@keyframes tcAsk {
  0% { opacity: 0; transform: translateY(4px) scale(0.76); }
  62% { opacity: 1; transform: translateY(0) scale(1.07); }
  100% { opacity: 1; transform: none; }
}

/* the pager the app puts under the sheet */
body.tutor .rd-pager {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 8px;
  padding-top: 6px;
  font-family: var(--mono);
  font-size: 9px;
  color: #8a7860;
}
body.tutor .rd-pager b { color: var(--ink); }
body.tutor .rd-pager i { display: grid; place-items: center; color: #b3a48c; }
body.tutor .rd-pager i svg { width: 11px; height: 11px; }

/* the Ask Lantern side panel */
body.tutor .rd-panel {
  position: absolute;
  top: 0; right: 0; bottom: 0;
  /* narrow enough that the highlighted line stays in view beside it â€” the
     answer is about that line, so covering it would break the sentence */
  width: 57%;
  display: flex;
  flex-direction: column;
  padding: 9px 10px 9px;
  background: #fbf6ec;
  border-left: 1px solid rgba(150, 116, 60, 0.28);
  box-shadow: -10px 0 26px rgba(78, 56, 22, 0.16);
  translate: 102% 0;
  transition: translate 0.6s var(--write);
}
body.tutor .rd-panel.in { translate: 0 0; }
body.tutor .rd-panel > header {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: 8px;
  padding-bottom: 7px;
  border-bottom: 1px solid rgba(150, 116, 60, 0.18);
}
body.tutor .rd-ph b { display: block; font-size: 12px; color: var(--ink); }
body.tutor .rd-ph em { font-style: normal; font-size: 8.5px; color: #9a8870; }
body.tutor .rd-full {
  flex: none;
  padding: 3px 9px;
  border-radius: 999px;
  background: #c08349;
  color: #fff8ec;
  font-style: normal;
  font-size: 9px;
  font-weight: 700;
}
body.tutor .rd-scroll { flex: 1; overflow: hidden; }
/* the gap under the header rides on the thread, not on the clipper: the
   scroll is measured as scrollHeight - clientHeight, and padding on the
   clipper would eat exactly that much off the last line */
body.tutor .rd-roll { padding-top: 8px; }
/* the thread inside the panel is what moves; .rd-scroll is the clipper, so
   translating IT would just slide the whole box out from under the header */
body.tutor .rd-roll { transition: transform 0.7s var(--write); }
body.tutor .rd-quote {
  position: relative;
  padding: 7px 9px 6px 16px;
  border-radius: 8px;
  background: #f4ead6;
  border: 1px solid rgba(150, 116, 60, 0.2);
}
body.tutor .rd-quote::before {
  content: "â€œ";
  position: absolute;
  left: 5px; top: 0;
  font-family: var(--display);
  font-size: 17px;
  color: #bfa77e;
}
body.tutor .rd-quote p {
  margin: 0;
  font-family: var(--display);
  font-style: italic;
  font-size: 10.4px;
  line-height: 1.45;
  color: #33291d;
}
body.tutor .rd-quote i { display: block; margin-top: 3px; font-style: normal; font-size: 8.5px; font-weight: 700; color: #9a8870; }
body.tutor .rd-asked {
  margin: 6px 0 7px;
  font-family: var(--mono);
  font-size: 8.5px;
  line-height: 1.45;
  color: #9a8870;
}
body.tutor .rd-ans p {
  margin: 0 0 5px;
  font-family: var(--display);
  font-weight: 500;
  font-size: 11.4px;
  line-height: 1.55;
  color: #33291d;
}
body.tutor .rd-ans u {
  text-decoration: none;
  font-weight: 700;
  color: #c07a30;
  border-bottom: 1.5px dotted rgba(192, 122, 48, 0.6);
}
body.tutor .rd-u {
  align-self: flex-end;
  width: fit-content;
  max-width: 82%;
  margin: 7px 0 7px auto;
  padding: 5px 10px;
  border-radius: 11px;
  background: #c08349;
  color: #fff8ec;
  font-size: 10.5px;
}
body.tutor .rd-comp {
  display: flex;
  align-items: center;
  gap: 7px;
  margin-top: 7px;
  padding: 4px 4px 4px 8px;
  border-radius: 999px;
  background: #fffdf7;
  border: 1.5px solid var(--accent);
}
body.tutor .rd-comp .cp-ic { display: grid; place-items: center; color: #a4937b; }
body.tutor .rd-comp .cp-ic svg { width: 12px; height: 12px; }
body.tutor .rd-comp .cp-txt { font-size: 10.5px; }
body.tutor .rd-comp .cp-send { width: 21px; height: 21px; }

/* â”€â”€ 03 Â· practise â€” set it up, answer, say how sure, see the result â”€â”€â”€â”€â”€
   The real loop, four panes: the setup panel (mode cards, subject, topic,
   how hard, what kind, how many) â†’ a generated question with its progress
   dots and Aâ€“D cards â†’ the confidence question the app asks BEFORE the
   score, which is the whole reason its weak spots are trustworthy â†’ the
   result, where "3 solid Â· 2 you were sure of but missed" is the sentence
   the rest of the product hangs off.                                     */
body.tutor .ap-prac .ap-main { position: relative; }
body.tutor .pr-head {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: 10px;
  margin-bottom: 9px;
}
body.tutor .pr-title b {
  font-family: var(--display);
  font-size: 15.5px;
  color: var(--accent-deep);
}
body.tutor .pr-title em {
  font-style: normal;
  font-family: var(--display);
  font-size: 15.5px;
  font-weight: 600;
  color: var(--ink);
}
body.tutor .pr-title i { display: block; font-style: normal; font-size: 9.5px; color: #9a8870; }
body.tutor .pr-hist {
  flex: none;
  display: inline-flex;
  align-items: center;
  gap: 5px;
  padding: 4px 11px;
  border-radius: 999px;
  background: #c08349;
  color: #fff8ec;
  font-size: 9.5px;
  font-weight: 700;
}
body.tutor .pr-hist svg { width: 11px; height: 11px; }

body.tutor .pr-pane { display: none; }
body.tutor .pr-pane.on { display: flex; flex-direction: column; flex: 1; animation: tcPane 0.5s var(--write); }

/* pane 0 Â· setup */
body.tutor .pr-modes { display: grid; grid-template-columns: repeat(3, 1fr); gap: 6px; }
body.tutor .pr-modes i {
  position: relative;
  padding: 6px 8px;
  border-radius: 9px;
  background: #f4ecdc;
  border: 1.5px solid transparent;
  font-style: normal;
}
body.tutor .pr-modes i b { display: block; font-size: 10px; color: var(--ink); }
body.tutor .pr-modes i span { font-size: 8.5px; color: #9a8870; }
body.tutor .pr-modes i.on { background: #fffdf7; border-color: var(--accent); }
body.tutor .pr-modes i.map { border-left: 3px solid #9dbfa8; }
body.tutor .pr-modes i svg { position: absolute; top: 7px; right: 7px; width: 10px; height: 10px; }
body.tutor .pr-modes i .tk { color: var(--accent); }
body.tutor .pr-modes i .lo { color: #b3a48c; }
body.tutor .pr-modes i.lk { opacity: 0.66; }

body.tutor .pr-panel {
  margin-top: 8px;
  padding: 7px 10px;
  border-radius: 10px;
  background: #fffdf7;
  border: 1px solid rgba(150, 116, 60, 0.18);
}
body.tutor .pr-row {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 5px 0;
  border-bottom: 1px solid rgba(150, 116, 60, 0.12);
}
body.tutor .pr-row.col { display: block; }
body.tutor .pr-row > b,
body.tutor .pr-kind b,
body.tutor .pr-many b {
  flex: none;
  width: 54px;
  font-family: var(--mono);
  font-size: 8px;
  letter-spacing: 0.05em;
  color: #9a8870;
}
body.tutor .pr-in {
  position: relative;
  flex: 1;
  padding: 4px 9px;
  border-radius: 7px;
  background: #f4ecdc;
  font-size: 10.5px;
  color: var(--ink-2);
  min-height: 21px;
}
body.tutor .pr-in b { font-weight: 500; }
body.tutor .pr-ph { position: absolute; left: 9px; top: 4px; font-style: normal; color: #ab9a82; transition: opacity 0.25s var(--write); }
body.tutor .pr-in.typed .pr-ph { opacity: 0; }
body.tutor .pr-row.col > b { display: block; margin-bottom: 5px; }
body.tutor .pr-hard { display: grid; grid-template-columns: repeat(3, 1fr); gap: 6px; }
body.tutor .pr-hard i {
  position: relative;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 1px;
  padding: 5px 4px 6px;
  border-radius: 8px;
  background: #f4ecdc;
  border: 1.5px solid transparent;
  font-style: normal;
  transition: border-color 0.35s var(--write), background 0.35s var(--write);
}
body.tutor .pr-hard i svg { width: 40px; height: 27px; }
body.tutor .pr-hard i svg .sh { fill: rgba(120, 92, 48, 0.16); }
body.tutor .pr-hard i:nth-child(1) svg .a { fill: #e9c94e; }
body.tutor .pr-hard i:nth-child(2) svg .a { fill: #cd8248; }
body.tutor .pr-hard i:nth-child(3) svg .d { fill: #4e463c; }
body.tutor .pr-hard i em { font-style: normal; font-size: 9.5px; font-weight: 700; color: #5f5140; }
body.tutor .pr-hard i span { font-size: 8px; color: #9a8870; text-align: center; }
body.tutor .pr-hard i.on { background: #fdf3e2; border-color: var(--accent); }
body.tutor .pr-hard i.on::after {
  content: "";
  position: absolute;
  top: 4px; right: 4px;
  width: 11px; height: 11px;
  border-radius: 50%;
  background: var(--accent) url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23fff' stroke-width='3.4' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M20 6 9 17l-5-5'/%3E%3C/svg%3E") center / 7px no-repeat;
}
body.tutor .pr-row.last { display: flex; align-items: center; gap: 12px; border-bottom: 0; padding-bottom: 0; margin-top: 6px; }
body.tutor .pr-kind { flex: 1; display: flex; align-items: center; gap: 8px; }
body.tutor .pr-kind .ap-seg.two { flex: 1; }
body.tutor .pr-many { display: flex; align-items: center; gap: 7px; }
body.tutor .pr-many em { font-style: normal; font-size: 11px; font-weight: 700; color: var(--accent-deep); }
body.tutor .pr-many em span { display: block; font-size: 8px; font-weight: 500; color: #9a8870; }
body.tutor .pr-go {
  align-self: center;
  margin-top: auto;
  padding: 7px 20px;
  border: 0;
  border-radius: 999px;
  background: #dcbb98;
  color: #fff8ec;
  font: inherit;
  font-size: 11.5px;
  font-weight: 700;
  display: inline-flex;
  align-items: center;
  gap: 6px;
  cursor: default;
  transition: background 0.35s var(--write), box-shadow 0.25s var(--write), scale 0.18s var(--write);
}
body.tutor .pr-go svg { width: 13px; height: 13px; }
body.tutor .pr-go.live { background: #c08349; }

/* panes 1â€“3 Â· the generated activity */
body.tutor .pr-card {
  flex: 1;
  display: flex;
  flex-direction: column;
  padding: 9px 10px;
  border-radius: 11px;
  background: #f7efe1;
  border: 1px solid rgba(150, 116, 60, 0.18);
}
body.tutor .pr-card header { display: flex; align-items: center; gap: 7px; }
body.tutor .pr-gen {
  flex: none;
  padding: 1px 7px;
  border-radius: 999px;
  border: 1px solid rgba(207, 133, 23, 0.45);
  color: var(--accent-deep);
  font-family: var(--mono);
  font-size: 7.5px;
  letter-spacing: 0.06em;
}
body.tutor .pr-card header b { font-size: 11px; color: var(--ink); }
body.tutor .pr-lede { margin: 6px 0 0; font-size: 10px; line-height: 1.45; color: #7d6a52; }
body.tutor .pr-qmeta { display: flex; justify-content: space-between; margin-top: 9px; font-size: 9px; color: #9a8870; }
body.tutor .pr-qmeta i { font-style: normal; }
body.tutor .pr-qmeta i b { color: var(--ink); }
body.tutor .pr-qmeta .r b { color: var(--accent-deep); }
body.tutor .pr-dots { display: flex; gap: 4px; margin: 5px 0 8px; }
body.tutor .pr-dots i {
  width: 15px; height: 4px;
  border-radius: 999px;
  background: #e2d5bd;
  transition: background 0.3s var(--write);
}
body.tutor .pr-dots i.on { background: var(--accent); }
body.tutor .pr-q { display: block; font-family: var(--display); font-size: 13px; color: var(--ink); }
body.tutor .pr-q.big { font-size: 12.5px; line-height: 1.3; }
body.tutor .pr-opts { display: grid; grid-template-columns: repeat(4, 1fr); gap: 6px; margin-top: 8px; }
body.tutor .pr-opts i {
  display: flex;
  align-items: flex-start;
  gap: 5px;
  padding: 6px 6px 7px;
  border-radius: 8px;
  background: #fffdf7;
  border: 1px solid rgba(150, 116, 60, 0.2);
  font-style: normal;
  font-size: 9.5px;
  line-height: 1.3;
  color: #5f5140;
  transition: background 0.3s var(--write), border-color 0.3s var(--write), box-shadow 0.3s var(--write);
}
body.tutor .pr-opts i em {
  flex: none;
  display: grid;
  place-items: center;
  width: 13px; height: 13px;
  border-radius: 50%;
  background: #ece0c8;
  font-style: normal;
  font-size: 7.5px;
  font-weight: 700;
  color: #8a7658;
}
body.tutor .pr-opts i.hov { box-shadow: 0 0 0 3px rgba(207, 133, 23, 0.14); }
body.tutor .pr-opts i.pick { background: #eedcbe; border-color: rgba(207, 133, 23, 0.5); color: var(--ink); }
body.tutor .pr-opts i.pick em { background: var(--accent); color: #fff; }
body.tutor .pr-nav {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 8px;
  margin-top: auto;
  padding-top: 9px;
}
body.tutor .pr-nav i {
  padding: 4px 11px;
  border-radius: 999px;
  border: 1px solid rgba(150, 116, 60, 0.22);
  font-style: normal;
  font-size: 9.5px;
  font-weight: 700;
  color: #8a7860;
  transition: box-shadow 0.25s var(--write), scale 0.18s var(--write);
}
body.tutor .pr-nav i.pv { opacity: 0.55; }
body.tutor .pr-nav i.nx { background: #fffdf7; color: var(--ink); }
body.tutor .pr-nav em { font-style: normal; font-size: 9px; color: #9a8870; }

/* pane 2 Â· confidence */
body.tutor .pr-conf { display: grid; grid-template-columns: repeat(3, 1fr); gap: 6px; margin-top: 10px; }
body.tutor .pr-conf i {
  padding: 7px 8px 8px;
  border-radius: 9px;
  background: #fffdf7;
  border: 1.5px solid rgba(150, 116, 60, 0.2);
  font-style: normal;
  transition: background 0.35s var(--write), border-color 0.35s var(--write);
}
body.tutor .pr-conf i em { font-style: normal; font-size: 13px; }
body.tutor .pr-conf i b { display: block; margin-top: 2px; font-size: 10px; color: var(--ink); }
body.tutor .pr-conf i span { font-size: 8.5px; color: #9a8870; }
body.tutor .pr-conf i.on { background: #f4e6cd; border-color: var(--accent); }

/* pane 3 Â· the result */
body.tutor .pr-score { display: flex; align-items: center; gap: 12px; margin-top: 9px; }
body.tutor .pr-ring { flex: none; width: 66px; height: 66px; }
body.tutor .pr-ring circle { fill: none; stroke-width: 7; }
body.tutor .pr-ring .bg { stroke: #e2d5bd; }
body.tutor .pr-ring .fg {
  stroke: #4e8f6b;
  stroke-linecap: round;
  stroke-dasharray: 0 138.2;
  transform: rotate(-90deg);
  transform-origin: 50% 50%;
  transition: stroke-dasharray 1s var(--write);
}
body.tutor .pr-ring.fill .fg { stroke-dasharray: 82.9 138.2; }
body.tutor .pr-ring .n { fill: var(--ink); font-family: var(--display); font-size: 17px; font-weight: 600; text-anchor: middle; }
body.tutor .pr-ring .of { fill: #9a8870; font-family: var(--display); font-size: 7.5px; text-anchor: middle; }
body.tutor .pr-nice { font-family: var(--mono); font-size: 8px; letter-spacing: 0.08em; color: #4e8f6b; }
body.tutor .pr-score > div > b { display: block; font-family: var(--display); font-size: 14px; color: var(--ink); }
body.tutor .pr-score p { margin: 2px 0 5px; font-size: 10px; }
body.tutor .pr-score em { font-style: normal; font-weight: 700; }
body.tutor .pr-score em.g { color: #3f7d5e; }
body.tutor .pr-score em.r { color: #b2553f; }
body.tutor .pr-went {
  display: inline-block;
  padding: 3px 9px;
  border-radius: 999px;
  background: #f0e7d4;
  font-size: 9px;
  color: #7d6a52;
}
body.tutor .pr-went b { color: var(--ink); }
body.tutor .pr-review {
  display: flex;
  align-items: center;
  gap: 8px;
  margin-top: 10px;
  padding: 7px 9px;
  border-radius: 9px;
  background: #f2e4c9;
  border: 1px solid rgba(178, 85, 63, 0.22);
}
body.tutor .pr-review > i {
  flex: none;
  display: grid;
  place-items: center;
  width: 17px; height: 17px;
  border-radius: 50%;
  background: #b2553f;
  color: #fff;
  font-style: normal;
  font-size: 9px;
  font-weight: 700;
}
body.tutor .pr-review b { display: block; font-size: 10px; color: var(--ink); }
body.tutor .pr-review span {
  display: block;
  font-size: 8.5px;
  color: #9a8870;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
body.tutor .pr-save {
  margin-top: 8px;
  padding: 7px 0;
  border: 0;
  border-radius: 999px;
  background: linear-gradient(#cf9155, #bd7c43);
  color: #fff8ec;
  font: inherit;
  font-size: 10.5px;
  font-weight: 700;
  cursor: default;
}
body.tutor .pr-note {
  margin: 9px 0 0;
  padding-left: 8px;
  border-left: 2px solid #9dbfa8;
  font-family: var(--display);
  font-weight: 500;
  font-size: 10.6px;
  line-height: 1.5;
  color: #33291d;
}
body.tutor .pr-note u {
  text-decoration: none;
  font-weight: 700;
  color: #c07a30;
  border-bottom: 1.5px dotted rgba(192, 122, 48, 0.6);
}

/* â”€â”€ 04 Â· progress â€” the fortnight, read twice â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€
   The one screen in this walkthrough a child doesn't open. It carries more
   than a 374px frame holds, so the grown-up pane is deliberately taller than
   the frame and the scene scrolls it once: the sentence gets the first
   screen, the numbers under it get the second. That distance is MEASURED at
   the moment it runs (rollEnd), never hardcoded â€” the revisit drawer opens
   above it, and any fixed offset would be wrong by exactly the drawer's
   height.

   The paragraph writes itself with a clip wipe, but its highlights come on
   AFTERWARDS: a highlighter is a second pass over a line you've already
   read, and lighting them during the wipe reads as the app shouting.      */
body.tutor .ap-gv .ap-main { padding: 9px 12px 10px; }
body.tutor .gv-top { display: flex; align-items: center; gap: 9px; }
body.tutor .gv-top b {
  display: inline-flex; align-items: center; gap: 5px;
  font-family: var(--display); font-size: 14.5px; font-weight: 600; color: var(--ink);
}
body.tutor .gv-top b svg { width: 13px; height: 13px; color: var(--accent); }

/* the frame the reading sits in */
body.tutor .gv-view { position: relative; flex: 1; min-height: 0; margin-top: 8px; overflow: hidden; }
body.tutor .gv-pane {
  position: absolute; inset: 0;
  opacity: 0; visibility: hidden; translate: 0 8px;
  transition: opacity 0.42s var(--write), translate 0.42s var(--write), visibility 0s linear 0.42s;
}
body.tutor .gv-pane.on {
  opacity: 1; visibility: visible; translate: none;
  transition: opacity 0.42s var(--write), translate 0.42s var(--write), visibility 0s;
}
body.tutor .gv-roll { transition: transform 0.95s var(--write); }

/* â”€â”€ the written summary â”€â”€ */
body.tutor .gv-card {
  padding: 9px 11px 10px;
  border: 1px solid rgba(207, 133, 23, 0.3);
  border-radius: 11px;
  background: #fffdf7;
  box-shadow: 0 1px 0 rgba(255, 255, 255, 0.7) inset;
}
body.tutor .gv-eb {
  display: flex; align-items: center; gap: 5px;
  font-size: 8.5px; font-weight: 800; letter-spacing: 0.12em; text-transform: uppercase;
  color: var(--accent-deep);
}
body.tutor .gv-eb svg { width: 11px; height: 11px; }
body.tutor .gv-hd {
  display: flex; align-items: baseline; gap: 6px; margin: 3px 0 6px;
  font-family: var(--display); font-size: 15px; font-weight: 600; color: var(--ink);
}
body.tutor .gv-hd i {
  margin-left: auto;
  font-family: var(--font); font-style: normal; font-size: 8.5px; font-weight: 500; color: #a2937b;
}
body.tutor .gv-para {
  font-size: 11px; line-height: 1.55; color: var(--ink-2);
  clip-path: inset(0 100% 0 0);
}
body.tutor .gv-para.in { clip-path: inset(0 -2% 0 0); transition: clip-path 1.05s var(--write); }
body.tutor .gv-para b {
  color: var(--ink);
  box-shadow: inset 0 0 0 rgba(207, 133, 23, 0);
  transition: box-shadow 0.45s var(--write);
}
body.tutor .gv-para b.lit { box-shadow: inset 0 -6px 0 rgba(207, 133, 23, 0.2); }

/* the one number the app is willing to put in a big face */
body.tutor .gv-band {
  display: flex; align-items: baseline; gap: 7px;
  margin-top: 8px; padding: 6px 10px 7px; border-radius: 9px;
  background: linear-gradient(90deg, rgba(207, 133, 23, 0.15), rgba(207, 133, 23, 0.04));
}
body.tutor .gv-band b {
  font-family: var(--display); font-size: 19px; font-weight: 600; line-height: 1;
  color: var(--accent-deep); font-variant-numeric: tabular-nums;
}
body.tutor .gv-band i { font-style: normal; font-size: 9px; color: #8a7a5f; }

/* The card WRITES itself: a block that hasn't arrived must leave no footprint,
   or the reader spends the first second looking at a hole where the sentence
   is going to be. Same display:none trick the chat turns use, for the same
   reason â€” and it costs nothing here, since the scroll below is measured
   after all of this has landed. */
body.tutor .gv-band.rise,
body.tutor .gv-gl.rise,
body.tutor .gv-ready.rise { display: none; }
body.tutor .gv-band.rise.in { display: flex; animation: tcMsgIn 0.4s var(--write); }
body.tutor .gv-gl.rise.in { display: block; animation: tcMsgIn 0.4s var(--write); }
body.tutor .gv-ready.rise.in { display: flex; animation: tcMsgIn 0.4s var(--write); }

/* the sentence, with both halves live */
body.tutor .gv-card .ap-lab { margin: 9px 0 3px; }
body.tutor .gv-glance { font-family: var(--display); font-size: 12.5px; color: var(--ink); }
body.tutor .gv-glance u {
  text-decoration: none; font-weight: 600;
  /* the padding is for the aim ring; the negative margin keeps it from
     opening a gap in front of the comma and the full stop */
  padding: 1px 3px; margin: 0 -2px; border-radius: 5px; border-bottom: 1.5px solid;
}
body.tutor .gv-glance u.ok { color: #2f6b4f; border-color: rgba(47, 107, 79, 0.45); }
body.tutor .gv-glance u.hot { color: #97600d; border-color: rgba(151, 96, 13, 0.5); }

/* the drawer behind it. grid-template-rows 0fr â†’ 1fr is the only way to
   transition to a height nobody has measured; the child div carries the
   overflow:hidden that makes it a clip rather than a squash. */
body.tutor .gv-drop { display: grid; grid-template-rows: 0fr; transition: grid-template-rows 0.5s var(--write); }
body.tutor .gv-drop.open { grid-template-rows: 1fr; }
body.tutor .gv-drop > div { overflow: hidden; display: grid; gap: 4px; }
body.tutor .gv-drop.open > div { padding-top: 6px; }
body.tutor .gv-mis {
  display: flex; align-items: center; gap: 7px;
  padding: 4px 8px;
  border: 1px solid rgba(150, 116, 60, 0.2); border-radius: 8px; background: #fffaf0;
}
body.tutor .gv-mis > i {
  flex: none; display: grid; place-items: center;
  width: 15px; height: 15px; border-radius: 50%;
  background: #fbeedb; color: #97600d;
  font-size: 9px; font-weight: 800; font-style: normal;
}
body.tutor .gv-mis > span { flex: 1; min-width: 0; }
/* the mistake is quoted in the child's own words, so it's set in the hand */
body.tutor .gv-mis b {
  display: block;
  font-family: var(--hand); font-size: 13.5px; font-weight: 400; color: var(--pencil);
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
body.tutor .gv-mis em { display: block; font-style: normal; font-size: 8px; color: var(--muted); }
body.tutor .gv-mis u {
  flex: none; text-decoration: none;
  padding: 2px 7px; border-radius: 999px;
  background: var(--accent-tint); color: var(--accent-deep);
  font-size: 8.5px; font-weight: 700;
}
body.tutor .gv-ready { display: flex; align-items: center; gap: 7px; margin-top: 8px; }
body.tutor .gv-ready em {
  flex: none; font-style: normal;
  font-size: 8px; font-weight: 800; letter-spacing: 0.12em; text-transform: uppercase; color: #a2937b;
}
body.tutor .gv-ready span {
  min-width: 0; padding: 3px 9px; border-radius: 999px;
  border: 1px solid rgba(150, 116, 60, 0.28); background: #fffdf7;
  font-size: 9px; font-weight: 600; color: var(--ink-2);
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}

/* â”€â”€ the numbers under the sentence â”€â”€ */
body.tutor .gv-two {
  display: grid; grid-template-columns: minmax(0, 0.86fr) minmax(0, 1fr);
  gap: 9px; margin-top: 9px;
}
body.tutor .gv-box {
  min-width: 0; padding: 8px 10px 9px;
  border: 1px solid rgba(150, 116, 60, 0.2); border-radius: 11px; background: #fffdf7;
}
body.tutor .gv-bt {
  display: flex; align-items: baseline; gap: 6px;
  font-family: var(--display); font-size: 11.5px; font-weight: 600; color: var(--ink);
}
body.tutor .gv-bt i {
  margin-left: auto;
  font-family: var(--font); font-style: normal; font-size: 8px; color: #a2937b; white-space: nowrap;
}
/* the dials. --fill is registered further down this sheet (@property), which
   is what lets a conic stop animate instead of jump. */
body.tutor .gv-rings { display: flex; gap: 6px; margin-top: 9px; }
body.tutor .gv-ring { flex: 1; min-width: 0; text-align: center; }
body.tutor .gv-ring > i {
  position: relative; display: grid; place-items: center; margin: 0 auto;
  width: 42px; height: 42px; border-radius: 50%;
  background: conic-gradient(var(--c) calc(var(--fill) * 1%), rgba(150, 116, 60, 0.15) 0);
  transition: --fill 1.1s var(--write);
}
body.tutor .gv-ring.fill > i { --fill: var(--p); }
body.tutor .gv-ring > i::before {
  content: ""; position: absolute; width: 33px; height: 33px; border-radius: 50%; background: #fffdf7;
}
body.tutor .gv-ring > i svg { position: relative; width: 14px; height: 14px; color: var(--c); }
body.tutor .gv-ring b {
  display: block; margin-top: 4px;
  font-family: var(--display); font-size: 11.5px; font-weight: 600; color: var(--ink);
}
body.tutor .gv-ring u { display: block; text-decoration: none; font-size: 8px; color: var(--muted); }

/* the trend. Three lines drawn with pathLength=1, so one dash length covers
   any polyline and all three finish together however long they are. */
body.tutor .gv-spark { display: block; width: 100%; height: auto; margin-top: 8px; overflow: visible; }
body.tutor .gv-spark .g { fill: none; stroke: rgba(150, 116, 60, 0.26); stroke-width: 1; stroke-dasharray: 2 3; }
body.tutor .gv-spark .ln {
  fill: none; stroke-width: 2; stroke-linecap: round; stroke-linejoin: round;
  stroke-dasharray: 1; stroke-dashoffset: 1;
}
body.tutor .gv-spark .ln.a, body.tutor .gv-spark .dot.a { stroke: #2f6b4f; }
body.tutor .gv-spark .ln.b, body.tutor .gv-spark .dot.b { stroke: #cf8517; }
body.tutor .gv-spark .ln.c, body.tutor .gv-spark .dot.c { stroke: #3b7bd0; }
body.tutor .gv-spark .dot { fill: #fffdf7; stroke-width: 2; opacity: 0; }
body.tutor .gv-spark.draw .ln { stroke-dashoffset: 0; transition: stroke-dashoffset 1.5s var(--write); }
body.tutor .gv-spark.draw .ln.b { transition-delay: 0.12s; }
body.tutor .gv-spark.draw .ln.c { transition-delay: 0.24s; }
body.tutor .gv-spark.draw .dot { opacity: 1; transition: opacity 0.3s var(--write) 1.35s; }
body.tutor .gv-ax { display: flex; justify-content: space-between; margin-top: 3px; font-size: 8px; color: #a2937b; }
body.tutor .gv-ax i { font-style: normal; }
body.tutor .gv-leg { display: flex; flex-wrap: wrap; gap: 4px 9px; margin-top: 6px; font-size: 8.5px; color: var(--muted); }
body.tutor .gv-leg span { display: inline-flex; align-items: center; gap: 4px; }
body.tutor .gv-leg i { width: 8px; height: 2.5px; border-radius: 2px; background: var(--c); }

/* On a phone the app frame keeps its 374px height but loses roughly 180px of
   width, and two boxes side by side stop being two boxes â€” the rings collide
   with their own labels. One column, and the second box goes: the trend line
   is the panel that needs width to say anything. */
@media (max-width: 620px) {
  body.tutor .gv-top b { font-size: 12.5px; }
  body.tutor .gv-hd { font-size: 13.5px; }
  body.tutor .gv-two { grid-template-columns: minmax(0, 1fr); }
  body.tutor .gv-two > .gv-box:last-child { display: none; }
}

/* â”€â”€ the fold-out map â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€
   The map screenshot framed as the fold-out page in the back of the book â€”
   an index tab to grab it by, two fold creases (one valley, one mountain,
   shaded in opposite directions), and a settle-open entrance on --write.
   Replaces the browser-chrome framing: nothing on this page is a window. */
body.tutor .foldout { position: relative; margin: 0; perspective: 1100px; }
body.tutor .fo-tab {
  position: absolute;
  top: -13px; left: 30px; z-index: 2;
  font-family: var(--mono);
  font-size: 10.5px;
  font-weight: 600;
  letter-spacing: 0.05em;
  text-transform: uppercase;
  color: var(--muted);
  background: var(--cream);
  border: 1px solid var(--line-2);
  border-bottom: 0;
  padding: 4px 12px 9px;
  border-radius: 8px 8px 0 0;
}
body.tutor .fo-page {
  position: relative;
  border-radius: 14px;
  overflow: hidden;
  border: 1px solid var(--line);
  box-shadow: var(--sh-3);
  transform-origin: 50% 0;
  transform: rotateX(-8deg);
  transition: transform 0.9s var(--write);
}
body.tutor .foldout.in .fo-page { transform: none; }
body.tutor .fo-page img { display: block; width: 100%; }
/* The creases are gone with the screenshot. A printed page folds; the live
   map inside doesn't, and a paper crease drawn across a running UI reads as
   a rendering fault. The index tab still frames it as the page in the back. */
body.tutor .fo-note {
  margin-top: 12px;
  text-align: right;
  font-size: 19px;
  color: var(--pencil);
}
html.no-motion body.tutor .fo-page { transform: none; transition: none; }

/* â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â• the map, running Â· desert skin â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•
   The section used to be a tall headline block over a full screenshot of the
   map page. Two problems: it was the tallest band on the page for the least
   motion, and a static picture of a graph can't show the one thing that
   makes the map worth having â€” that tapping a dot tells you what an idea
   builds on and what it opens up.

   So this is the map itself, coded, cropped to the part that matters (the
   canvas and the idea panel; the app's own title and search bar are cut â€”
   the section heading above already says what this is).

   THE SKIN. The app ships a reef palette: mint canvas, coral rings, coral
   formations growing in from the bottom corners. Dropped onto this page's
   warm cream that coral reads as a second, cooler product. Same composition,
   walked around the wheel into a desert: dune canvas, clay rings, saguaro
   and prickly pear in the corners. Sand and cactus, not coral and reef.

   THE WORLD IS WIDER THAN ITS VIEWPORT. .mp-net is a fixed 1020px world
   inside an ~840px canvas, so a pan genuinely uncovers map (the right-hand
   locked lane starts off-screen). Panning a world that already fits would
   just slide pixels around and read as a glitch. */
body.tutor .mp {
  --mp-clay: #c2794c;         /* the ring/accent â€” was the app's coral */
  --mp-clay-2: #d99a6a;
  --mp-clay-tint: #f6e7d4;
  --mp-cactus: #5f7f5e;       /* unlocks, mastered â€” was the reef teal */
  --mp-cactus-2: #8aa382;
  --mp-panel: #fbf6ea;
  --mp-line: #e5dabf;
  --mp-ink: #14302c;
  --mp-mute: #766e59;
  position: relative;
  display: flex;
  flex-direction: column;
  /* fixed, like the walkthrough frames: the panel swaps content of two
     different lengths and a growing frame would walk the page mid-beat */
  height: 428px;
  overflow: hidden;
  background: #f7f2e3;
  font-family: var(--font);
  color: var(--mp-ink);
  text-align: left;
}

/* â”€â”€ toolbar â”€â”€ */
/* padding-top clears the fold-out index tab, which hangs down over the top
   of the page it labels */
body.tutor .mp-top {
  flex: none;
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 19px 12px 8px;
}
body.tutor .mp-chips { display: flex; gap: 6px; min-width: 0; overflow: hidden; }
body.tutor .mp-chips i {
  display: inline-flex;
  align-items: center;
  gap: 5px;
  padding: 4px 10px;
  border-radius: 999px;
  background: #fdfaf1;
  border: 1px solid var(--mp-line);
  font-size: 10px;
  font-weight: 650;
  font-style: normal;
  color: var(--mp-ink);
  white-space: nowrap;
}
body.tutor .mp-chips i.on { background: var(--mp-clay); border-color: var(--mp-clay); color: #fdf8ef; }
body.tutor .mp-chips .dot { width: 6px; height: 6px; border-radius: 50%; }
body.tutor .mp-chips .dot.eng { background: var(--mp-clay-2); }
body.tutor .mp-chips .dot.sci { background: var(--mp-cactus); }
body.tutor .mp-reset {
  position: absolute;
  top: 8px; right: 9px;
  z-index: 4;
  padding: 4px 9px;
  border-radius: 8px;
  background: #fdfaf1;
  border: 1px solid var(--mp-line);
  font-size: 9.5px;
  font-weight: 650;
  color: var(--mp-mute);
  white-space: nowrap;
}

/* â”€â”€ the canvas â”€â”€ */
body.tutor .mp-body { flex: 1; min-height: 0; display: flex; gap: 10px; padding: 0 11px 11px; }
body.tutor .mp-canvas {
  position: relative;
  flex: 1;
  min-width: 0;
  overflow: hidden;
  border-radius: 12px;
  border: 1px solid var(--mp-line);
  background:
    radial-gradient(120% 80% at 50% 8%, rgba(255, 252, 240, 0.9), transparent 62%),
    linear-gradient(178deg, #f4efdd 0%, #ece2c6 100%);
}
body.tutor .mp-dune {
  position: absolute; left: 0; right: 0; bottom: 0;
  width: 100%; height: 74px;
  fill: rgba(206, 186, 141, 0.34);
}
body.tutor .mp-cact { position: absolute; bottom: -14px; width: 92px; height: 130px; }
body.tutor .mp-cact g { fill: var(--mp-cactus); opacity: 0.2; }
body.tutor .mp-cact.l { left: -16px; }
body.tutor .mp-cact.r { right: -12px; transform: scaleX(-1); }

/* --mp-z zooms the world down for narrow canvases; JS writes the pan into
   the same transform and reads the zoom back out of the rendered width, so
   the two never fight over the property */
body.tutor .mp-net {
  position: absolute;
  left: 0; top: 50%;
  width: 1020px; height: 340px;
  margin-top: -178px;
  transform-origin: 0 50%;
  transition: transform 1.1s var(--write);
}
body.tutor .mp-edges { position: absolute; inset: 0; width: 100%; height: 100%; overflow: visible; }
/* clear of the Reset view pill, which floats at the canvas top-right */
body.tutor .mp-lane {
  position: absolute;
  left: var(--x); top: 30px;
  transform: translateX(-50%);
  font-family: var(--mono);
  font-size: 9px;
  letter-spacing: 0.13em;
  text-transform: uppercase;
  color: rgba(107, 98, 74, 0.8);
  white-space: nowrap;
}

/* edges: opacity is the reveal, dash-march is the "this one is live" cue */
body.tutor .mp-e {
  opacity: 0;
  stroke-linecap: round;
  transition: opacity 0.6s var(--write), stroke-width 0.4s var(--write);
}
/* .opt = dashed, "helps but optional"; .must = solid, "must know it first" â€”
   the line style carries the requirement, not the direction of travel */
body.tutor .mp-e.faint { stroke: rgba(150, 130, 92, 0.5); stroke-width: 1.1; stroke-dasharray: 3 5; }
body.tutor .mp-e.opt { stroke: var(--mp-clay-2); stroke-width: 1.9; stroke-dasharray: 7 6; }
body.tutor .mp-e.must { stroke: var(--mp-cactus-2); stroke-width: 1.9; }
body.tutor .mp-e.in { opacity: 0.62; }
body.tutor .mp-e.faint.in { opacity: 0.5; }
body.tutor .mp-e.lit { opacity: 1; stroke-width: 2.7; }
body.tutor .mp-e.opt.lit { stroke: var(--mp-clay); animation: mpFlow 1.05s linear infinite; }
body.tutor .mp-e.must.lit { stroke: var(--mp-cactus); }
@keyframes mpFlow { to { stroke-dashoffset: -13; } }

/* â”€â”€ nodes â”€â”€ */
body.tutor .mp-n {
  position: absolute;
  left: var(--x); top: var(--y);
  transform: translate(-50%, -50%);
  display: grid;
  place-items: center;
  width: 44px; height: 44px;
  border-radius: 50%;
  border: 2px solid var(--mp-clay);
  background: #dfe7dc;
  font-style: normal;
  opacity: 0;
  scale: 0.78;
  box-shadow: 0 1px 3px rgba(92, 68, 30, 0.16);
  transition: opacity 0.5s var(--write), scale 0.5s var(--write),
              box-shadow 0.45s var(--write), border-color 0.4s var(--write);
}
body.tutor .mp-n.in { opacity: 1; scale: 1; }
body.tutor .mp-n b { font-size: 15px; line-height: 1; }
body.tutor .mp-n.sci { background: #dde8de; }
body.tutor .mp-n.eng { background: #f4e0cc; }
body.tutor .mp-n.big { width: 58px; height: 58px; border-width: 2.5px; }
body.tutor .mp-n.big b { font-size: 19px; }
/* locked: dashed, faded, unnamed â€” the rest of the grade, out of reach */
body.tutor .mp-n.lock {
  border-style: dashed;
  border-color: rgba(194, 121, 76, 0.45);
  background: #eae4d1;
  box-shadow: none;
}
body.tutor .mp-n.lock.in { opacity: 0.55; }
body.tutor .mp-n.lock b { font-size: 13px; filter: grayscale(0.35); }
body.tutor .mp-n u {
  position: absolute;
  top: calc(100% + 6px); left: 50%;
  transform: translateX(-50%);
  padding: 3px 7px;
  border-radius: 7px;
  background: #fdfaf2;
  box-shadow: 0 1px 3px rgba(92, 68, 30, 0.13);
  font-size: 9.5px;
  font-weight: 700;
  line-height: 1.2;
  text-align: center;
  text-decoration: none;
  color: var(--mp-ink);
  white-space: nowrap;
}
body.tutor .mp-n s {
  position: absolute;
  top: -3px; left: -7px;
  display: grid;
  place-items: center;
  width: 17px; height: 17px;
  border-radius: 50%;
  background: var(--mp-clay);
  color: #fdf8ef;
  font-size: 9px;
  font-weight: 700;
  text-decoration: none;
  opacity: 0;
  scale: 0.6;
  transition: opacity 0.35s var(--write), scale 0.35s var(--write);
}
body.tutor .mp-n.hov { scale: 1.07; box-shadow: 0 3px 9px rgba(92, 68, 30, 0.2); }
body.tutor .mp-n.sel {
  border-color: var(--mp-clay);
  box-shadow: 0 0 0 4px rgba(194, 121, 76, 0.15), 0 0 0 11px rgba(194, 121, 76, 0.07),
              0 4px 12px rgba(92, 68, 30, 0.18);
}
body.tutor .mp-n.sel s { opacity: 1; scale: 1; }
body.tutor .mp-n.sel u { background: #fff; box-shadow: 0 2px 7px rgba(92, 68, 30, 0.18); }
/* the cross-link: hovering a panel row pings its dot on the map */
body.tutor .mp-n.ping { animation: mpPing 0.9s var(--write) 2; }
@keyframes mpPing {
  0%, 100% { box-shadow: 0 0 0 0 rgba(194, 121, 76, 0.55), 0 1px 3px rgba(92, 68, 30, 0.16); scale: 1; }
  42% { box-shadow: 0 0 0 11px rgba(194, 121, 76, 0), 0 4px 11px rgba(92, 68, 30, 0.2); scale: 1.09; }
}

/* â”€â”€ legend â”€â”€ */
body.tutor .mp-legend {
  position: absolute;
  left: 0; right: 0; bottom: 0;
  display: flex;
  align-items: center;
  gap: 11px;
  padding: 8px 11px 7px;
  background: linear-gradient(180deg, rgba(238, 227, 199, 0), rgba(238, 227, 199, 0.82) 46%);
  font-size: 8.6px;
  color: var(--mp-mute);
  white-space: nowrap;
  overflow: hidden;
}
body.tutor .mp-legend b {
  font-family: var(--mono);
  font-size: 8px;
  letter-spacing: 0.11em;
  text-transform: uppercase;
  opacity: 0.85;
}
body.tutor .mp-legend i { display: inline-flex; align-items: center; gap: 4px; font-style: normal; }
body.tutor .mp-legend .sw { width: 8px; height: 8px; border-radius: 50%; border: 1.7px solid; }
body.tutor .mp-legend .sw.done { border-color: var(--mp-cactus); }
body.tutor .mp-legend .sw.ready { border-color: var(--mp-clay); }
body.tutor .mp-legend .sw.lock { border-color: #c9bfa4; }
/* drawn, not bordered: a 15px dashed border renders as two fat squares */
body.tutor .mp-legend .ln { width: 17px; height: 2px; border-radius: 1px; }
body.tutor .mp-legend .ln.solid { background: var(--mp-cactus); }
body.tutor .mp-legend .ln.dash {
  background: repeating-linear-gradient(90deg, var(--mp-clay) 0 4px, transparent 4px 7px);
}

/* â”€â”€ the idea panel â”€â”€ */
body.tutor .mp-panel {
  position: relative;
  flex: 0 0 clamp(244px, 28%, 320px);
  min-width: 0;
  display: flex;
  flex-direction: column;
  border-radius: 12px;
  border: 1px solid var(--mp-line);
  background: var(--mp-panel);
  overflow: hidden;
}
/* the mask does the work a scrollbar would: content that has rolled past
   the top edge fades out instead of being guillotined mid-word */
body.tutor .mp-scroll {
  flex: 1;
  min-height: 0;
  overflow: hidden;
  padding: 12px 12px 0;
  mask-image: linear-gradient(180deg, transparent 0, #000 11px, #000 calc(100% - 15px), transparent 100%);
}
body.tutor .mp-roll { transition: transform 0.8s var(--write); }
body.tutor .mp .rise {
  opacity: 0;
  translate: 0 7px;
  transition: opacity 0.42s var(--write), translate 0.42s var(--write);
}
body.tutor .mp .rise.in { opacity: 1; translate: none; }

body.tutor .mp-meta {
  display: flex; align-items: center; gap: 5px;
  font-family: var(--mono);
  font-size: 8.4px;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: rgba(107, 98, 74, 0.95);
}
body.tutor .mp-meta s { width: 6px; height: 6px; border-radius: 50%; background: var(--mp-clay); }
body.tutor .mp-title {
  display: flex; align-items: baseline; gap: 6px;
  margin: 5px 0 6px;
  font-family: var(--display);
  font-size: 15.5px;
  font-weight: 600;
  letter-spacing: -0.012em;
  line-height: 1.2;
  color: var(--mp-ink);
}
body.tutor .mp-title b { flex: none; font-size: 13px; }
body.tutor .mp-desc { font-size: 10.5px; line-height: 1.5; color: #5f6a60; margin-bottom: 9px; }
body.tutor .mp-goal {
  padding: 8px 9px;
  border-radius: 8px;
  background: var(--mp-clay-tint);
  font-size: 10px;
  line-height: 1.5;
  color: #4f5a50;
}
body.tutor .mp-goal b { color: var(--mp-ink); }
body.tutor .mp-lvl {
  display: flex; align-items: baseline; justify-content: space-between; gap: 8px;
  margin: 11px 0 6px;
  padding-top: 10px;
  border-top: 1px solid var(--mp-line);
}
body.tutor .mp-lvl b { font-size: 11px; font-weight: 700; color: var(--mp-clay); }
body.tutor .mp-lvl span { font-size: 8.4px; color: var(--mp-mute); text-align: right; }
body.tutor .mp-track { position: relative; height: 6px; border-radius: 999px; background: #ece2ca; }
body.tutor .mp-track i {
  display: block;
  width: 0; height: 100%;
  border-radius: 999px;
  background: linear-gradient(90deg, var(--mp-clay-2), var(--mp-clay));
  transition: width 1.15s var(--write);
}
body.tutor .mp-track s {
  position: absolute; top: -2px; left: 0;
  width: 2px; height: 10px;
  border-radius: 1px;
  background: #3f4b40;
  transition: left 0.9s var(--write);
}
body.tutor .mp-scale {
  display: flex; justify-content: space-between; gap: 6px;
  margin-top: 4px;
  font-family: var(--mono);
  font-size: 7.4px;
  color: rgba(107, 98, 74, 0.85);
}
body.tutor .mp-sh {
  display: flex; align-items: center; justify-content: space-between;
  margin: 12px 0 6px;
  padding-top: 10px;
  border-top: 1px solid var(--mp-line);
  font-family: var(--mono);
  font-size: 8.2px;
  letter-spacing: 0.11em;
  text-transform: uppercase;
  color: rgba(107, 98, 74, 0.95);
}
body.tutor .mp-sh em { font-style: normal; opacity: 0.7; }
body.tutor .mp-rows { display: flex; flex-direction: column; gap: 5px; }
body.tutor .mp-row {
  display: flex; align-items: center; gap: 7px;
  padding: 6px 8px;
  border-radius: 8px;
  background: #fdfaf1;
  border: 1px solid var(--mp-line);
  font-size: 10px;
  font-style: normal;
  line-height: 1.3;
  transition: border-color 0.3s var(--write), box-shadow 0.3s var(--write);
}
body.tutor .mp-row b { flex: none; font-size: 11px; }
body.tutor .mp-row span { flex: 1; min-width: 0; }
body.tutor .mp-row u {
  flex: none;
  padding: 2px 5px;
  border-radius: 5px;
  background: #f0e8d4;
  font-family: var(--mono);
  font-size: 7.4px;
  text-decoration: none;
  color: var(--mp-mute);
}
body.tutor .mp-row u.need { background: var(--mp-clay-tint); color: #a05c2a; }
body.tutor .mp-row.hot { border-color: var(--mp-clay); box-shadow: 0 0 0 3px rgba(194, 121, 76, 0.13); }
body.tutor .mp-row.off { display: none; }

body.tutor .mp-foot {
  flex: none;
  padding: 8px 12px 10px;
  border-top: 1px solid var(--mp-line);
  background: var(--mp-panel);
}
body.tutor .mp-hand { font-size: 9px; color: var(--mp-mute); margin-bottom: 7px; }
body.tutor .mp-hand b { color: var(--mp-clay); font-weight: 700; }
body.tutor .mp-cta { display: grid; grid-template-columns: 1.2fr 1fr; gap: 6px; }
body.tutor .mp-cta > span {
  display: grid; place-items: center;
  height: 27px;
  border-radius: 8px;
  font-size: 10.5px;
  font-weight: 700;
  white-space: nowrap;
}
body.tutor .mp-go { background: var(--mp-clay); color: #fdf8ef; }
body.tutor .mp-prac { background: #fdfaf1; border: 1px solid var(--mp-line); color: var(--mp-ink); }
/* the hand-off: the button doesn't just click, it lights */
body.tutor .mp-go.go { animation: mpGo 1s var(--write); }
@keyframes mpGo {
  0% { box-shadow: 0 0 0 0 rgba(194, 121, 76, 0.55); }
  100% { box-shadow: 0 0 0 16px rgba(194, 121, 76, 0); }
}

body.tutor .mp-empty {
  position: absolute;
  inset: 0;
  z-index: 3;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 9px;
  padding: 0 24px;
  background: var(--mp-panel);
  text-align: center;
  font-size: 10.5px;
  line-height: 1.5;
  color: var(--mp-mute);
  transition: opacity 0.5s var(--write);
}
body.tutor .mp-empty b { font-size: 21px; }
body.tutor .mp-empty.off { opacity: 0; }

/* â”€â”€ the hand-off: Let's learn! opens Lantern, already briefed â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€ */
body.tutor .mp-over {
  position: absolute;
  inset: 0;
  z-index: 5;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 0 26px;
  background: rgba(247, 242, 227, 0.74);
  backdrop-filter: blur(3px);
  opacity: 0;
  pointer-events: none;
  transition: opacity 0.45s var(--write);
}
body.tutor .mp-over.in { opacity: 1; }
body.tutor .mo-card {
  width: min(520px, 100%);
  padding: 14px 16px 13px;
  border-radius: 14px;
  background: var(--mp-panel);
  border: 1px solid var(--mp-line);
  box-shadow: 0 18px 44px rgba(78, 56, 22, 0.22);
  translate: 0 15px;
  transition: translate 0.5s var(--write);
}
body.tutor .mp-over.in .mo-card { translate: none; }
body.tutor .mo-top {
  font-family: var(--mono);
  font-size: 8.6px;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--mp-mute);
  margin-bottom: 11px;
}
body.tutor .mo-top b { font-size: 11px; }
body.tutor .mo-top em { font-style: normal; color: var(--mp-clay); font-weight: 700; }
body.tutor .mo-msg { display: flex; gap: 9px; align-items: flex-start; }
body.tutor .mo-av {
  flex: none;
  display: grid;
  place-items: center;
  width: 24px; height: 24px;
  border-radius: 50%;
  background: var(--mp-clay-tint);
  font-size: 12px;
  font-style: normal;
}
body.tutor .mo-bub {
  flex: 1;
  min-width: 0;
  font-family: var(--display);
  font-size: 13px;
  line-height: 1.55;
  color: var(--mp-ink);
}
body.tutor .mo-chips { display: flex; flex-wrap: wrap; gap: 6px; margin: 12px 0 0 33px; }
body.tutor .mo-chips i {
  display: inline-flex;
  align-items: center;
  padding: 5px 10px;
  border-radius: 999px;
  background: #fdfaf1;
  border: 1px solid var(--mp-line);
  font-size: 10px;
  font-weight: 650;
  font-style: normal;
  color: var(--mp-ink);
  white-space: nowrap;
}

/* â”€â”€ the pointer â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€
   A hand does not glide at one constant speed between two points: it throws
   the mouse most of the way fast, then makes a short correction. to() drives
   both phases and sets the duration from the distance, so a hop across the
   panel and a nudge to the next dot no longer take the same 740ms. The
   wrapper is an <i>, not the <svg>, because ::after (the click ripple) does
   not render on SVG elements. */
body.tutor .mp-cur {
  position: absolute;
  left: 0; top: 0;
  z-index: 6;
  width: 13px; height: 18px;
  opacity: 0;
  pointer-events: none;
  transition: transform 0.6s var(--write), opacity 0.3s var(--write);
}
body.tutor .mp-cur svg {
  display: block;
  width: 100%; height: 100%;
  filter: drop-shadow(0 1px 2px rgba(40, 30, 10, 0.4));
  transition: scale 0.16s var(--write);
}
body.tutor .mp-cur path { fill: #33422f; stroke: #fdf8ef; stroke-width: 1.1; paint-order: stroke; }
body.tutor .mp-cur.on { opacity: 1; }
body.tutor .mp-cur.tap svg { scale: 0.76; }
body.tutor .mp-cur::after {
  content: "";
  position: absolute;
  left: 0; top: 0;
  width: 9px; height: 9px;
  margin: -4.5px 0 0 -4.5px;
  border-radius: 50%;
  border: 2px solid var(--mp-clay);
  opacity: 0;
}
body.tutor .mp-cur.tap::after { animation: mpRipple 0.55s var(--write); }
@keyframes mpRipple {
  0% { opacity: 0.85; scale: 0.4; }
  100% { opacity: 0; scale: 4.2; }
}
body.tutor .mp .aim { box-shadow: 0 0 0 3px rgba(194, 121, 76, 0.24); }
body.tutor .mp .press { scale: 0.95; }

/* the section itself, tightened: the map earns its space by moving, not by
   being big â€” head padding and the shot's bottom rule both come in */
body.tutor .stars-head { padding: 68px 0 22px; }
body.tutor .stars-stats { margin-top: 18px; gap: 34px; }
body.tutor .stars-shot { padding: 16px 0 66px; }

/* stacked: the panel drops under the canvas, and needs real height â€” the
   roll is ~440px, and a 130px window turns reading an idea into a slot
   machine. The world zooms down to match the shorter canvas. */
@media (max-width: 900px) {
  body.tutor .mp { height: 548px; }
  body.tutor .mp-body { flex-direction: column; gap: 9px; }
  body.tutor .mp-canvas { flex: 1; min-height: 190px; }
  body.tutor .mp-panel { flex: none; height: 248px; }
  body.tutor .mp-net { --mp-z: 0.76; margin-top: -172px; }
}
@media (max-width: 620px) {
  body.tutor .mp { height: 530px; }
  body.tutor .mp-panel { height: 240px; }
  body.tutor .mp-net { --mp-z: 0.64; }
  body.tutor .mp-legend i:nth-child(n + 4) { display: none; }
  /* the floating "Oli â€” tap any dot" chip lands on the fold-out index tab
     once the frame is this narrow */
  body.tutor .stars-shot .chip.keep { display: none; }
  body.tutor .stars-head { padding-top: 58px; }
  body.tutor .stars-shot { padding-bottom: 58px; }
}
html.no-motion body.tutor .mp-net,
html.no-motion body.tutor .mp-roll,
html.no-motion body.tutor .mp-cur { transition: none; }
html.no-motion body.tutor .mp-e.opt.lit { animation: none; }
html.no-motion body.tutor .mo-card { translate: none; transition: none; }

/* â”€â”€ the earth band Â· the Lantern Link section's own ground â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€
   v4: the band is night, the same night the teach site's Link band runs on.
   It stood on warm earth for three versions, on the argument that the evening
   desk IS this page and a second dark blue band would just be the hero again.
   That held while the two grounds were only cousins; it stopped holding once
   the sections were read side by side, because this is the ONE section that
   appears on both sites telling the same story from the two ends, and two
   different grounds under it made it read as two different features. The
   crossing is the thing that has to be recognisable, so the ground it happens
   on is now shared verbatim with `.link-sec.night` above â€” same radial, same
   stops, same lift at the top where the headline sits.

   The hero is no longer the collision it was: `#promise` on this page is
   already this exact night, so the band is joining a ground the page owns
   rather than importing one.

   The palette split inside the band is load-bearing and must not be crossed.
   GOLD is the tutor / home end. GREEN is the school's own band ladder (Secure,
   Developing) and the affirmative marks. The brick red is the page's own
   stationery, which is why Emerging borrows it rather than introducing a
   fourth hue. A gold tick in the legend, or a green wash on the home half,
   spends a signal the report ladder depends on. */
body.tutor .link-sec.earth {
  position: relative;
  /* lit from the top, where the headline sits, falling to a darker floor.
     Identical to `.link-sec.night` â€” if that one is ever retuned, this moves
     with it, and `python3 _src/verify-earth.py` must be re-run, because the
     gold and sage labels that sit directly on this ground were measured
     against these stops. */
  background:
    radial-gradient(125% 92% at 50% 4%, #1B2A33 0%, #101A20 44%, #080D11 100%);
  border-block: 0;
  color: #E9EFEA;
  overflow: hidden;
  isolation: isolate;
}
body.tutor .link-sec.earth > .wrap { position: relative; z-index: 2; }
body.tutor .lk-earth { position: absolute; inset: 0; z-index: 0; pointer-events: none; }

/* Grit drifts by translating the whole layer EXACTLY one tile â€” the pattern
   repeats, so it lands on an identical frame and the loop is invisible. Done
   this way rather than by animating background-position because `translate`
   is composited: this element is the full width of the band, and repainting
   it every frame for a few motes is not a trade worth making. The layer is
   inset past the band by more than a tile in both axes so the travelling edge
   never comes into view. Up and to the right, against the scroll, same as the
   teach sky.

   The motes were warm â€” sand lifting off a desert floor â€” and went cool with
   the ground in v4, because the same specks that read as blown grit over
   brown read as dust on the lens over blue. They are starlight now, tinted to
   the canvas field `#link-sky` sows on this page's own night band, so the two
   dark sections drift the same weather. */
body.tutor .ea-grit {
  position: absolute;
  inset: -340px -400px;
  background-repeat: repeat;
  animation: ea-drift var(--dur, 52s) linear infinite;
  will-change: transform;
}
@keyframes ea-drift { to { translate: var(--dx) var(--dy); } }
/* near grit â€” biggest, warmest, quickest */
body.tutor .ea-grit.g1 {
  --dx: 340px; --dy: -260px; --dur: 44s;
  background-size: 340px 260px;
  background-image:
    radial-gradient(1.7px 1.7px at 18px 44px, rgba(226,238,247,.19), transparent),
    radial-gradient(1.5px 1.5px at 126px 88px, rgba(226,238,247,.17), transparent),
    radial-gradient(1.8px 1.8px at 210px 132px, rgba(226,238,247,.14), transparent),
    radial-gradient(1.4px 1.4px at 300px 22px, rgba(226,238,247,.18), transparent),
    radial-gradient(1.6px 1.6px at 58px 196px, rgba(226,238,247,.15), transparent),
    radial-gradient(1.3px 1.3px at 246px 232px, rgba(226,238,247,.17), transparent),
    radial-gradient(1.7px 1.7px at 168px 12px, rgba(226,238,247,.13), transparent);
}
body.tutor .ea-grit.g2 {
  --dx: 268px; --dy: -304px; --dur: 76s;
  background-size: 268px 304px;
  background-image:
    radial-gradient(1.2px 1.2px at 34px 118px, rgba(220,233,244,.15), transparent),
    radial-gradient(1.1px 1.1px at 152px 40px, rgba(220,233,244,.12), transparent),
    radial-gradient(1.3px 1.3px at 96px 254px, rgba(220,233,244,.14), transparent),
    radial-gradient(1px 1px at 230px 170px, rgba(220,233,244,.15), transparent),
    radial-gradient(1.2px 1.2px at 200px 288px, rgba(220,233,244,.12), transparent),
    radial-gradient(1.1px 1.1px at 258px 76px, rgba(220,233,244,.12), transparent);
}
/* far â€” barely there, and slow enough that it reads as distance */
body.tutor .ea-grit.g3 {
  --dx: 196px; --dy: -226px; --dur: 118s;
  background-size: 196px 226px;
  background-image:
    radial-gradient(1px 1px at 22px 66px, rgba(214,229,242,.11), transparent),
    radial-gradient(0.9px 0.9px at 118px 18px, rgba(214,229,242,.10), transparent),
    radial-gradient(1px 1px at 74px 174px, rgba(214,229,242,.11), transparent),
    radial-gradient(0.9px 0.9px at 176px 128px, rgba(214,229,242,.09), transparent),
    radial-gradient(1px 1px at 148px 208px, rgba(214,229,242,.10), transparent);
}

/* The fossils are bedded â€” they do not drift, they are IN the ground, which is
   the whole difference between this cast and the grit above it. They sit in
   the margins the .wrap leaves and are dropped at the widths where those
   margins stop existing, so nothing is ever behind copy. Opacity is set here
   rather than in the symbols so the whole cast retunes in one place.

   v4 keeps them, cooled and dimmed. On brown they were bones in soil; on the
   night ground they read as the faint constellation-figure the band's sky is
   drawn around â€” the same shapes doing a different job, which is cheaper than
   losing the page's one piece of prehistory. Dimmer than before because a
   line drawing over a dark ground carries much further than the same line
   over a mid-brown, and at .075 they had become objects rather than texture. */
body.tutor .ea-fx {
  position: absolute;
  color: #dceaf6;
  opacity: 0.055;
}
body.tutor .ea-fx.f1 { left: 1.4%;  top: 15%;  width: 88px;  height: 82px; rotate: -13deg; }
body.tutor .ea-fx.f2 { left: 4.2%;  bottom: 13%; width: 84px; height: 49px; rotate: 9deg; }
body.tutor .ea-fx.f3 { left: 9.5%;  top: 52%;  width: 40px;  height: 40px; rotate: -22deg; opacity: .06; }
body.tutor .ea-fx.f4 { right: 1.8%; top: 9%;   width: 72px;  height: 67px; rotate: 17deg; }
body.tutor .ea-fx.f5 { right: 4.6%; bottom: 18%; width: 78px; height: 36px; rotate: -7deg; opacity: .06; }
@media (max-width: 1320px) { body.tutor .ea-fx.f3 { display: none; } }
@media (max-width: 1180px) {
  body.tutor .ea-fx.f2, body.tutor .ea-fx.f5 { display: none; }
  body.tutor .ea-fx { opacity: .06; }
}
@media (max-width: 900px) { body.tutor .ea-fx { display: none; } }
html.no-motion body.tutor .ea-grit { animation: none; }

/* Ink that sits DIRECTLY on the ground, re-solved for it. Everything else in
   this band sits on a cream card and is safe by construction, which is why
   only these are here. Measured the way _src/verify-band.py measures the teach
   band: screenshot what is actually behind each element's own glyphs and take
   the WORST TENTH of those pixels rather than an average, because the grit
   drifts under the type and an average hides exactly the motes that cause the
   failure.

   v4 moved the ground from brown to night, which is the safe direction for
   every one of these: each stop is darker than the brown it replaces, so the
   gold and the sage gain contrast rather than losing it and the ratios that
   were solved against the earth all still clear. The warm-tinted NEUTRALS did
   need moving â€” a cream at #F4EDDF is a warm cast over a blue-black ground â€”
   so they take the night band's own off-white. Re-run verify-earth.py after
   any further retune of the gradient or the grit. */
body.tutor .link-sec.earth .eyebrow { color: #F7D69C; }
body.tutor .link-sec.earth .eyebrow::before { background: #F7D69C; }
body.tutor .link-sec.earth .tag {
  background: none;
  color: rgba(233,239,234,.75);
  border: 1px solid rgba(233,239,234,.28);
}
body.tutor .link-sec.earth h2 { color: #F6F1E5; }
body.tutor .link-sec.earth .lk-who.home { color: #F7D69C; }
body.tutor .link-sec.earth .lk-who.sch { color: #BEDBC7; }
body.tutor .link-sec.earth .lk-rec { color: #A9CDB6; }

/* â”€â”€ and the band comes in â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€
   Everything below is the section drawn tighter. It had a section head sized
   like a chapter opening (52px at 1440, a 60px well beneath it), then four
   full-width plates stacked at page rhythm â€” 1236px at 1440, about a screen
   and a half for a section that is one idea with a proof and a promise. It is
   the LAST thing before the FAQ and it is addressed to a parent who has
   already read the page, so it should read at a glance, not at chapter
   weight. 1236 â†’ 797, and nothing was cut: the head went two-up, the three
   step cards moved into the crossing's middle column where they were always
   describing it anyway, and the two-column crosses plate became one rule. */
body.tutor .link-sec.earth { padding: 56px 0 54px; }

/* head centred over the crossing, as the teach site's night band is. The lede
   sat beside the claim while the middle column still carried the steps and the
   head had to earn its height; now the steps are a row below, the section reads
   top-down and the head is its axis. */
body.tutor .lk-head {
  max-width: 760px;
  margin-bottom: 26px;
}
body.tutor .lk-head h2 { font-size: clamp(26px, 2.9vw, 35px); line-height: 1.08; }
body.tutor .lk-head .eyebrow { margin-bottom: 10px; }
body.tutor .lk-lede {
  margin: 12px auto 0;
  max-width: 640px;
  font-size: 15px;
  line-height: 1.58;
  color: rgba(233,239,234,.8);
}

/* the legend rail â€” what used to be a 138px two-column plate */
body.tutor .lk-rail {
  display: grid;
  gap: 6px 40px;
  grid-template-columns: 1fr 1fr;
  margin-top: 20px;
  padding-top: 16px;
  border-top: 1px solid rgba(233,239,234,.16);
}
body.tutor .lk-rail p {
  display: flex;
  gap: 8px;
  align-items: baseline;
  font-size: 12.5px;
  line-height: 1.5;
  color: rgba(233,239,234,.55);
}
body.tutor .lk-rail p i {
  flex: none;
  width: 84px;
  font-family: var(--mono);
  font-style: normal;
  font-size: 10px;
  font-weight: 700;
  letter-spacing: 0.1em;
  text-transform: uppercase;
}
body.tutor .lk-rail .yes { color: rgba(240,244,238,.88); }
body.tutor .lk-rail .yes i { color: #BEDBC7; }
body.tutor .lk-rail .no i { color: rgba(233,239,234,.44); }

/* the cross-site link â€” a rule and a line, not a plate. The teach site ends
   with exactly this gesture pointing back here; a 125px card was the section
   paying furniture prices for one sentence, and the sentence it carried
   (lesson plans, worksheets, marking) is that page's job to make, not this
   one's. The underline is the link, so it is drawn rather than implied. */
body.tutor .lk-cross {
  display: flex;
  width: fit-content;
  align-items: center;
  gap: 12px;
  margin: 26px auto 0;
  padding-bottom: 9px;
  border-bottom: 1px solid rgba(235,177,98,.42);
  font-size: 16px;
  font-weight: 600;
  color: #F5CE8C;
  text-decoration: none;
  transition: border-color .22s var(--ease), color .22s var(--ease);
}
body.tutor .lk-cross:hover { color: #FBE0AE; border-bottom-color: rgba(235,177,98,.85); }
body.tutor .lk-cross-ico { width: 18px; height: 18px; flex: none; }
body.tutor .lk-cross-go {
  width: 15px; height: 15px; flex: none;
  transition: transform .22s var(--ease);
}
body.tutor .lk-cross:hover .lk-cross-go { transform: translateX(3px); }

@media (max-width: 900px) {
  body.tutor .lk-rail { grid-template-columns: 1fr; }
}
@media (max-width: 800px) {
  body.tutor .link-sec.earth { padding: 50px 0 48px; }
}

/* â”€â”€ the link bridge Â· only the tick travels â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€
   The hero's 7:42 pm page reappears at the Lantern Link section, stamped
   "stays at home", while the margin tick alone crosses to the teacher's
   Wednesday report â€” the same crossing the teach site draws as its night
   band, seen from home's end. Bars fill on .in ([data-ink]).

   v2 â€” levelled up against that night band, which had three things this end
   lacked. (a) BOTH sides carry the same label (.lk-who), so the row reads as
   one crossing with two ends rather than a page sitting beside a card; the
   page's own `Tue Â· 7:42 pm` stamp moved out to become that label. (b) The
   report names a learner and dates the opt-in â€” an anonymous list of rows is
   a component, not somebody's report, and the whole promise of this section
   is that a real child's evidence moves. (c) The revocation line is stated AT
   the crossing instead of as a footnote four hundred pixels below it.
   Colour still carries the split and must not be crossed: marigold is the
   home end, forest green is the school's own band ladder, and the brick red
   is the page's stationery â€” which is why Emerging borrows it rather than
   introducing a fourth hue. */
/* stretch, not center: both ends carry a .lk-who label, and the crossing only
   reads as one row if the two labels share a baseline. The taller side (the
   report) sets the height and the ruled page grows into it â€” an exercise book
   with the evening's page half used is the truthful picture anyway.
   Tried `start` when the band got its sand ground, on the theory that the
   ~110px of blank ruling could be given back to the desert. It can't: the
   report is the taller column either way, so the section is exactly as tall
   and all that changes is a ragged bottom edge with a hole under the shorter
   side. Stretch costs nothing here. Leave it. */
body.tutor .link-bridge {
  display: grid;
  grid-template-columns: 1.02fr auto 1.1fr;
  gap: 20px;
  align-items: stretch;
  margin: 0;
}
/* a grid item's automatic minimum is its min-content width â€” without this the
   report's nowrap row can drag the whole column past a narrow viewport */
body.tutor .lk-side { display: flex; flex-direction: column; min-width: 0; }
body.tutor .lk-who {
  display: flex;
  align-items: center;
  gap: 8px;
  margin: 0 0 12px;
  padding-left: 2px;
  font-family: var(--mono);
  font-size: 11px;
  font-weight: 700;
  letter-spacing: 0.1em;
  text-transform: uppercase;
}
body.tutor .lk-who svg { width: 14px; height: 14px; flex: none; }
body.tutor .lk-who.home { color: var(--accent-deep); }
body.tutor .lk-who.sch { color: var(--green-deep); }

/* â”€â”€ the home end Â· the conversation, on paper â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€
   The teach site shows this same panel as lit glass on a dark desk, because
   there it is somebody ELSE's evening being described to a teacher. Here it
   is the reader's own house, so it is a page: warm bone, the app's own panel
   colour, sitting on the earth rather than glowing over it.

   The two voices are kept apart the way the whole page keeps them apart, not
   the way a chat UI would. The child speaks in pencil (Caveat, --pencil) on a
   marigold tint; the tutor speaks in the app's own face on the quiet cream.
   That is the tutor page's rule everywhere else â€” the app speaks in
   Fraunces/Jakarta, the child speaks in pencil â€” and a chat panel is no
   reason to break it. No avatars and no name labels: two faces and two sides
   already say who is talking. */
/* natural height, NOT stretched to the column. The ruled page this replaced
   could be stretched for free â€” an exercise book with the evening's page half
   used is the truthful picture anyway â€” but a conversation with 200px of card
   under its last turn just reads as a card that failed to fill. */
body.tutor .lk-chat {
  display: flex;
  flex-direction: column;
  background: var(--card);
  border: 1px solid var(--line);
  border-radius: var(--r-lg);
  box-shadow: 0 30px 66px rgba(4, 9, 13, 0.62);
  padding: 15px 17px 13px;
}
body.tutor .lk-chat h4 {
  font-family: var(--display);
  font-size: 19px;
  font-weight: 600;
  letter-spacing: -0.012em;
  color: var(--ink);
  margin: 0 0 2px;
}
body.tutor .lk-chat-sub { margin: 0 0 12px; font-size: 12.5px; color: var(--muted); }
body.tutor .lk-turns { display: grid; gap: 7px; }
body.tutor .lk-turns span {
  font-size: 13.5px;
  line-height: 1.5;
  border-radius: 14px;
  padding: 8px 13px;
}
body.tutor .t-bot {
  background: var(--paper-2);
  color: var(--ink-2);
  max-width: 93%;
  justify-self: start;
}
/* the child's turn carries the lantern's hue and the child's hand â€” it keeps
   the two voices apart without a name label */
body.tutor .t-kid {
  background: var(--accent-tint);
  color: var(--pencil);
  justify-self: end;
  text-align: right;
  max-width: 88%;
}
body.tutor .t-kid.hand { font-size: 17px; line-height: 1.35; padding: 6px 14px 7px; }
body.tutor .t-kid .hand-tick {
  width: 17px; height: 17px; vertical-align: -3px; margin-left: 5px; color: var(--green);
}
/* the citation is a chip on its own line inside the tutor's turn, not a badge
   floating beside it: it belongs to the sentence it grounds */
/* flex, not inline-flex: the chip takes its own line under the sentence it
   grounds rather than trailing the last word, which is where it kept landing
   in the middle of a wrap */
body.tutor .t-bot .pg-cite { display: flex; width: fit-content; margin-top: 7px; font-size: 10.5px; height: 21px; }
body.tutor .lk-chat-foot {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  margin: 12px 0 0;
  padding-top: 11px;
  border-top: 1px solid var(--line);
}
/* what DID get written down â€” the same code the teacher's top row shows */
body.tutor .lk-rec {
  display: flex;
  align-items: center;
  gap: 7px;
  margin: 0;
  font-family: var(--mono);
  font-size: 10.5px;
  font-weight: 700;
  letter-spacing: 0.07em;
  text-transform: uppercase;
  color: var(--green-deep);
}
body.tutor .lk-rec svg { width: 14px; height: 14px; flex: none; }
/* framework codes are case-bearing â€” 4Bs.02 is not 4BS.02 */
body.tutor .lk-rec code { font: inherit; text-transform: none; }
/* the counterpart to the code beside it: the promise, stamped on the thing it
   is about rather than filed in a footnote below the section */
body.tutor .lk-stamp {
  flex: none;
  rotate: -5deg;
  font-family: var(--mono);
  font-size: 10.5px;
  font-weight: 700;
  letter-spacing: 0.09em;
  text-transform: uppercase;
  color: var(--green-deep);
  border: 1.5px solid var(--green-deep);
  border-radius: 6px;
  padding: 3px 8px;
  opacity: 0.75;
}

/* â”€â”€ the middle column Â· the gate â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€
   The gate is the teach page's .bridge-mid, brought over: two dashed rails
   with the disc between them, travelling. It is drawn rather than left empty
   because this is the one thing in the band that is a TRANSFER and not an
   object â€” everything either side of it is paper, and paper does not need to
   be animated to be believed.

   It carried the three join steps for a while too, which made it a column of
   prose that happened to have an arrow in it. With the steps moved to a row
   below, the only thing standing between the two cards is the only thing that
   actually crosses, and `align-self: center` puts the arrow on the line the
   two cards' own centres sit on. */
body.tutor .lk-mid {
  align-self: stretch;
  display: flex;
  flex-direction: column;
  justify-content: center;
  width: 212px;
  /* 72.8px, and every part of it was measured rather than guessed â€” re-check
     with _src/verify-gate.py, which reports the disc's offset from each card's
     centre and should stay within Â±11px.

     Three things are being corrected at once. (a) The sides carry a .lk-who
     label above the card, so the column must clear its height + margin
     (17.8 + 12) before centring anything: the label is part of the column but
     not part of the crossing. (b) The disc is NOT the gate's midpoint â€” its
     caption hangs below it, putting the arrow 53px into a 170px box, so
     centring the box floats the disc ~32px high and the padding has to pay
     twice that back. (c) The two cards do not share a centre line: a
     conversation and a report are different lengths, and the report runs ~43px
     shorter. There is no value that hits both, so this one splits it â€” the
     disc lands exactly halfway between the two centres, 10.7px from each.
     Prefer that to picking a favourite; the crossing has two ends. */
  padding-top: 72.8px;
  text-align: left;
}
/* a row under the crossing, one step per column */
body.tutor .lk-steps {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: 22px 30px;
  margin: 26px 0 0;
  padding: 0;
  list-style: none;
  counter-reset: lks;
}
body.tutor .lk-steps li {
  display: grid;
  grid-template-columns: 18px 1fr;
  gap: 10px;
  align-items: start;
  font-size: 12.5px;
  line-height: 1.5;
  color: rgba(233, 239, 234, 0.7);
}
body.tutor .lk-steps li::before {
  content: counter(lks);
  counter-increment: lks;
  font-family: var(--mono);
  font-size: 9.5px;
  font-weight: 700;
  width: 18px;
  height: 18px;
  border-radius: 50%;
  display: grid;
  place-items: center;
  translate: 0 1px;
  background: rgba(235, 177, 98, 0.18);
  color: #F5CE8C;
}
body.tutor .lk-steps b {
  display: block;
  font-size: 12.5px;
  font-weight: 700;
  color: #F6F1E5;
}
/* no vertical auto margin: the column it sits in is itself centred against the
   two cards now, so the gate only has to fill its own box. */
body.tutor .lk-gate {
  position: relative;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 10px;
  padding: 30px 6px 26px;
  text-align: center;
}
/* the rails. Two, not one: a single line reads as a divider, and a pair with
   the disc between them reads as a channel with something moving down it. */
body.tutor .lk-gate::before,
body.tutor .lk-gate::after {
  content: "";
  position: absolute;
  left: 6px;
  right: 6px;
  height: 2px;
  background: repeating-linear-gradient(90deg,
    rgba(235,177,98,.62) 0 8px, transparent 8px 17px);
  animation: lk-flow 1.1s linear infinite;
}
body.tutor .lk-gate::before { top: 6px; }
body.tutor .lk-gate::after { bottom: 4px; }
@keyframes lk-flow { to { background-position-x: 17px; } }
body.tutor .lk-arrow {
  display: grid;
  place-items: center;
  width: 46px;
  height: 46px;
  flex: none;
  border-radius: 50%;
  background: rgba(250, 244, 231, 0.06);
  border: 1px solid rgba(235, 177, 98, 0.4);
}
body.tutor .lk-arrow svg { width: 19px; height: 19px; color: #F7D69C; }
body.tutor .lk-gate b {
  display: block;
  font-family: var(--mono);
  font-size: 10px;
  font-weight: 700;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  line-height: 1.5;
  color: #EFEFE6;
}
body.tutor .lk-gate > span:last-child {
  display: block;
  font-size: 11.5px;
  line-height: 1.45;
  color: rgba(233, 239, 234, 0.62);
  text-wrap: balance;
}
html.no-motion body.tutor .lk-gate::before,
html.no-motion body.tutor .lk-gate::after { animation: none; }
body.tutor .lk-report {
  background: var(--card);
  border: 1px solid var(--line);
  border-radius: var(--r-lg);
  box-shadow: var(--sh-1);
  padding: 12px 17px 13px;
}
body.tutor .lk-top {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 10px;
  padding-bottom: 9px;
  border-bottom: 1px solid var(--line);
}
/* marigold, because on both sites this pill means "this row came from the
   home end" â€” the school's own colour is the green band ladder below it */
body.tutor .lk-from {
  display: inline-flex;
  align-items: center;
  gap: 7px;
  padding: 4px 10px;
  border-radius: 999px;
  background: var(--accent-tint);
  font-family: var(--mono);
  font-size: 10px;
  font-weight: 700;
  letter-spacing: 0.09em;
  text-transform: uppercase;
  color: var(--accent-deep);
}
body.tutor .lk-from i {
  width: 6px; height: 6px;
  border-radius: 50%;
  background: var(--accent);
}
body.tutor .lk-opt { font-size: 11.5px; color: var(--muted); }
body.tutor .lk-kid {
  display: flex;
  align-items: baseline;
  flex-wrap: wrap;
  gap: 4px 10px;
  margin: 9px 0 0;
}
body.tutor .lk-kid b {
  font-family: var(--display);
  font-size: 19px;
  font-weight: 600;
  letter-spacing: -0.01em;
  color: var(--ink);
}
body.tutor .lk-kid span { font-size: 12.5px; color: var(--muted); }
/* Two things, both borrowed from the teach site's report and both about the
   same 400-odd pixels this column actually gets. The code and band columns
   are FIXED, not auto â€” 4TWSp.02 is half again as wide as 4Bs.02 and
   "Developing" wider than "Secure", and on auto columns that ragged every
   objective name and every recency out of alignment, which is precisely what
   a report exists to make scannable. And recency sits UNDER the name rather
   than beside it: five things on one line left the name about a hundred
   pixels at 1100px and wrapped "Skeletons & muscles" in two. */
body.tutor .lk-row {
  display: grid;
  grid-template-columns: 66px 1fr 46px 80px;
  grid-template-areas:
    "code name bar band"
    "code ago  ago ago";
  column-gap: 9px;
  row-gap: 1px;
  align-items: center;
  padding: 7px 0;
  border-bottom: 1px solid var(--line);
}
body.tutor .lk-row code { grid-area: code; justify-self: start; align-self: center; }
body.tutor .lk-name { grid-area: name; }
body.tutor .lk-row .lk-bar { grid-area: bar; }
body.tutor .lk-band { grid-area: band; justify-self: start; }
body.tutor .lk-ago { grid-area: ago; justify-self: start; }
body.tutor .lk-row.dim { opacity: 0.75; border-bottom: 0; }
body.tutor .lk-row code {
  font-family: var(--mono);
  font-size: 10.5px;
  font-weight: 700;
  color: var(--accent-deep);
  background: var(--accent-tint);
  padding: 2px 7px;
  border-radius: 6px;
}
body.tutor .lk-name { font-size: 13.5px; font-weight: 600; color: var(--ink); }
body.tutor .lk-bar {
  position: relative;
  height: 6px;
  border-radius: 999px;
  background: var(--cream-deep);
  overflow: hidden;
}
body.tutor .lk-bar::after {
  content: "";
  position: absolute;
  inset: 0 auto 0 0;
  width: 0;
  border-radius: inherit;
  background: var(--green);
  transition: width 0.9s var(--write) 0.35s;
}
body.tutor .lk-bar.dev::after { background: var(--accent); transition-delay: 0.55s; }
/* Emerging borrows the page's own stationery brick rather than adding a
   fourth hue â€” the band ladder stays green â†’ marigold â†’ red as it does on
   the teach site, in this page's inks */
body.tutor .lk-bar.em::after { background: var(--rule-red); transition-delay: 0.75s; }
body.tutor .link-bridge.in .lk-bar::after { width: var(--s); }
body.tutor .lk-band {
  font-family: var(--mono);
  font-size: 10.5px;
  padding: 3px 9px;
  border-radius: 999px;
  background: var(--green-tint);
  color: var(--green-deep);
}
body.tutor .lk-band.dev { background: var(--accent-tint); color: var(--accent-deep); }
body.tutor .lk-band.em {
  background: color-mix(in srgb, var(--rule-red) 18%, var(--card));
  color: var(--rule-red-deep);
}
body.tutor .lk-ago { font-size: 11px; font-style: italic; color: var(--muted); white-space: nowrap; }
/* the one row that isn't an objective: a misunderstanding the tutor saw more
   than once. It crosses because it is the only thing here a teacher can act
   on in the morning â€” hence the marker's own red down its edge. */
body.tutor .lk-mis {
  margin: 9px 0 0;
  padding: 8px 12px;
  border-radius: var(--r-md);
  border-left: 2px solid var(--rule-red);
  background: var(--paper-2);
  font-size: 12.5px;
  line-height: 1.55;
  color: var(--ink-2);
}
body.tutor .lk-mis b {
  font-family: var(--mono);
  font-size: 10px;
  font-weight: 700;
  letter-spacing: 0.09em;
  text-transform: uppercase;
  color: var(--rule-red-deep);
  margin-right: 7px;
}
body.tutor .lk-mis em { font-style: italic; }
body.tutor .lk-mis i {
  margin-left: 6px;
  font-style: italic;
  font-size: 11.5px;
  color: var(--muted);
  white-space: nowrap;
}
html.no-motion body.tutor .lk-bar::after { width: var(--s); transition: none; }

/* â”€â”€ the band, narrow â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€
   The crossing is a left-to-right argument and there is no honest way to keep
   it once the column is gone, so under 1000px it becomes a top-to-bottom one:
   the conversation, then the gate turned a quarter turn with its rails now
   running across the column, then the report. The steps are already a row of
   their own below and simply narrow with the page. */
@media (max-width: 1000px) {
  body.tutor .link-bridge { grid-template-columns: 1fr; gap: 20px; }
  body.tutor .lk-mid { width: auto; padding: 0; }
  body.tutor .lk-steps { gap: 16px; }
  body.tutor .lk-gate {
    margin-top: 16px;
    flex-direction: row;
    flex-wrap: wrap;
    justify-content: center;
    gap: 6px 16px;
    padding: 22px 6px 20px;
    text-align: center;
  }
  body.tutor .lk-gate::before { top: 2px; }
  body.tutor .lk-gate::after { bottom: 0; }
  body.tutor .lk-arrow svg { transform: rotate(90deg); }
  body.tutor .lk-gate b { flex: none; }
  body.tutor .lk-gate > span:last-child { flex: 1 1 240px; max-width: 340px; text-align: left; }
}
@media (max-width: 760px) {
  body.tutor .lk-steps { grid-template-columns: 1fr; gap: 10px; }
  body.tutor .lk-gate > span:last-child { flex-basis: 100%; text-align: center; }
}
@media (max-width: 680px) {
  body.tutor .lk-chat-foot { flex-wrap: wrap; gap: 10px; }
  body.tutor .lk-row { grid-template-columns: 62px 1fr 36px 76px; column-gap: 8px; }
  body.tutor .lk-ago { font-size: 10.5px; }
}

@media (max-width: 560px) {
  body.tutor .lk-row {
    grid-template-columns: 62px 1fr auto;
    grid-template-areas:
      "code name band"
      "code ago  ago";
  }
  body.tutor .lk-row .lk-bar { display: none; }
  body.tutor .lk-name { font-size: 13px; }
}

/* â”€â”€ read one whole session â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€
   The receipts section's final receipt: the hero card's session in full, on
   a scrollable exercise-book page. Static by design â€” this one is for
   READING, so nothing animates except the tick it ends on. */
body.tutor .session {
  display: grid;
  grid-template-columns: 0.9fr 1.1fr;
  gap: 56px;
  align-items: start;
}
body.tutor .ss-txt h3 { font-size: clamp(24px, 2.6vw, 30px); margin: 8px 0 12px; }
body.tutor .ss-txt > p { font-size: 15.5px; line-height: 1.6; color: var(--ink-2); margin: 0 0 18px; }
body.tutor .ss-txt .feat-list b { font-family: var(--mono); font-weight: 700; font-size: 12.5px; color: var(--accent-deep); }
body.tutor .ss-page {
  margin: 0;
  border: 1px solid var(--line);
  border-radius: var(--r-lg);
  box-shadow: var(--sh-2);
  overflow: hidden;
  background: var(--card);
}
body.tutor .ss-head {
  display: block;
  padding: 13px 20px;
  border-bottom: 1px solid var(--line);
  font-family: var(--mono);
  font-size: 11px;
  font-weight: 700;
  letter-spacing: 0.05em;
  text-transform: uppercase;
  color: var(--muted);
}
body.tutor .ss-scroll {
  --rule-x: 50px;
  max-height: 540px;
  overflow-y: auto;
  padding: 24px 26px 30px 70px;
  font-size: 14.5px;
  line-height: var(--rule-gap);
  color: var(--ink-2);
  border-radius: 0;                  /* the card clips; the page runs edge to edge */
  /* the ruling must scroll WITH the writing â€” default attachment pins the
     background to the box and the text would slide over stationary rules */
  background-attachment: local, local;
}
body.tutor .ss-scroll::-webkit-scrollbar { width: 8px; }
body.tutor .ss-scroll::-webkit-scrollbar-thumb { background: var(--line-2); border-radius: 99px; }
body.tutor .ss-scroll p { margin: 0; }
body.tutor .ss-kid { font-size: 20px; line-height: var(--rule-gap); margin-top: var(--rule-gap) !important; }
body.tutor .ss-kid:first-child { margin-top: 0 !important; }
body.tutor .ss-kid .hand-tick { width: 18px; height: 18px; vertical-align: -3px; margin-left: 6px; color: var(--green); }
body.tutor .ss-end { margin-top: var(--rule-gap) !important; }
body.tutor .ss-arm {
  display: block;
  width: 200px;
  margin: 2px 0;
  color: var(--pencil);
}
body.tutor .ss-arm line,
body.tutor .ss-arm ellipse { fill: none; stroke: currentColor; stroke-width: 2; stroke-linecap: round; }
body.tutor .ss-arm circle { fill: currentColor; stroke: none; }
body.tutor .ss-arm text { fill: var(--muted); font-family: var(--mono); font-size: 9.5px; }
body.tutor .ss-foot {
  padding: 12px 20px;
  border-top: 1px solid var(--line);
  font-size: 12.5px;
  line-height: 1.5;
  color: var(--muted);
}
@media (max-width: 1000px) {
  body.tutor .session { grid-template-columns: 1fr; gap: 34px; }
  body.tutor .ss-scroll { max-height: 460px; }
}

/* reduced motion / no GSAP: the page ships finished â€” rule fully inked,
   every tick down, no nib resting on a finished line (same reasoning as
   the teach trail) */
html.no-motion body.tutor .mrule * { transition: none !important; }
html.no-motion body.tutor .mr-fill { height: 100%; }
html.no-motion body.tutor .mr-step { opacity: 1; }
html.no-motion body.tutor .mr-scene .wr { clip-path: none; }
html.no-motion body.tutor .mr-scene .fr { opacity: 1; translate: none; }
html.no-motion body.tutor .mr-mark .hand-tick path { stroke-dashoffset: 0; }
html.no-motion body.tutor .mr-ring { opacity: 0; }   /* every step is reached */

@media (max-width: 1000px) {
  body.tutor .mr-head, body.tutor .mr-steps { padding-left: 56px; }
  body.tutor .mr-rail { left: 28px; }
  /* the ribbon narrows but stays centred on the rule (28â€“34px) */
  body.tutor .mr-fill { left: 24px; width: 14px; }
  body.tutor .mr-tail { width: 14px; height: 12px; }
  body.tutor .mr-mark { left: -32px; width: 14px; height: 14px; }
  body.tutor .mr-mark .hand-tick { width: 12px; height: 12px; }
  body.tutor .mr-step { grid-template-columns: 1fr; gap: 26px; padding: 34px 0 62px; }
  body.tutor .mr-step:last-child { padding-bottom: 18px; }
}


/* â•â•â•â•â•â•â•â•â•â•â•â•â•â•â• INSIDE THE APP Â· the index of doors (#doors) â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•
   The teach page's feature tour is a centred pill row over one wide card. This
   is deliberately a different object: an INDEX â€” tabbed dividers down the left
   in the app's own rail order (study tools, hairline, explore-and-keep), and
   the chosen door opened on the page beside them. The active divider loses its
   right border and shares the page's ground, so tab and page read as one sheet
   rather than a control pointing at a picture.

   Everything inside the page is the app, coded: .ap / .ap-rail / .ap-seg /
   .ap-av / .cp-cur are the same primitives the #learn walkthrough uses. What
   this block adds is (a) the index chrome, (b) the reveal verbs restated for
   this container â€” the originals are .scr-scoped and would not reach here â€”
   and (c) one small stylesheet per scene.
   â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€ */
body.tutor .doors { background: var(--band-alt); position: relative; }
body.tutor .doors::before {
  /* the feint ruling, but only a whisper of it: the page below carries the
     real paper, and a second fully-ruled band back-to-back with #learn reads
     as the same section twice */
  content: "";
  position: absolute; inset: 0;
  background: repeating-linear-gradient(
    to bottom, transparent 0, transparent calc(var(--rule-gap) - 1px),
    var(--rule-feint) calc(var(--rule-gap) - 1px), var(--rule-feint) var(--rule-gap));
  opacity: 0.35;
  pointer-events: none;
}
body.tutor .doors > .wrap { position: relative; z-index: 1; }
body.tutor .dk-head { max-width: 660px; margin-bottom: 34px; }
body.tutor .dk-lede { margin-top: 12px; color: var(--muted); font-size: 16px; line-height: 1.62; }

body.tutor .dk {
  display: grid;
  grid-template-columns: 244px minmax(0, 1fr);   /* min-width:0 â€” a nowrap chip
     row inside a scene measures wider than the column and would drag the grid
     past the viewport (the trap the walkthrough hit) */
  align-items: start;
}

/* â”€â”€ the index â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€ */
body.tutor .dk-index {
  display: flex;
  flex-direction: column;
  gap: 2px;
  padding-top: 26px;
  margin-right: -1px;              /* the active tab's edge sits ON the page */
  position: relative;
  z-index: 2;
}
body.tutor .dk-group {
  font-size: 10.5px;
  font-weight: 700;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: #a0937c;
  margin: 0 0 8px 14px;
  padding-top: 4px;
}
body.tutor .dk-group + .dk-tab { margin-top: 0; }
body.tutor .dk-index .dk-group ~ .dk-group { margin-top: 22px; }

body.tutor .dk-tab {
  position: relative;
  display: grid;
  grid-template-columns: 26px minmax(0, 1fr);
  grid-template-rows: auto auto;
  gap: 1px 8px;
  align-items: center;
  text-align: left;
  padding: 10px 16px 11px 12px;
  border: 1px solid transparent;
  border-right: 0;
  border-radius: 10px 0 0 10px;
  background: transparent;
  color: var(--ink-2);
  cursor: pointer;
  font: inherit;
  transition: background 0.32s var(--write), color 0.32s var(--write),
              border-color 0.32s var(--write), padding-left 0.32s var(--write);
}
body.tutor .dk-no {
  grid-row: 1 / span 2;
  font-family: var(--display);
  font-size: 14px;
  font-style: normal;
  font-weight: 600;
  color: #b9ab90;
  text-align: right;
  transition: color 0.32s var(--write);
}
body.tutor .dk-nm {
  display: flex;
  align-items: center;
  gap: 7px;
  font-family: var(--display);
  font-size: 16px;
  font-weight: 600;
  letter-spacing: -0.01em;
  line-height: 1.2;
}
body.tutor .dk-nm svg { width: 15px; height: 15px; color: #ab9c81; transition: color 0.32s var(--write); }
body.tutor .dk-sub {
  font-size: 11.5px;
  line-height: 1.35;
  color: var(--muted);
  opacity: 0.82;
}
body.tutor .dk-tab:hover { background: rgba(255, 252, 243, 0.62); }
body.tutor .dk-tab[aria-selected="true"] {
  background: var(--card);
  border-color: rgba(150, 116, 60, 0.24);
  padding-left: 16px;
  box-shadow: -3px 0 10px -6px rgba(78, 56, 22, 0.28);
}
body.tutor .dk-tab[aria-selected="true"] .dk-no { color: var(--rule-red-deep); }
body.tutor .dk-tab[aria-selected="true"] .dk-nm svg { color: var(--accent); }
body.tutor .dk-tab[aria-selected="true"] .dk-sub { color: var(--ink-2); opacity: 1; }
/* the tab's own hairline joins the page: cover the shared 1px with the page's
   ground so nothing draws a seam between them */
body.tutor .dk-tab[aria-selected="true"]::after {
  content: "";
  position: absolute;
  top: -1px; bottom: -1px; right: -1px;
  width: 2px;
  background: var(--card);
}
/* the auto-advance clock, drawn as a pencil rule under the open tab */
body.tutor .dk-run {
  position: absolute;
  left: 16px; right: 14px; bottom: 5px;
  height: 1.5px;
  border-radius: 2px;
  background: var(--rule-red);
  transform-origin: 0 50%;
  scale: 0 1;
  opacity: 0;
}
body.tutor .dk-tab[aria-selected="true"] .dk-run.go {
  opacity: 0.5;
  scale: 1 1;
  transition: scale var(--dk-run, 9s) linear, opacity 0.3s var(--write);
}

/* â”€â”€ the opened page â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€ */
body.tutor .dk-page {
  min-width: 0;
  background: var(--card);
  border: 1px solid rgba(150, 116, 60, 0.24);
  border-radius: 4px 14px 14px 14px;
  box-shadow: 0 1px 0 rgba(255, 255, 255, 0.7) inset, 0 16px 34px -20px rgba(78, 56, 22, 0.4);
  padding: 20px 22px 18px;
  position: relative;
}
/* the exercise book's double margin rule, inside the page's left edge */
body.tutor .dk-page::before {
  content: "";
  position: absolute;
  top: 12px; bottom: 12px; left: 11px;
  width: 3px;
  border-left: 1px solid var(--rule-red);
  border-right: 1px solid var(--rule-red);
  opacity: 0.36;
  pointer-events: none;
}
body.tutor .dk-stage { position: relative; }
body.tutor .dk-scene { display: none; }
body.tutor .dk-scene.on { display: block; }

body.tutor .dk-foot {
  display: grid;
  margin-top: 16px;
  padding-top: 14px;
  border-top: 1px dashed rgba(150, 116, 60, 0.3);
}
body.tutor .dk-note { grid-area: 1 / 1; opacity: 0; pointer-events: none; }
body.tutor .dk-note.on { opacity: 1; pointer-events: auto; transition: opacity 0.4s var(--write); }
body.tutor .dk-cap {
  max-width: 62ch;
  font-size: 14.5px;
  line-height: 1.6;
  color: var(--ink-2);
}
body.tutor .dk-rcps { display: flex; flex-wrap: wrap; gap: 7px; margin-top: 11px; }
body.tutor .dk-rcp {
  display: inline-flex;
  align-items: center;
  gap: 5px;
  padding: 4px 11px 5px;
  border: 1px solid rgba(150, 116, 60, 0.28);
  border-radius: 999px;
  background: rgba(255, 253, 246, 0.7);
  font-size: 11.5px;
  font-weight: 600;
  color: var(--muted);
}
body.tutor .dk-rcp svg { width: 12px; height: 12px; color: var(--green); }
body.tutor .dk-marg {
  margin-top: 10px;
  font-family: var(--hand);
  font-size: 19px;
  line-height: 1;
  color: var(--pencil);
  opacity: 0.85;
}
body.tutor .dk-marg u { text-decoration-color: var(--rule-red); text-underline-offset: 2px; }

/* â”€â”€ reveal verbs, restated for this container â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€ */
body.tutor .dk .rise { opacity: 0; translate: 0 9px; }
body.tutor .dk .rise.in {
  opacity: 1; translate: none;
  transition: opacity 0.45s var(--write), translate 0.45s var(--write);
}
body.tutor .dk .ak2-turn.rise { display: none; }
body.tutor .dk .ak2-turn.rise.in { display: flex; animation: tcMsgIn 0.4s var(--write); }
body.tutor .dk .aim { box-shadow: 0 0 0 3px rgba(207, 133, 23, 0.22); }
body.tutor .dk .press { scale: 0.965; }
body.tutor .dk .aim,
body.tutor .dk .press { transition: box-shadow 0.25s var(--write), scale 0.18s var(--write); }

/* â”€â”€ the app frame in this section â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€ */
body.tutor .dk .ap { height: 430px; border-radius: 11px; }
body.tutor .dk .ap-rail { gap: 5px; padding-top: 9px; }
body.tutor .dk .ap-rail i { width: 20px; height: 20px; border-radius: 6px; }
body.tutor .dk .ap-rail i svg { width: 13px; height: 13px; }
body.tutor .rail-sep {
  width: 15px; height: 1px;
  background: rgba(150, 116, 60, 0.3);
  margin: 3px 0;
}
body.tutor .dk .ap-main { padding: 11px 13px 12px; gap: 0; }

/* shared micro-primitives inside the scenes */
body.tutor .ap-lab {
  font-size: 9.5px;
  font-weight: 700;
  letter-spacing: 0.13em;
  text-transform: uppercase;
  color: #a2937b;
  margin: 9px 0 6px;
}
body.tutor .ap-btn {
  display: inline-flex;
  align-items: center;
  gap: 5px;
  padding: 5px 11px 6px;
  border-radius: 999px;
  background: var(--accent);
  color: #fff;
  font-size: 10.5px;
  font-weight: 700;
  font-style: normal;
  white-space: nowrap;
}
body.tutor .dk .dot { display: inline-block; width: 6px; height: 6px; border-radius: 50%; }
body.tutor .dk .dot.sci { background: #cf8517; }
body.tutor .dk .dot.mat { background: #3b7bd0; }
body.tutor .dk .dot.eng { background: #2f6b4f; }
body.tutor .dk .dot.evs { background: #a4633f; }

/* ── walked doors ──────────────────────────────────────────────────────────
   Five doors already played in full further up the page — the evening walks
   Ask, Read, Practise and Progress, and the stars band unfolds Map. Their
   tabs wear a small green tick, and their stage state is a postcard rather
   than a second copy of the same scene: what the scene above left out, and
   an anchor back up to it. */
body.tutor .dk-seen {
  flex: none;
  margin-left: auto;
  width: 15px;
  height: 15px;
  border-radius: 50%;
  display: grid;
  place-items: center;
  background: var(--green);
  color: #f6f2e6;
}
body.tutor .dk-seen svg { width: 9px; height: 9px; }

body.tutor .dk .pc {
  position: relative;
  min-height: 430px;
  display: flex;
  flex-direction: column;
  justify-content: center;
  align-items: flex-start;
  gap: 13px;
  padding: 34px 38px;
  border: 1px solid var(--line-2);
  border-radius: 11px;
  background: var(--card);
}
body.tutor .dk .pc-stamp {
  position: absolute;
  top: 20px;
  right: 20px;
  width: 44px;
  height: 52px;
  border: 1.5px dashed var(--line-2);
  border-radius: 5px;
  display: grid;
  place-items: center;
  color: var(--accent);
  transform: rotate(3deg);
}
body.tutor .dk .pc-stamp svg { width: 19px; height: 19px; }
body.tutor .dk .pc-seen {
  display: inline-flex;
  align-items: center;
  gap: 7px;
  font-size: 11.5px;
  font-weight: 700;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--green);
}
body.tutor .dk .pc-seen svg { width: 14px; height: 14px; }
body.tutor .dk .pc-h {
  font-family: var(--display);
  font-size: 24px;
  font-weight: 600;
  line-height: 1.2;
  letter-spacing: -0.01em;
  color: var(--ink);
  text-wrap: balance;
  max-width: 22ch;
}
body.tutor .dk .pc-p {
  font-size: 15px;
  line-height: 1.62;
  color: var(--ink-2);
  max-width: 46ch;
}
body.tutor .dk .pc-up {
  font-size: 13.5px;
  font-weight: 700;
  color: var(--accent-deep);
  text-decoration: underline;
  text-decoration-color: var(--accent);
  text-decoration-thickness: 1.5px;
  text-underline-offset: 3px;
  transition: color 0.25s var(--write);
}
body.tutor .dk .pc-up:hover { color: var(--ink); }
body.tutor .hm-prog {
  display: block;
  height: 3px;
  border-radius: 2px;
  background: rgba(150, 116, 60, 0.2);
  overflow: hidden;
}
body.tutor .hm-prog b { display: block; height: 100%; background: var(--accent); border-radius: 2px; }

/* â•â• 01 Â· home â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â• */
/* NOT display:flex â€” flex promotes every bare text node to its own item, and
   "Tuesday evening Â· <b>Meera</b>, Stage 4" then reads with a gap before the
   comma. Block flow plus a floated pill gets the same layout honestly. */
body.tutor .hm-who { font-size: 10.5px; color: var(--muted); line-height: 1.7; }
body.tutor .hm-who b { color: var(--ink); }
body.tutor .hm-hud {
  float: right;
  padding: 2px 8px 3px;
  border-radius: 999px;
  background: var(--accent-tint);
  color: var(--accent-deep);
  font-weight: 700;
}
body.tutor .hm-band {
  display: flex;
  align-items: center;
  gap: 9px;
  margin-top: 9px;
  padding: 9px 11px;
  border: 1px solid rgba(47, 107, 79, 0.28);
  border-radius: 10px;
  background: linear-gradient(180deg, #eef6f0, #e6f0e8);
}
body.tutor .hm-ic {
  display: grid; place-items: center;
  flex: none; width: 24px; height: 24px;
  border-radius: 8px;
  background: var(--green); color: #fff;
}
body.tutor .hm-ic svg { width: 14px; height: 14px; }
body.tutor .hm-bb { flex: 1; min-width: 0; display: block; }
body.tutor .hm-eb {
  display: flex; align-items: center; gap: 5px;
  font-size: 9px; font-weight: 800; letter-spacing: 0.1em; text-transform: uppercase;
  color: var(--green-deep);
}
body.tutor .hm-new {
  padding: 1px 5px 2px; border-radius: 4px;
  background: var(--rule-red); color: #fff;
  font-size: 8px; letter-spacing: 0.06em; font-style: normal;
}
body.tutor .hm-bb b { display: block; font-size: 12.5px; font-weight: 700; color: var(--ink); margin-top: 1px; }
body.tutor .hm-s { display: block; font-size: 10px; color: var(--muted); }
body.tutor .hm-due { flex: none; text-align: right; }
body.tutor .hm-due b { display: block; font-size: 10.5px; color: var(--green-deep); }
body.tutor .hm-due span { font-size: 9.5px; color: var(--muted); }
body.tutor .hm-h {
  font-family: var(--display); font-size: 17px; font-weight: 600;
  color: var(--ink); margin: 13px 0 8px; letter-spacing: -0.01em;
}
body.tutor .hm-comp {
  border: 1px solid rgba(150, 116, 60, 0.28);
  border-radius: 11px;
  background: #fffdf7;
  padding: 9px 10px 8px;
  box-shadow: 0 2px 7px rgba(78, 56, 22, 0.07);
}
body.tutor .hm-in { display: block; font-size: 12px; color: #a2947c; }
body.tutor .hm-bar { display: flex; align-items: center; gap: 6px; margin-top: 9px; }
body.tutor .hm-subj {
  padding: 3px 9px 4px; border-radius: 999px;
  background: var(--accent-tint); color: var(--accent-deep);
  font-size: 9.5px; font-weight: 700; font-style: normal;
}
body.tutor .hm-tool {
  margin-left: auto; display: grid; place-items: center;
  width: 21px; height: 21px; border-radius: 50%;
  background: rgba(214, 196, 164, 0.34); color: #8d8171;
}
body.tutor .hm-tool + .hm-tool { margin-left: 0; }
body.tutor .hm-tool.send { background: var(--accent); color: #fff; }
body.tutor .hm-tool svg { width: 11px; height: 11px; }
body.tutor .hm-res { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: 7px; }
body.tutor .hm-card {
  display: block; min-width: 0;
  padding: 8px 9px 9px;
  border: 1px solid rgba(150, 116, 60, 0.2);
  border-radius: 9px;
  background: #fffdf7;
  position: relative;
  overflow: hidden;
}
body.tutor .hm-well {
  position: absolute; right: -8px; top: -8px;
  width: 42px; height: 42px; border-radius: 50%;
  opacity: 0.5;
}
body.tutor .hm-well.books { background: radial-gradient(circle at 35% 35%, #f2d9a8, #d8a95f); }
body.tutor .hm-well.tree { background: radial-gradient(circle at 35% 35%, #cfe6d3, #8bbd9b); }
body.tutor .hm-well.bub { background: radial-gradient(circle at 35% 35%, #e7dcf2, #b9a3d4); }
body.tutor .hm-k {
  display: flex; align-items: center; gap: 4px;
  font-size: 8.5px; font-weight: 800; letter-spacing: 0.08em; text-transform: uppercase;
  color: #a2937b; font-style: normal;
}
body.tutor .hm-k svg { width: 10px; height: 10px; }
body.tutor .hm-card b {
  display: block; font-size: 11.5px; color: var(--ink); margin-top: 3px;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
body.tutor .hm-card b.q { font-family: var(--hand); font-size: 14px; font-weight: 400; color: var(--pencil); }
body.tutor .hm-card > span { display: block; font-size: 9.5px; color: var(--muted); margin-bottom: 5px; }
body.tutor .hm-go { font-size: 9.5px; font-weight: 700; color: var(--accent-deep); font-style: normal; }
body.tutor .hm-map {
  display: flex; align-items: center; gap: 9px;
  margin-top: 10px; padding: 8px 11px;
  border-radius: 10px;
  background: linear-gradient(100deg, #223247, #2f3a52);
  color: #f0e7d4;
}
body.tutor .hm-stars { font-size: 13px; color: #ffd684; font-style: normal; }
body.tutor .hm-mc { flex: 1; min-width: 0; }
body.tutor .hm-mc b { display: block; font-size: 11px; }
body.tutor .hm-dots { display: flex; gap: 2.5px; margin-top: 4px; flex-wrap: wrap; }
body.tutor .hm-dots u {
  width: 4px; height: 4px; border-radius: 50%;
  background: rgba(240, 231, 212, 0.22);
  text-decoration: none;
  transition: background 0.4s var(--write), box-shadow 0.4s var(--write);
}
body.tutor .hm-dots u.lit { background: #ffd684; box-shadow: 0 0 4px rgba(255, 214, 132, 0.8); }
body.tutor .hm-see { flex: none; font-size: 9.5px; font-weight: 700; font-style: normal; color: #ffd684; }

/* â•â• 02 Â· ask â€” the day skin (the walkthrough shows the night one) â•â•â•â•â•â•â• */
body.tutor .ap-ask .ap-main { padding-bottom: 10px; }
body.tutor .ak2-top { display: flex; align-items: baseline; gap: 8px; padding-bottom: 8px; border-bottom: 1px solid rgba(150, 116, 60, 0.16); }
body.tutor .ak2-top b { display: inline-flex; align-items: center; gap: 5px; font-family: var(--display); font-size: 14px; font-weight: 600; color: var(--ink); }
body.tutor .ak2-top b svg { width: 14px; height: 14px; color: var(--accent); }
body.tutor .ak2-sub { font-size: 10px; color: var(--muted); font-style: normal; }
body.tutor .ap-ask .ap-thread { flex: 1; min-height: 0; overflow: hidden; padding-top: 9px; }
/* a short conversation sits on the composer, not under the header â€” the app
   fills the thread from the bottom. min-height keeps scrollHeight equal to the
   frame until there is real overflow, so rollEnd() stays a no-op until then. */
body.tutor .ap-ask .ap-roll {
  display: flex; flex-direction: column; justify-content: flex-end;
  min-height: 100%;
  transition: transform 0.6s var(--write);
}
body.tutor .ak2-turn { display: flex; gap: 7px; margin-bottom: 9px; align-items: flex-start; }
body.tutor .ak2-turn.u { justify-content: flex-end; }
body.tutor .ak2-say {
  max-width: 82%;
  padding: 7px 10px 8px;
  border-radius: 12px 12px 3px 12px;
  background: var(--accent-tint);
  border: 1px solid rgba(207, 133, 23, 0.2);
}
body.tutor .ak2-say em { font-style: normal; font-size: 11.5px; color: #6b4a12; }
body.tutor .ak2-say.bot {
  background: #fffdf7;
  border-color: rgba(150, 116, 60, 0.2);
  border-radius: 12px 12px 12px 3px;
  max-width: 88%;
}
body.tutor .ak2-shot { display: block; width: 78px; margin-bottom: 5px; }
body.tutor .ak2-shot svg { width: 100%; height: auto; display: block; border-radius: 3px; }
body.tutor .ak2-shot .pg { fill: #fffdf7; stroke: rgba(150, 116, 60, 0.45); }
body.tutor .ak2-shot .ln { stroke: #cbbda2; stroke-width: 1.6; stroke-linecap: round; }
body.tutor .ak2-shot .cl { fill: none; stroke: #b1855b; stroke-width: 1.4; }
body.tutor .ak2-shot .wr { stroke: #b1855b; stroke-width: 1.4; stroke-linecap: round; }
body.tutor .ak2-think {
  display: inline-flex; align-items: center; gap: 4px;
  padding: 6px 10px; border-radius: 12px 12px 12px 3px;
  background: #f6efe2; font-size: 10.5px; color: var(--muted);
}
body.tutor .ak2-think i { width: 3px; height: 3px; border-radius: 50%; background: var(--accent); animation: dkBob 1.05s infinite; }
body.tutor .ak2-think i:nth-child(2) { animation-delay: 0.14s; }
body.tutor .ak2-think i:nth-child(3) { animation-delay: 0.28s; }
@keyframes dkBob { 0%, 60%, 100% { opacity: 0.28; translate: 0 0; } 30% { opacity: 1; translate: 0 -2px; } }
body.tutor .ak2-guard {
  display: flex; align-items: center; gap: 5px;
  margin-bottom: 6px; padding: 4px 8px 5px;
  border-radius: 6px;
  background: var(--green-tint);
  border: 1px solid rgba(47, 107, 79, 0.26);
  font-size: 9.5px; font-weight: 700; color: var(--green-deep);
}
body.tutor .ak2-guard svg { width: 11px; height: 11px; flex: none; }
body.tutor .ak2-say.bot p { font-size: 11.5px; line-height: 1.55; color: var(--ink-2); }
body.tutor .ak2-say.bot p + p { margin-top: 6px; }
body.tutor .ak2-acts { display: flex; flex-wrap: wrap; align-items: center; gap: 5px; margin-top: 8px; }
body.tutor .ak2-key {
  display: grid; place-items: center;
  width: 20px; height: 20px; border-radius: 50%;
  background: var(--accent-tint); color: var(--accent-deep);
}
body.tutor .ak2-key svg { width: 12px; height: 12px; fill: currentColor; }
body.tutor .ak2-key .w1, body.tutor .ak2-key .w2 { fill: none; stroke: currentColor; stroke-width: 1.7; stroke-linecap: round; }
body.tutor .ak2-act {
  padding: 3px 8px 4px; border-radius: 999px;
  border: 1px solid rgba(150, 116, 60, 0.26);
  font-size: 9.5px; font-weight: 600; font-style: normal; color: var(--muted);
}
body.tutor .ak2-act.save { border-color: rgba(207, 133, 23, 0.45); color: var(--accent-deep); }
/* the chip she actually pressed, held lit for the rest of the scene */
body.tutor .ak2-act.on {
  background: var(--accent-tint); border-color: rgba(207, 133, 23, 0.5);
  color: var(--accent-deep);
  transition: background 0.3s var(--write), color 0.3s var(--write), border-color 0.3s var(--write);
}
body.tutor .ak2-rate { margin-left: auto; font-size: 9.5px; font-style: normal; color: #a2937b; display: inline-flex; gap: 4px; align-items: center; }

/* reads aloud â€” the speaker breathes while the reply is being spoken */
body.tutor .ak2-key.play { color: #fff; background: var(--accent); animation: dkSpeak 1.5s var(--write) infinite; }
@keyframes dkSpeak {
  0%, 100% { box-shadow: 0 0 0 0 rgba(207, 133, 23, 0.45); }
  55% { box-shadow: 0 0 0 5px rgba(207, 133, 23, 0); }
}

/* â”€â”€ go deeper: the reply is badged as a layer, not a new answer â”€â”€â”€â”€â”€â”€â”€â”€â”€ */
body.tutor .ak2-deep {
  display: flex; align-items: center; gap: 5px;
  margin-bottom: 6px; padding: 4px 8px 5px;
  border-radius: 6px;
  background: rgba(207, 133, 23, 0.11);
  border: 1px solid rgba(207, 133, 23, 0.28);
  font-size: 9.5px; font-weight: 700; color: var(--accent-deep);
}
body.tutor .ak2-deep svg { width: 11px; height: 11px; flex: none; rotate: 90deg; }
/* the question it hands back â€” the tutor never stops on a full stop */
body.tutor .ak2-say.bot p.ak2-ask2 { color: var(--accent-deep); font-weight: 600; }

/* â”€â”€ the generated picture: an empty frame that fills in â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€ */
body.tutor .ak2-fig {
  position: relative;
  margin-top: 8px; padding: 7px 8px 6px;
  border: 1px solid rgba(150, 116, 60, 0.26);
  border-radius: 8px; background: #fffdf7;
}
/* the placeholder sits over the plate, sweeping, until the drawing lands */
body.tutor .ak2-gen {
  position: absolute; inset: 7px 8px 22px;
  display: flex; align-items: center; justify-content: center; gap: 6px;
  border-radius: 5px;
  background: linear-gradient(100deg, #f4ecdc 30%, #fbf5e8 48%, #f4ecdc 66%) 0 0 / 300% 100%;
  animation: dkSweep 1.5s linear infinite;
  font-size: 9.5px; font-weight: 600; color: #9c8b6e;
  z-index: 2;
}
@keyframes dkSweep { to { background-position: -300% 0; } }
body.tutor .ak2-genspin {
  width: 10px; height: 10px; border-radius: 50%;
  border: 1.6px solid rgba(207, 133, 23, 0.3); border-top-color: var(--accent);
  animation: dkSpin 0.8s linear infinite;
}
@keyframes dkSpin { to { rotate: 360deg; } }
body.tutor .ak2-tag {
  position: absolute; top: 11px; right: 12px; z-index: 3;
  display: inline-flex; align-items: center; gap: 3px;
  padding: 2px 6px 3px; border-radius: 999px;
  background: var(--accent); color: #fff;
  font-size: 8px; font-weight: 700; letter-spacing: 0.04em; text-transform: uppercase;
}
body.tutor .ak2-tag svg { width: 9px; height: 9px; }
body.tutor .ak2-fig figcaption { margin-top: 4px; font-size: 9px; color: var(--muted); }

/* the diagram itself: every stroke draws on, then the bulb is switched on */
body.tutor .ak2-dia { display: block; width: 100%; height: auto; }
body.tutor .ak2-dia .pg { fill: #fdf9ef; }
body.tutor .ak2-dia .w, body.tutor .ak2-dia .sw {
  fill: none; stroke: #7c6444; stroke-width: 2.6; stroke-linecap: round; stroke-linejoin: round;
}
body.tutor .ak2-dia .cel { fill: none; stroke: #a5651a; stroke-width: 3.4; stroke-linecap: round; }
body.tutor .ak2-dia .pv { fill: #7c6444; }
body.tutor .ak2-dia .bulb { fill: #fdf6e2; stroke: #a5651a; stroke-width: 2.2; }
body.tutor .ak2-dia .fil { fill: none; stroke: #a5651a; stroke-width: 1.8; stroke-linecap: round; }
body.tutor .ak2-dia .lbl { font-family: var(--font); font-size: 10px; fill: #9c8b6e; }
body.tutor .ak2-dia .rays { fill: none; stroke: #e0a63a; stroke-width: 2.2; stroke-linecap: round; opacity: 0; }
/* drawn state: dashes retracted, bulb lit. Set by .drawn on the <svg>. */
body.tutor .ak2-dia .w, body.tutor .ak2-dia .sw,
body.tutor .ak2-dia .cel, body.tutor .ak2-dia .fil {
  stroke-dasharray: 100; stroke-dashoffset: 100;
}
body.tutor .ak2-dia .pv, body.tutor .ak2-dia .bulb, body.tutor .ak2-dia .lbl { opacity: 0; }
body.tutor .ak2-dia.drawn .w,
body.tutor .ak2-dia.drawn .cel { stroke-dashoffset: 0; transition: stroke-dashoffset 0.85s var(--write); }
body.tutor .ak2-dia.drawn .sw,
body.tutor .ak2-dia.drawn .fil { stroke-dashoffset: 0; transition: stroke-dashoffset 0.45s var(--write) 0.6s; }
body.tutor .ak2-dia.drawn .pv,
body.tutor .ak2-dia.drawn .bulb,
body.tutor .ak2-dia.drawn .lbl { opacity: 1; transition: opacity 0.5s var(--write) 0.35s; }
body.tutor .ak2-dia.lit .bulb { fill: #ffe9a8; }
body.tutor .ak2-dia.lit .rays { opacity: 1; }
body.tutor .ak2-dia.lit .bulb,
body.tutor .ak2-dia.lit .rays { transition: fill 0.4s var(--write), opacity 0.4s var(--write); }

/* â”€â”€ the hands-on offer: the one chip that leaves the screen â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€ */
body.tutor .ak2-offer {
  display: inline-flex; align-items: center; gap: 6px;
  margin-top: 9px; padding: 5px 12px 6px;
  border-radius: 999px;
  background: #fff; border: 1px solid rgba(150, 116, 60, 0.34);
  box-shadow: 0 2px 5px rgba(78, 56, 22, 0.09);
  font-size: 10.5px; font-weight: 700; color: var(--ink);
}
body.tutor .ak2-offer svg { width: 13px; height: 13px; color: var(--accent); }

/* â”€â”€ the activity card it opens â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€ */
body.tutor .ak2-card { max-width: 92%; }
body.tutor .ac-head { display: flex; align-items: center; gap: 6px; }
body.tutor .ac-ic {
  display: grid; place-items: center; flex: none;
  width: 20px; height: 20px; border-radius: 6px;
  background: var(--accent-tint); color: var(--accent-deep);
}
body.tutor .ac-ic svg { width: 12px; height: 12px; }
body.tutor .ac-head b { font-family: var(--display); font-size: 12px; font-weight: 600; color: var(--ink); }
body.tutor .ac-head em { margin-left: auto; font-style: normal; font-size: 9px; color: #a2937b; }
body.tutor .ac-do { margin-top: 6px; }
body.tutor .ac-q {
  display: block; margin-top: 8px; margin-bottom: 5px;
  font-size: 10px; font-weight: 700; color: var(--muted);
}
body.tutor .ac-opt {
  display: flex; align-items: center; gap: 6px;
  margin-bottom: 4px; padding: 4px 8px 5px;
  border: 1px solid rgba(150, 116, 60, 0.22);
  border-radius: 7px; background: #fdfaf3;
  font-size: 10.5px; color: var(--ink-2);
}
body.tutor .ac-opt i {
  flex: none; width: 11px; height: 11px; border-radius: 3px;
  border: 1.4px solid rgba(150, 116, 60, 0.4); background: #fff;
}
body.tutor .ac-opt.pick { border-color: rgba(47, 107, 79, 0.45); background: var(--green-tint); color: var(--green-deep); font-weight: 600; }
body.tutor .ac-opt.pick i {
  border-color: var(--green); background: var(--green);
  box-shadow: inset 0 0 0 1.4px #fff, inset 0 0 0 3px var(--green);
}
body.tutor .ac-opt.pick,
body.tutor .ac-opt.pick i { transition: all 0.3s var(--write); }
body.tutor .ac-foot { display: flex; align-items: center; gap: 8px; margin-top: 7px; }
body.tutor .ac-go {
  padding: 4px 11px 5px; border-radius: 999px;
  background: var(--accent); color: #fff;
  font-size: 9.5px; font-weight: 700; font-style: normal;
}
body.tutor .ac-foot em { font-style: normal; font-size: 9px; color: #a2937b; }
body.tutor .ak2-comp {
  display: flex; align-items: center; gap: 6px;
  margin-top: 8px; padding: 7px 8px;
  border: 1px solid rgba(150, 116, 60, 0.3);
  border-radius: 11px; background: #fffdf7;
}
body.tutor .ak2-att {
  display: inline-flex; align-items: center; gap: 4px;
  padding: 3px 7px 4px; border-radius: 6px;
  background: var(--accent-tint); color: var(--accent-deep);
  font-size: 9.5px; font-weight: 700;
}
body.tutor .ak2-att svg { width: 10px; height: 10px; }
body.tutor .ak2-att i { font-style: normal; opacity: 0.6; }
body.tutor .ak2-field { flex: 1; min-width: 0; font-size: 11.5px; color: var(--ink-2); }
body.tutor .ak2-field em { font-style: normal; }
body.tutor .ak2-mic, body.tutor .ak2-send {
  display: grid; place-items: center; flex: none;
  width: 22px; height: 22px; border-radius: 50%;
  background: rgba(214, 196, 164, 0.34); color: #8d8171;
}
body.tutor .ak2-send { background: var(--accent); color: #fff; }
body.tutor .ak2-mic svg, body.tutor .ak2-send svg { width: 12px; height: 12px; }

/* â•â• 03 Â· library â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â• */
body.tutor .lb2-top { display: flex; align-items: center; gap: 9px; }
body.tutor .lb2-top b { display: inline-flex; align-items: center; gap: 5px; font-family: var(--display); font-size: 15px; font-weight: 600; color: var(--ink); }
body.tutor .lb2-top b svg { width: 14px; height: 14px; color: var(--accent); }
body.tutor .lb2-find {
  margin-left: auto;
  display: inline-flex; align-items: center; gap: 5px;
  padding: 4px 10px 5px; border-radius: 999px;
  border: 1px solid rgba(150, 116, 60, 0.26);
  background: #fffdf7;
  font-size: 10px; font-style: normal; color: #a2947c;
}
body.tutor .lb2-find svg { width: 11px; height: 11px; }
body.tutor .lb2-tabs {
  align-self: flex-start;      /* .ap-main is a flex column: without this the
                                  inline pill stretches to the whole frame */
  margin: 10px 0 2px;
  background: rgba(214, 196, 164, 0.3);
  border-radius: 999px;
  padding: 2px;
}
body.tutor .lb2-pane { display: none; }
body.tutor .lb2-pane.on { display: block; }
/* the reader pane is the one that should own the rest of the frame */
body.tutor .lb2-pane.on[data-l-pane="read"] { display: flex; flex-direction: column; flex: 1; min-height: 0; }
/* opening a book is its own screen in the app â€” shelf chrome stands down */
body.tutor .lb2-rtop { display: none; }
body.tutor .dk-lib .ap-main.reading .lb2-top,
body.tutor .dk-lib .ap-main.reading .lb2-tabs { display: none; }
body.tutor .dk-lib .ap-main.reading .lb2-rtop { display: flex; align-items: center; gap: 9px; }
body.tutor .lb2-rtop b { font-family: var(--display); font-size: 14px; font-weight: 600; color: var(--ink); }
body.tutor .lb2-back { display: inline-flex; align-items: center; gap: 4px; font-size: 10px; font-style: normal; color: var(--accent-deep); font-weight: 600; }
body.tutor .lb2-back svg { width: 11px; height: 11px; }
body.tutor .lb2-rpg { margin-left: auto; font-size: 10px; font-style: normal; color: var(--muted); }
body.tutor .lb2-cont {
  display: flex; align-items: center; gap: 10px;
  padding: 9px 11px;
  border: 1px solid rgba(150, 116, 60, 0.24);
  border-radius: 10px; background: #fffdf7;
}
body.tutor .lb2-cov {
  flex: none; display: grid; place-items: center;
  width: 34px; height: 46px; border-radius: 3px 5px 5px 3px;
  box-shadow: inset 4px 0 0 rgba(0, 0, 0, 0.14), 0 2px 5px rgba(78, 56, 22, 0.2);
}
body.tutor .lb2-cov b { font-size: 7px; font-weight: 800; color: #fff; letter-spacing: 0.04em; text-align: center; padding: 0 2px; }
body.tutor .lb2-cov.sci { background: linear-gradient(150deg, #d99b34, #a5651a); }
body.tutor .lb2-cov.mat { background: linear-gradient(150deg, #4f89d6, #2c5f9e); }
body.tutor .lb2-cov.eng { background: linear-gradient(150deg, #4e9070, #2c6047); }
body.tutor .lb2-cov.evs { background: linear-gradient(150deg, #bc7a52, #8a4f2e); }
body.tutor .lb2-cb { flex: 1; min-width: 0; }
body.tutor .lb2-cb b { display: block; font-size: 12px; color: var(--ink); }
body.tutor .lb2-cb > span { display: block; font-size: 10px; color: var(--muted); margin-bottom: 6px; }
body.tutor .lb2-pg { display: block; font-size: 9.5px; color: #a2937b; margin-top: 4px; }
body.tutor .lb2-open { flex: none; font-size: 10px; font-weight: 700; font-style: normal; color: var(--accent-deep); }
body.tutor .lb2-grid { display: grid; grid-template-columns: repeat(4, minmax(0, 1fr)); gap: 9px; }
body.tutor .lb2-grid.st { grid-template-columns: repeat(3, minmax(0, 1fr)); }
body.tutor .lb2-bk { text-align: center; min-width: 0; }
/* a cover keeps a book's proportions â€” stretched to the cell it reads as a
   coloured banner, not as something you could pull off a shelf */
body.tutor .lb2-bk .lb2-cov { width: 100%; max-width: 56px; height: 68px; margin: 0 auto; border-radius: 3px 6px 6px 3px; }
body.tutor .lb2-bk .lb2-cov b { font-size: 9px; }
body.tutor .lb2-bk > span { display: block; font-size: 9.5px; color: var(--muted); margin-top: 5px; }
body.tutor .lb2-st {
  min-width: 0; padding: 0 0 8px;
  border: 1px solid rgba(150, 116, 60, 0.2);
  border-radius: 9px; overflow: hidden; background: #fffdf7;
}
body.tutor .lb2-art { display: grid; place-items: center; height: 56px; }
/* the cover is a drawing, not a swatch: one line figure per story, keyed to
   what the story is about, in the same ink at every size */
body.tutor .lb2-art svg {
  width: 58px; height: auto;
  fill: none; stroke: rgba(255, 253, 246, 0.92);
  stroke-width: 1.7; stroke-linecap: round; stroke-linejoin: round;
}
body.tutor .lb2-art.a1 { background: linear-gradient(160deg, #f3d79b, #cf8517); }
body.tutor .lb2-art.a2 { background: linear-gradient(160deg, #cfe0f3, #4d7cb5); }
body.tutor .lb2-art.a3 { background: linear-gradient(160deg, #cfe6d3, #3f7d5c); }
body.tutor .lb2-art.a4 { background: linear-gradient(160deg, #e7d6ef, #7a5aa6); }
body.tutor .lb2-art.a5 { background: linear-gradient(160deg, #f6d6c6, #b4643c); }
body.tutor .lb2-art.a6 { background: linear-gradient(160deg, #d9e7bf, #6d8a3a); }
/* on the For-you shelf the story row sits under the books, so it runs tighter
   â€” same card, less art, and labels that don't cost a line each */
body.tutor .lb2-pane[data-l-pane="feed"] .lb2-art { height: 38px; }
body.tutor .lb2-pane[data-l-pane="feed"] .lb2-art svg { width: 42px; }
body.tutor .lb2-pane[data-l-pane="feed"] .ap-lab { margin: 7px 0 5px; }
body.tutor .lb2-pane[data-l-pane="feed"] .lb2-st b { margin-top: 5px; }
body.tutor .lb2-pane[data-l-pane="feed"] .lb2-st { padding-bottom: 6px; }
body.tutor .lb2-st b { display: block; font-size: 10.5px; color: var(--ink); margin: 7px 8px 2px; line-height: 1.25; }
body.tutor .lb2-st span { display: block; font-size: 9px; color: var(--muted); margin: 0 8px; }
body.tutor .lb2-reader {
  display: grid; grid-template-columns: 92px minmax(0, 1fr); gap: 10px;
  margin-top: 10px;
  flex: 1; min-height: 0;      /* fill what's left of the frame, don't float */
}
body.tutor .lb2-toc {
  padding: 8px 9px; border-radius: 8px;
  background: rgba(244, 236, 220, 0.7);
  border: 1px solid rgba(150, 116, 60, 0.16);
}
body.tutor .lb2-toc b { display: block; font-size: 9px; font-weight: 800; letter-spacing: 0.1em; text-transform: uppercase; color: #a2937b; margin-bottom: 6px; }
body.tutor .lb2-toc i { display: block; font-size: 10px; font-style: normal; color: var(--muted); padding: 3px 5px; border-radius: 5px; }
body.tutor .lb2-toc i.on { background: var(--accent-tint); color: var(--accent-deep); font-weight: 700; }
body.tutor .lb2-spread {
  display: grid; grid-template-columns: 1fr 1fr; gap: 14px;
  padding: 12px 14px;
  background: #fffdf7;
  border: 1px solid rgba(150, 116, 60, 0.2);
  border-radius: 8px;
  position: relative;
}
body.tutor .lb2-spread::before { content: ""; position: absolute; top: 8px; bottom: 8px; left: 50%; width: 1px; background: rgba(150, 116, 60, 0.16); }
body.tutor .lb2-col { min-width: 0; }
body.tutor .lb2-col b { display: block; font-family: var(--display); font-size: 12px; font-weight: 600; color: var(--ink); margin-bottom: 7px; }
body.tutor .lb2-col > i { display: block; height: 3px; border-radius: 2px; background: rgba(150, 116, 60, 0.19); margin-bottom: 7px; }
body.tutor .lb2-col > i.s { width: 62%; }
body.tutor .lb2-col > i.t { width: 84%; }
body.tutor .lb2-fig { display: block; width: 118px; margin-bottom: 9px; }
body.tutor .lb2-fig svg { width: 100%; height: auto; }
body.tutor .lb2-fig .cell { fill: #e2c48c; stroke: #a5651a; stroke-width: 1.3; }
body.tutor .lb2-fig .bulb { fill: #fdf0cc; stroke: #a5651a; stroke-width: 1.3; }
body.tutor .lb2-fig .wire { fill: none; stroke: #7c6444; stroke-width: 1.5; stroke-linecap: round; }
body.tutor .lb2-count { text-align: center; font-size: 9.5px; color: #a2937b; margin-top: 8px; }

/* â”€â”€ the story player, opened from the Stories shelf â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€ */
/* the shelf's own kind tag, corner-pinned on the cover art */
body.tutor .lb2-kind {
  position: absolute; left: 5px; bottom: 5px;
  padding: 2px 6px 3px; border-radius: 999px;
  background: rgba(30, 21, 8, 0.5);
  color: #fff; font-size: 8px; font-weight: 700; letter-spacing: 0.05em;
}
body.tutor .lb2-art { position: relative; }
/* playing a story is its own screen, exactly as opening a book is */
body.tutor .lb2-stop { display: none; }
body.tutor .dk-lib .ap-main.playing .lb2-top,
body.tutor .dk-lib .ap-main.playing .lb2-tabs { display: none; }
body.tutor .dk-lib .ap-main.playing .lb2-stop {
  display: flex; align-items: center; gap: 9px;
  padding-bottom: 8px;
  border-bottom: 1px solid rgba(150, 116, 60, 0.16);
}
body.tutor .lb2-steps { margin-left: auto; font-size: 10px; font-style: normal; color: var(--muted); }
body.tutor .lb2-steps b { color: var(--accent-deep); font-weight: 700; }
body.tutor .lb2-pane.on[data-l-pane="story"] { display: flex; flex-direction: column; flex: 1; min-height: 0; }
body.tutor .lb2-node {
  margin-top: 10px;
  padding: 11px 12px 12px;
  border: 1px solid rgba(150, 116, 60, 0.24);
  border-radius: 11px;
  background: #fffdf7;
}
body.tutor .lb2-ntitle {
  display: flex; align-items: center; gap: 5px;
  font-family: var(--display); font-size: 12.5px; font-weight: 600; color: var(--ink);
}
body.tutor .lb2-ntitle svg { width: 13px; height: 13px; color: var(--accent); }
body.tutor .lb2-nart {
  display: grid; place-items: center;
  margin: 8px 0 9px;
  height: 62px; border-radius: 8px;
  background: linear-gradient(160deg, #f7e4bb, #e0ab55);
}
body.tutor .lb2-nart svg { width: 232px; height: auto; }
body.tutor .lb2-nart .loop { fill: none; stroke: #7a5520; stroke-width: 1.7; stroke-linecap: round; stroke-linejoin: round; }
body.tutor .lb2-nart .cell { fill: #fff6e2; stroke: #7a5520; stroke-width: 1.6; }
body.tutor .lb2-nart .cell-t { stroke: #7a5520; stroke-width: 2.4; stroke-linecap: round; }
body.tutor .lb2-nart .lever { stroke: #8e2f22; stroke-width: 2.2; stroke-linecap: round; }
body.tutor .lb2-nart .stud { fill: #7a5520; }
body.tutor .lb2-nart .gap { stroke: #8e2f22; stroke-width: 1.2; stroke-dasharray: 2 3; stroke-linecap: round; opacity: 0.75; }
body.tutor .lb2-nart .bulb { fill: #fffaf0; stroke: #7a5520; stroke-width: 1.6; }
body.tutor .lb2-nart .fil { fill: none; stroke: #7a5520; stroke-width: 1.3; }
body.tutor .lb2-ntext { font-size: 11.5px; line-height: 1.6; color: var(--ink-2); }
body.tutor .lb2-q { margin-top: 10px; padding-top: 9px; border-top: 1px dashed rgba(150, 116, 60, 0.28); }
body.tutor .lb2-qp { font-size: 11.5px; font-weight: 600; color: var(--ink); margin-bottom: 7px; }
body.tutor .lb2-opts { display: grid; gap: 5px; }
body.tutor .lb2-opt {
  display: block; padding: 6px 9px 7px;
  border: 1px solid rgba(150, 116, 60, 0.26);
  border-radius: 8px; background: #fdfaf2;
  font-size: 10.5px; color: var(--ink-2);
  transition: border-color 0.3s var(--write), background 0.3s var(--write), color 0.3s var(--write);
}
body.tutor .lb2-opt.pickd { border-color: rgba(207, 133, 23, 0.6); background: var(--accent-tint); color: var(--accent-deep); font-weight: 600; }
body.tutor .lb2-opt.right { border-color: rgba(47, 107, 79, 0.5); background: var(--green-tint); color: var(--green-deep); font-weight: 600; }
body.tutor .lb2-sub { margin-top: 9px; }
body.tutor .lb2-fb { margin-top: 9px; }
body.tutor .lb2-fb b.ok {
  display: inline-flex; align-items: center; gap: 4px;
  font-size: 10.5px; font-weight: 700; color: var(--green-deep);
}
body.tutor .lb2-fb b.ok svg { width: 12px; height: 12px; }
body.tutor .lb2-fb p { font-size: 11px; line-height: 1.55; color: var(--ink-2); margin: 5px 0 8px; }

/* â•â• 04 Â· mark & ask â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â• */
body.tutor .mk-top { display: flex !important; align-items: center; gap: 9px; padding-bottom: 8px; border-bottom: 1px solid rgba(150, 116, 60, 0.16); }
body.tutor .mk-body {
  position: relative;                /* the popover, the fab and the panel all
                                        anchor to the reading area, not the page */
  flex: 1; min-height: 0;
  margin-top: 9px;
  overflow: hidden;
}
/* With the panel docked, the page keeps the column the panel leaves it â€” so
   the sentence she marked stays beside the answer instead of underneath it. */
body.tutor .mk-body.paneled .mk-page { padding-right: 57%; }
body.tutor .mk-page {
  height: 100%;
  padding: 11px 13px 12px;
  transition: padding-right 0.5s var(--write);
  border: 1px solid rgba(150, 116, 60, 0.22);
  border-radius: 8px;
  background: #fffdf7;
  box-shadow: inset 0 0 0 1px rgba(255, 255, 255, 0.6);
  overflow: hidden;
}
body.tutor .mk-h { display: block; font-family: var(--display); font-size: 12.5px; font-weight: 600; color: var(--ink); margin-bottom: 7px; }
body.tutor .mk-p { font-size: 11px; line-height: 1.62; color: var(--ink-2); }
body.tutor .mk-p + .mk-p { margin-top: 6px; }
/* The marked line. The app paints its own rects over the words it resolved â€”
   here the same paint is one background swept left to right, cloned onto every
   line box so a two-line sentence fills like a highlighter, not like a block. */
body.tutor .mk-sel {
  -webkit-box-decoration-break: clone;
  box-decoration-break: clone;
  padding: 1px 1px 2px;
  border-radius: 2px;
  background-image: linear-gradient(rgba(240, 186, 74, 0.5), rgba(240, 186, 74, 0.5));
  background-repeat: no-repeat;
  background-size: 0% 100%;
  transition: background-size 0.85s var(--write);
}
body.tutor .mk-sel.mkd { background-size: 100% 100%; }
/* once "Ask about this" is tapped the paint stays, a shade deeper â€” the app
   keeps the confirmed highlight on the page while the panel answers it */
body.tutor .mk-sel.kept {
  background-image: linear-gradient(rgba(233, 170, 47, 0.62), rgba(233, 170, 47, 0.62));
  box-shadow: 0 1px 0 rgba(166, 108, 16, 0.5);
}
body.tutor .mk-fig { display: block; width: 132px; margin: 8px 0; }
body.tutor .mk-fig svg { width: 100%; height: auto; display: block; }
body.tutor .mk-fig .wire { fill: none; stroke: #7c6444; stroke-width: 1.5; stroke-linecap: round; stroke-linejoin: round; }
body.tutor .mk-fig .cell { fill: #f0dcb4; stroke: #a5651a; stroke-width: 1.4; }
body.tutor .mk-fig .bulb { fill: #fdf0cc; stroke: #a5651a; stroke-width: 1.4; }
body.tutor .mk-fig .fil { fill: none; stroke: #a5651a; stroke-width: 1.2; }
body.tutor .mk-fig .lever { stroke: #9c3a28; stroke-width: 2; stroke-linecap: round; }
body.tutor .mk-fig em { display: block; margin-top: 3px; font-size: 8.5px; font-style: italic; color: #a2937b; }
/* the finger, travelling the line it is marking. Where it starts and how far
   it runs are measured off the sentence itself (DK.mark.aim), never guessed â€”
   the text rewraps at every width. */
body.tutor .mk-cursor {
  position: absolute; left: 22px; top: 92px;
  width: 9px; height: 9px; border-radius: 50%;
  background: rgba(166, 108, 16, 0.5);
  box-shadow: 0 0 0 4px rgba(240, 186, 74, 0.22);
  opacity: 0;
  transition: opacity 0.25s var(--write), translate 0.85s var(--write);
}
body.tutor .mk-cursor.on { opacity: 1; }
body.tutor .mk-cursor.drag { translate: var(--mk-dx, 168px) var(--mk-dy, 15px); }
body.tutor .mk-pop {
  position: absolute; left: 96px; top: 66px;
  transform: translateX(-50%);      /* centred on the first marked line, as the
                                       app centres it on the first painted rect */
  padding: 4px 10px 5px;
  border-radius: 999px;
  background: var(--accent-deep); color: #fff;
  font-size: 10px; font-weight: 700; font-style: normal;
  white-space: nowrap;
  box-shadow: 0 4px 12px -4px rgba(78, 56, 22, 0.7);
  opacity: 0; translate: 0 4px; scale: 0.94;
  transform-origin: 50% 120%;
  transition: opacity 0.28s var(--write), translate 0.28s var(--write), scale 0.28s var(--write);
  pointer-events: none;
}
body.tutor .mk-pop::after {
  content: ""; position: absolute; left: 50%; bottom: -3px;
  width: 7px; height: 7px; margin-left: -3.5px;
  background: var(--accent-deep); rotate: 45deg; border-radius: 1px;
}
body.tutor .mk-pop.up { opacity: 1; translate: none; scale: 1; }
body.tutor .mk-pop.press { scale: 0.9; }
/* the minimized panel: a lantern button parked bottom-right of the reader */
body.tutor .mk-fab {
  position: absolute; right: 10px; bottom: 10px;
  display: inline-flex; align-items: center; gap: 5px;
  padding: 5px 11px 6px;
  border-radius: 999px;
  background: #fffdf7;
  border: 1px solid rgba(207, 133, 23, 0.45);
  box-shadow: 0 4px 12px -6px rgba(78, 56, 22, 0.6);
  font-size: 10px; font-weight: 700; font-style: normal; color: var(--accent-deep);
  transition: opacity 0.3s var(--write), translate 0.3s var(--write);
}
body.tutor .mk-fab svg { width: 13px; height: 13px; color: var(--accent); }
body.tutor .mk-fab.gone { opacity: 0; translate: 0 6px; }
/* the docked answer panel */
body.tutor .mk-panel {
  position: absolute; top: 0; right: 0; bottom: 0;
  width: 54%;
  display: flex; flex-direction: column;
  border: 1px solid rgba(150, 116, 60, 0.28);
  border-radius: 10px;
  background: #fdf9f0;
  box-shadow: -12px 0 26px -18px rgba(78, 56, 22, 0.8);
  translate: 106% 0;
  opacity: 0;
  transition: translate 0.5s var(--write), opacity 0.35s var(--write);
}
body.tutor .mk-panel.open { translate: 0 0; opacity: 1; }
body.tutor .mk-ph {
  display: flex; align-items: center; gap: 6px;
  padding: 8px 9px;
  border-bottom: 1px solid rgba(150, 116, 60, 0.2);
}
body.tutor .mk-pt { flex: 1; min-width: 0; }
body.tutor .mk-pt b { display: block; font-family: var(--display); font-size: 12px; font-weight: 600; color: var(--ink); }
body.tutor .mk-pt i { display: block; font-size: 8.5px; font-style: normal; color: var(--muted); }
body.tutor .mk-full {
  flex: none;
  padding: 3px 7px 4px; border-radius: 999px;
  border: 1px solid rgba(150, 116, 60, 0.3);
  font-size: 8.5px; font-weight: 700; font-style: normal; color: var(--muted);
  opacity: 0.42;
  transition: opacity 0.35s var(--write), color 0.35s var(--write), border-color 0.35s var(--write);
}
body.tutor .mk-full.live { opacity: 1; color: var(--accent-deep); border-color: rgba(207, 133, 23, 0.5); }
body.tutor .mk-ic { flex: none; display: grid; place-items: center; width: 15px; height: 15px; color: #a2937b; font-size: 10px; font-style: normal; }
body.tutor .mk-ic svg { width: 12px; height: 12px; }
/* the vertical padding belongs to the roll, not to its window: rollEnd()
   measures scrollHeight against the window's clientHeight, and padding on the
   window would make it stop short and clip the newest turn */
body.tutor .mk-scroll { flex: 1; min-height: 0; overflow: hidden; padding: 0 9px; }
body.tutor .mk-roll { padding: 9px 0; transition: transform 0.6s var(--write); }
/* the quote chip IS the child's turn â€” the app never re-types the sentence */
body.tutor .mk-quote {
  position: relative;
  padding: 7px 9px 8px 16px;
  border-radius: 8px;
  border: 1px solid rgba(207, 133, 23, 0.32);
  border-left: 3px solid var(--accent);
  background: var(--accent-tint);
}
body.tutor .mk-qm { position: absolute; left: 6px; top: 3px; font-family: var(--display); font-size: 15px; color: rgba(166, 108, 16, 0.55); }
body.tutor .mk-qt { display: block; font-size: 10.5px; line-height: 1.5; color: #6b4a12; }
body.tutor .mk-qs { display: block; margin-top: 4px; font-size: 8.5px; font-weight: 700; color: rgba(107, 74, 18, 0.68); }
body.tutor .mk-asked { margin-top: 5px; font-size: 9px; font-style: italic; color: #a2937b; }
body.tutor .mk-think {
  display: inline-flex; align-items: center; gap: 4px;
  margin-top: 8px; padding: 5px 9px; border-radius: 12px 12px 12px 3px;
  background: #f6efe2; font-size: 9.5px; color: var(--muted);
}
body.tutor .mk-think i { width: 3px; height: 3px; border-radius: 50%; background: var(--accent); animation: dkBob 1.05s infinite; }
body.tutor .mk-think i:nth-child(2) { animation-delay: 0.14s; }
body.tutor .mk-think i:nth-child(3) { animation-delay: 0.28s; }
body.tutor .mk-ans {
  margin-top: 8px; padding: 8px 10px 9px;
  border-radius: 12px 12px 12px 3px;
  border: 1px solid rgba(150, 116, 60, 0.2);
  background: #fffdf7;
}
body.tutor .mk-ans p { font-size: 10.5px; line-height: 1.55; color: var(--ink-2); }
body.tutor .mk-ans p + p { margin-top: 6px; }
/* the display face has no true italic, so the browser slants it â€” which eats
   the space after the last glyph. Give it that space back. */
body.tutor .mk-ans em { font-style: italic; color: var(--accent-deep); padding-right: 0.09em; }
body.tutor .mk-acts { display: flex; align-items: center; gap: 6px; margin-top: 8px; }
body.tutor .mk-save {
  display: grid; place-items: center;
  width: 20px; height: 20px; border-radius: 50%;
  border: 1px solid rgba(150, 116, 60, 0.3);
  color: #a2937b;
  transition: background 0.3s var(--write), color 0.3s var(--write), border-color 0.3s var(--write);
}
body.tutor .mk-save svg { width: 11px; height: 11px; }
body.tutor .mk-save.kept { background: var(--accent-tint); border-color: rgba(207, 133, 23, 0.5); color: var(--accent-deep); }
body.tutor .mk-save.kept svg { fill: currentColor; }
body.tutor .mk-src {
  display: inline-flex; align-items: center; gap: 4px;
  font-size: 8.5px; font-weight: 700; font-style: normal; color: var(--green-deep);
  padding: 3px 7px 4px; border-radius: 999px;
  background: var(--green-tint); border: 1px solid rgba(47, 107, 79, 0.24);
}
body.tutor .mk-src svg { width: 10px; height: 10px; }
body.tutor .mk-u { display: flex; justify-content: flex-end; margin-top: 9px; }
body.tutor .mk-u span {
  max-width: 86%;
  padding: 6px 9px 7px; border-radius: 12px 12px 3px 12px;
  background: var(--accent-tint); border: 1px solid rgba(207, 133, 23, 0.2);
  font-size: 10.5px; color: #6b4a12;
}
body.tutor .mk-comp {
  display: flex; align-items: center; gap: 5px;
  margin: 0 9px 9px;
  padding: 5px 6px;
  border: 1px solid rgba(150, 116, 60, 0.3);
  border-radius: 999px; background: #fffdf7;
}
body.tutor .mk-mic, body.tutor .mk-send {
  display: grid; place-items: center; flex: none;
  width: 19px; height: 19px; border-radius: 50%;
  background: rgba(214, 196, 164, 0.34); color: #8d8171;
}
body.tutor .mk-send { background: var(--accent); color: #fff; }
body.tutor .mk-mic svg, body.tutor .mk-send svg { width: 11px; height: 11px; }
body.tutor .mk-field { flex: 1; min-width: 0; font-size: 10px; color: var(--ink-2); white-space: nowrap; overflow: hidden; }
body.tutor .mk-field em { font-style: normal; }
body.tutor .mk-ph-txt { font-weight: 400; color: #a2947c; }
body.tutor .mk-ph-txt.gone { display: none; }
body.tutor .mk-foot {
  display: flex; align-items: center; justify-content: center; gap: 10px;
  margin-top: 8px; font-size: 9.5px; color: #a2937b;
}
body.tutor .mk-foot i {
  display: grid; place-items: center;
  width: 15px; height: 15px; border-radius: 50%;
  border: 1px solid rgba(150, 116, 60, 0.28);
  font-style: normal; font-size: 9px;
}

/* â•â• 05 Â· practice â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â• */
/* the app's two-tone hero, compressed to one line of chrome */
body.tutor .pk-top { display: flex; align-items: baseline; gap: 6px; }
body.tutor .pk-top > b { font-family: var(--display); font-size: 14px; font-weight: 600; color: var(--accent-deep); }
body.tutor .pk-top em { font-family: var(--display); font-size: 14px; font-style: normal; font-weight: 600; color: var(--ink); }
body.tutor .pk-hist { margin-left: auto; font-size: 9.5px; font-style: normal; font-weight: 700; color: var(--accent-deep); }
body.tutor .pk-when { margin-left: auto; font-size: 10px; font-style: normal; color: var(--muted); }

body.tutor .pk-modes { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: 7px; margin: 9px 0 2px; }
body.tutor .pk-mode {
  min-width: 0; display: flex; align-items: flex-start; gap: 6px;
  padding: 7px 9px 8px;
  border: 1px solid rgba(150, 116, 60, 0.22);
  border-radius: 9px; background: #fffdf7;
  transition: border-color 0.3s var(--write), background 0.3s var(--write);
}
body.tutor .pk-mt { flex: 1; min-width: 0; }
body.tutor .pk-mode b { display: block; font-size: 11.5px; color: var(--ink); }
body.tutor .pk-mode span span,
body.tutor .pk-mode .pk-mt > span { display: block; font-size: 9.5px; color: var(--muted); margin-top: 1px; }
body.tutor .pk-mk { flex: none; opacity: 0; transition: opacity 0.3s var(--write); }
body.tutor .pk-mk svg { width: 13px; height: 13px; color: var(--accent-deep); }
body.tutor .pk-mode.on { border-color: var(--accent); background: var(--accent-tint); box-shadow: 0 0 0 1px var(--accent) inset; }
body.tutor .pk-mode.on .pk-mk { opacity: 1; }

body.tutor .pk-pane { display: none; }
/* the visible pane owns the rest of the frame â€” otherwise a short pane leaves
   the app's floor floating halfway up the card */
body.tutor .pk-pane.on { display: block; flex: 1; min-height: 0; }
body.tutor .pk-pane.on[data-k-pane="gate"] { display: flex; flex-direction: column; }
body.tutor .pk-gwrap { margin: auto 0; }
/* the setup reads as two questions side by side, as the app lays it out */
body.tutor .pk-2 { display: grid; grid-template-columns: minmax(0, 1fr) minmax(0, 1fr); gap: 0 16px; }
body.tutor .pk-2 > div { min-width: 0; }
body.tutor .pk-2 .ap-lab { margin-top: 8px; }
body.tutor .ap-lab .ap-sub {
  margin-left: 6px; font-size: 8.5px; font-weight: 600; font-style: normal;
  letter-spacing: 0.02em; text-transform: none; color: #b3a68e;
}
body.tutor .pk-subs { display: flex; flex-wrap: wrap; gap: 4px; }
body.tutor .pk-subs i {
  display: inline-flex; align-items: center; gap: 4px;
  padding: 3px 9px 4px; border-radius: 999px;
  border: 1px solid rgba(150, 116, 60, 0.24);
  font-size: 9.5px; font-weight: 600; font-style: normal; color: var(--muted);
}
body.tutor .pk-subs i.on { border-color: var(--accent); background: var(--accent-tint); color: var(--accent-deep); }
body.tutor .pk-field {
  display: block; padding: 7px 10px;
  border: 1px solid rgba(150, 116, 60, 0.28);
  border-radius: 9px; background: #fffdf7;
  font-size: 10.5px; color: #a2947c;
}
body.tutor .pk-kind { width: 100%; }
body.tutor .pk-kind i { font-size: 9.5px; padding: 4px 6px; }
body.tutor .pk-hint { font-size: 9px; line-height: 1.4; color: #a2937b; margin-top: 4px; }
body.tutor .pk-auto {
  display: block; padding: 6px 9px 7px; border-radius: 8px;
  border: 1px dashed rgba(150, 116, 60, 0.36);
  background: rgba(244, 236, 220, 0.55);
}
body.tutor .pk-auto b { display: block; font-size: 10.5px; color: var(--ink); }
body.tutor .pk-auto small { display: block; font-size: 9px; color: var(--muted); }

/* how hard: three clay pebbles, the app's own still-life */
body.tutor .pk-diff { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: 7px; }
body.tutor .pk-d {
  position: relative; min-width: 0; text-align: center;
  padding: 7px 8px 8px; border-radius: 9px;
  border: 1px solid rgba(150, 116, 60, 0.22);
  background: #fffdf7;
}
body.tutor .pk-d .pb {
  display: block; width: 26px; height: 15px; margin: 1px auto 5px;
  border-radius: 13px 13px 9px 9px;
  box-shadow: inset 0 -3px 5px rgba(0, 0, 0, 0.12), inset 0 2px 3px rgba(255, 255, 255, 0.5);
}
body.tutor .pk-d .pb.warm { background: linear-gradient(160deg, #e8c99a, #c99a5e); }
body.tutor .pk-d .pb.lev { background: linear-gradient(160deg, #d9a05f, #b4713a); }
body.tutor .pk-d .pb.str { background: linear-gradient(160deg, #b98a68, #8a563a); }
body.tutor .pk-d b { display: block; font-size: 10.5px; color: var(--ink); }
body.tutor .pk-d small { display: block; font-size: 8.5px; line-height: 1.3; color: var(--muted); }
body.tutor .pk-d.on { border-color: var(--accent); background: var(--accent-tint); box-shadow: 0 0 0 1px var(--accent) inset; }
body.tutor .pk-tick {
  position: absolute; top: -5px; right: -5px;
  display: grid; place-items: center; width: 15px; height: 15px;
  border-radius: 50%; background: var(--accent); color: #fff;
}
body.tutor .pk-tick svg { width: 9px; height: 9px; }

/* which mistake â€” a named misconception, not a topic */
body.tutor .pk-ws { display: grid; gap: 5px; }
body.tutor .pk-w {
  display: flex; align-items: center; gap: 8px;
  padding: 6px 9px 7px;
  border: 1px solid rgba(150, 116, 60, 0.22);
  border-radius: 9px; background: #fffdf7;
  transition: border-color 0.3s var(--write), background 0.3s var(--write);
}
body.tutor .pk-w > span { flex: 1; min-width: 0; }
body.tutor .pk-w b { display: block; color: var(--ink); font-family: var(--hand); font-size: 14px; font-weight: 400; line-height: 1.25; }
body.tutor .pk-w i { display: flex; align-items: center; gap: 4px; font-size: 9px; font-style: normal; color: var(--muted); }
body.tutor .pk-radio {
  flex: none; width: 13px; height: 13px; border-radius: 50%;
  border: 1.6px solid rgba(150, 116, 60, 0.4);
  transition: border-color 0.3s var(--write), box-shadow 0.3s var(--write);
}
body.tutor .pk-sev {
  flex: none; padding: 2px 7px 3px; border-radius: 999px;
  border: 1px solid rgba(150, 116, 60, 0.26);
  font-size: 8.5px; font-weight: 700; font-style: normal; color: var(--muted);
}
body.tutor .pk-sev.hot { border-color: rgba(207, 115, 97, 0.5); background: rgba(207, 115, 97, 0.11); color: #a4503d; }
body.tutor .pk-w.on { border-color: var(--accent); background: var(--accent-tint); }
body.tutor .pk-w.on .pk-radio { border-color: var(--accent); box-shadow: inset 0 0 0 2.4px #fffdf7, inset 0 0 0 9px var(--accent); }
body.tutor .pk-fold { margin-top: 6px; }
body.tutor .pk-launch { display: flex; align-items: center; gap: 9px; margin-top: 10px; }
body.tutor .pk-hard { display: flex; align-items: center; gap: 7px; font-size: 9.5px; font-weight: 700; letter-spacing: 0.08em; text-transform: uppercase; color: #a2937b; }
body.tutor .pk-opts { background: rgba(214, 196, 164, 0.3); border-radius: 999px; padding: 2px; }
/* the label above is uppercase; the pills inside it are NOT â€” reset, or the
   segmented control shouts back at its own caption */
body.tutor .pk-opts i { font-size: 9.5px; text-transform: none; letter-spacing: 0; }
body.tutor .pk-launch .ap-btn { margin-left: auto; }
body.tutor .pk-count { font-size: 10px; color: var(--muted); }

/* the escape hatches the app gives every question */
body.tutor .pk-esc { display: flex; align-items: center; gap: 7px; margin-top: 6px; }
body.tutor .pk-none, body.tutor .pk-oth {
  display: inline-flex; align-items: center; gap: 6px;
  padding: 5px 9px 6px; border-radius: 8px;
  border: 1px dashed rgba(150, 116, 60, 0.32);
  font-size: 10px; color: var(--muted);
}
body.tutor .pk-none i { width: 11px; height: 11px; border-radius: 3px; border: 1.4px solid rgba(150, 116, 60, 0.45); }
body.tutor .pk-oth { flex: 1; min-width: 0; color: #a2947c; }
body.tutor .pk-oth b { font-weight: 700; color: var(--muted); }

/* the generated activity's own header, carried on every quiz pane */
body.tutor .pk-act { display: flex; align-items: baseline; gap: 7px; margin-bottom: 5px; }
body.tutor .pk-act em {
  flex: none; padding: 2px 7px 3px; border-radius: 999px;
  background: var(--accent-tint); border: 1px solid rgba(207, 133, 23, 0.34);
  font-size: 8.5px; font-weight: 700; font-style: normal; color: var(--accent-deep);
}
body.tutor .pk-act > b { font-family: var(--display); font-size: 12.5px; font-weight: 600; color: var(--ink); }
body.tutor .pk-mis { margin-left: auto; font-size: 9px; font-style: normal; color: var(--muted); white-space: nowrap; }
body.tutor .pk-inst {
  font-size: 10px; line-height: 1.45; color: var(--ink-2);
  padding: 6px 9px 7px; border-radius: 8px;
  background: rgba(244, 236, 220, 0.62);
  border-left: 2px solid rgba(207, 133, 23, 0.5);
}
body.tutor .pk-pgh { display: flex; align-items: baseline; margin: 9px 0 4px; font-size: 9.5px; color: var(--muted); }
body.tutor .pk-pgh i { font-style: normal; font-weight: 700; color: var(--ink-2); }
body.tutor .pk-pgh > b { margin-left: auto; font-weight: 600; }
body.tutor .pk-pgb { display: flex; gap: 4px; }
body.tutor .pk-pgb i {
  flex: 1; height: 4px; border-radius: 2px;
  background: rgba(150, 116, 60, 0.2);
  transition: background 0.35s var(--write);
}
body.tutor .pk-pgb i.done { background: var(--green); }
body.tutor .pk-pgb i.cur { background: var(--accent); }
body.tutor .pk-q { font-size: 12.5px; line-height: 1.45; color: var(--ink); margin: 9px 0 7px; }
/* four lettered options, two up â€” the app's options grid */
body.tutor .pk-ch { display: grid; grid-template-columns: 1fr 1fr; gap: 5px; }
body.tutor .pk-ch span {
  display: flex; align-items: center; gap: 7px;
  padding: 6px 9px 7px;
  border: 1px solid rgba(150, 116, 60, 0.24);
  border-radius: 8px; background: #fffdf7;
  font-size: 10.5px; line-height: 1.3; color: var(--ink-2);
  transition: border-color 0.3s var(--write), background 0.3s var(--write), color 0.3s var(--write);
}
body.tutor .pk-ch span > b {
  flex: none; display: grid; place-items: center;
  width: 15px; height: 15px; border-radius: 5px;
  background: rgba(214, 196, 164, 0.42);
  font-size: 8.5px; font-weight: 800; color: #8a7a62;
}
body.tutor .pk-ch span.pickd { border-color: var(--accent); background: var(--accent-tint); }
body.tutor .pk-ch span.pickd > b { background: var(--accent); color: #fff; }
body.tutor .pk-ch span.right { border-color: var(--green); background: var(--green-tint); color: var(--green-deep); font-weight: 700; }
body.tutor .pk-nav { display: flex; align-items: center; gap: 9px; margin-top: 9px; }
body.tutor .pk-nav i {
  padding: 4px 10px 5px; border-radius: 999px;
  border: 1px solid rgba(150, 116, 60, 0.3);
  font-size: 9.5px; font-weight: 700; font-style: normal; color: var(--muted);
}
body.tutor .pk-nav > span { flex: 1; text-align: center; font-size: 9px; color: #a2937b; }
body.tutor .pk-nav.gate > .ap-btn { margin-left: auto; font-size: 10px; }
body.tutor .pk-dots { display: flex; justify-content: center; gap: 5px; margin-top: 8px; }
body.tutor .pk-dots i {
  display: grid; place-items: center;
  width: 17px; height: 17px; border-radius: 50%;
  border: 1px solid rgba(150, 116, 60, 0.28);
  font-size: 8.5px; font-weight: 700; font-style: normal; color: #a2937b;
}
body.tutor .pk-dots i.ok { border-color: rgba(47, 107, 79, 0.4); background: var(--green-tint); color: var(--green-deep); }
body.tutor .pk-dots i.cur { border-color: var(--accent); background: var(--accent); color: #fff; }

/* the confidence gate â€” asked BEFORE the score, or a guess reads as knowledge */
body.tutor .pk-gq { font-size: 12.5px; line-height: 1.4; color: var(--ink); margin-top: 12px; }
body.tutor .pk-gs { font-size: 10px; line-height: 1.5; color: var(--muted); margin: 4px 0 10px; max-width: 52ch; }
body.tutor .pk-conf { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: 8px; }
body.tutor .pk-cf {
  min-width: 0; text-align: center;
  padding: 9px 9px 10px; border-radius: 10px;
  border: 1px solid rgba(150, 116, 60, 0.24);
  background: #fffdf7;
  transition: border-color 0.3s var(--write), background 0.3s var(--write);
}
body.tutor .pk-cf > b { display: block; font-size: 17px; line-height: 1.1; }
body.tutor .pk-cf i { display: block; margin-top: 3px; font-size: 11px; font-style: normal; font-weight: 700; color: var(--ink); }
body.tutor .pk-cf small { display: block; font-size: 9px; color: var(--muted); }
body.tutor .pk-cf.on { border-color: var(--accent); background: var(--accent-tint); box-shadow: 0 0 0 1px var(--accent) inset; }

/* the verdict: one ring, one sentence, and the miss made tappable.
   --pct has to be REGISTERED or the conic sweep would jump from 0 to done in a
   single frame â€” an unregistered custom property is not an animatable type. */
@property --pct {
  syntax: "<number>";
  inherits: false;
  initial-value: 0;
}
body.tutor .pk-hero {
  display: flex; align-items: center; gap: 13px;
  margin: 9px 0 8px; padding: 10px 12px;
  border: 1px solid rgba(47, 107, 79, 0.26);
  border-radius: 11px;
  background: linear-gradient(180deg, #eef6f0, #e6f0e8);
}
body.tutor .pk-ring {
  flex: none; position: relative;
  display: grid; place-items: center; align-content: center;
  width: 62px; height: 62px; border-radius: 50%;
  background: conic-gradient(var(--green) calc(var(--pct, 0) * 1%), rgba(47, 107, 79, 0.16) 0);
  transition: --pct 0.9s var(--write);
}
body.tutor .pk-ring::before {
  content: ""; position: absolute; inset: 6px;
  border-radius: 50%; background: #f2f8f4;
}
body.tutor .pk-ring b, body.tutor .pk-ring small { position: relative; }
body.tutor .pk-ring b { font-family: var(--display); font-size: 20px; font-weight: 600; color: var(--green-deep); line-height: 1; }
body.tutor .pk-ring small { font-size: 8.5px; color: var(--green); }
body.tutor .pk-hc { min-width: 0; }
body.tutor .pk-hc > i { font-size: 9px; font-style: normal; font-weight: 800; letter-spacing: 0.1em; text-transform: uppercase; color: var(--green); }
body.tutor .pk-hc > b { display: block; font-family: var(--display); font-size: 14px; font-weight: 600; color: var(--ink); }
body.tutor .pk-hc > em { display: block; margin-top: 2px; font-size: 10px; font-style: normal; color: var(--muted); }
body.tutor .pk-hc em .ok { color: var(--green-deep); font-weight: 700; }
body.tutor .pk-hc em .rev { color: #a4503d; font-weight: 700; }
body.tutor .pk-cp {
  display: inline-block; margin-top: 6px;
  padding: 2px 8px 3px; border-radius: 999px;
  background: rgba(255, 255, 255, 0.7);
  border: 1px solid rgba(47, 107, 79, 0.22);
  font-size: 9px; color: var(--muted);
}
body.tutor .pk-cp b { color: var(--ink-2); }
body.tutor .pk-rev {
  display: flex; align-items: center; gap: 9px;
  padding: 7px 10px 8px; border-radius: 9px;
  border: 1px solid rgba(207, 115, 97, 0.4);
  background: rgba(207, 115, 97, 0.08);
}
body.tutor .pk-rev > b {
  flex: none; display: grid; place-items: center;
  width: 18px; height: 18px; border-radius: 50%;
  background: #a4503d; color: #fff; font-size: 9.5px; font-weight: 800;
}
body.tutor .pk-rev > span { flex: 1; min-width: 0; }
body.tutor .pk-rev strong { display: block; font-size: 11px; font-weight: 700; color: var(--ink); }
body.tutor .pk-rev small { display: block; font-size: 9px; color: var(--muted); }
body.tutor .pk-rev svg { flex: none; width: 14px; height: 14px; color: #a4503d; }
body.tutor .pk-rv {
  display: block; margin-top: 7px; padding: 8px 10px 9px;
  border: 1px solid rgba(150, 116, 60, 0.24);
  border-radius: 9px; background: #fffdf7;
}
body.tutor .pk-rvh { display: block; font-size: 11px; color: var(--ink); }
body.tutor .pk-rvh i {
  display: inline-block; margin-right: 6px;
  font-size: 8.5px; font-style: normal; font-weight: 800;
  letter-spacing: 0.06em; color: #a2937b;
}
body.tutor .pk-rvh em {
  display: inline-block; margin-right: 6px;
  padding: 1px 7px 2px; border-radius: 999px;
  background: rgba(207, 115, 97, 0.13); color: #a4503d;
  font-size: 8.5px; font-weight: 700; font-style: normal;
}
body.tutor .pk-rva {
  display: block; margin-top: 4px; padding: 4px 8px 5px;
  border-radius: 6px; font-size: 9.5px; line-height: 1.35;
}
body.tutor .pk-rva.bad { background: rgba(207, 115, 97, 0.1); color: #8c4535; text-decoration: line-through; text-decoration-color: rgba(164, 80, 61, 0.5); }
body.tutor .pk-rva.key { background: var(--green-tint); color: var(--green-deep); font-weight: 600; }
body.tutor .pk-after { display: flex; align-items: center; gap: 7px; margin-top: 9px; }
body.tutor .pk-save, body.tutor .pk-again {
  display: inline-flex; align-items: center; gap: 5px;
  padding: 4px 10px 5px; border-radius: 999px;
  border: 1px solid rgba(150, 116, 60, 0.3);
  font-size: 9.5px; font-weight: 700; font-style: normal; color: var(--muted);
}
body.tutor .pk-save svg { width: 11px; height: 11px; }
body.tutor .pk-note { font-size: 9px; color: #a2937b; }
body.tutor .pk-after .pk-note { margin-left: auto; }
body.tutor .pk-note b { color: var(--accent-deep); }

/* â•â• 06 Â· school â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â• */
body.tutor .sc-stats { display: grid; grid-template-columns: repeat(4, minmax(0, 1fr)); gap: 7px; margin: 11px 0 10px; }
body.tutor .sc-st {
  min-width: 0; padding: 7px 9px 8px;
  border: 1px solid rgba(150, 116, 60, 0.2);
  border-radius: 9px; background: #fffdf7;
}
body.tutor .sc-st b { display: block; font-family: var(--display); font-size: 19px; font-weight: 600; color: var(--ink); line-height: 1.1; }
body.tutor .sc-st i { display: block; font-size: 9px; font-style: normal; color: var(--muted); margin-top: 1px; }
body.tutor .sc-st.due b { color: var(--rule-red-deep); }
body.tutor .sc-st.ok b { color: var(--green-deep); }
body.tutor .sc-tools { display: flex; align-items: center; gap: 8px; margin-bottom: 9px; }
body.tutor .sc-tools .lb2-find { margin-left: 0; flex: 1; min-width: 0; }
body.tutor .sc-chips { display: flex; gap: 4px; }
body.tutor .sc-chips i {
  display: inline-flex; align-items: center; gap: 4px;
  padding: 3px 8px 4px; border-radius: 999px;
  border: 1px solid rgba(150, 116, 60, 0.24);
  font-size: 9.5px; font-weight: 600; font-style: normal; color: var(--muted);
}
body.tutor .sc-chips i.on { border-color: var(--accent); background: var(--accent-tint); color: var(--accent-deep); }
body.tutor .sc-chips b { font-size: 8.5px; opacity: 0.7; }
body.tutor .sc-table { border: 1px solid rgba(150, 116, 60, 0.2); border-radius: 9px; overflow: hidden; background: #fffdf7; }
body.tutor .sc-tr {
  display: grid;
  grid-template-columns: minmax(0, 1fr) 62px 96px 78px;
  align-items: center; gap: 8px;
  padding: 8px 10px;
  border-top: 1px solid rgba(150, 116, 60, 0.14);
}
body.tutor .sc-tr:first-child { border-top: 0; }
body.tutor .sc-th { background: rgba(244, 236, 220, 0.7); padding: 5px 10px; }
body.tutor .sc-th i { font-size: 8.5px; font-weight: 800; letter-spacing: 0.1em; text-transform: uppercase; color: #a2937b; font-style: normal; }
body.tutor .sc-tr > i { font-size: 9.5px; font-style: normal; color: var(--muted); }
body.tutor .sc-nm { display: flex; align-items: center; gap: 7px; min-width: 0; }
body.tutor .sc-nm > span { min-width: 0; }
body.tutor .sc-nm b.dot { flex: none; }
body.tutor .sc-nm b:not(.dot) { display: block; font-size: 11px; color: var(--ink); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
body.tutor .sc-nm i { display: block; font-size: 9px; font-style: normal; color: var(--muted); }
body.tutor .sc-pill {
  display: inline-block; padding: 3px 8px 4px; border-radius: 999px;
  font-size: 9px; font-weight: 700;
  transition: background 0.35s var(--write), color 0.35s var(--write);
}
body.tutor .sc-pill.soon { background: #fbeedb; color: #97600d; }
body.tutor .sc-pill.mark { background: rgba(214, 196, 164, 0.4); color: #766952; }
body.tutor .sc-pill.done { background: var(--green-tint); color: var(--green-deep); }
body.tutor .sc-tr .n { text-align: right; }
body.tutor .sc-tr .n b { font-size: 12px; color: var(--ink); }
body.tutor .sc-fine {
  display: flex; align-items: center; gap: 6px;
  margin-top: 10px; font-size: 9.5px; color: var(--green-deep);
}
body.tutor .sc-fine svg { width: 12px; height: 12px; flex: none; }

/* â•â• 07 Â· notebooks â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â• */
body.tutor .ap-note .ap-main { position: relative; overflow: hidden; }
body.tutor .nb-pane { display: none; }
body.tutor .nb-pane.on { display: block; }
body.tutor .nb-ans { display: flex; gap: 8px; margin-top: 12px; }
body.tutor .nb-body {
  flex: 1; min-width: 0;
  padding: 9px 11px 10px;
  border: 1px solid rgba(150, 116, 60, 0.2);
  border-radius: 12px 12px 12px 3px;
  background: #fffdf7;
}
body.tutor .nb-body p { font-size: 12px; line-height: 1.55; color: var(--ink-2); }
body.tutor .nb-fig {
  margin-top: 9px; padding: 7px;
  border: 1px solid rgba(150, 116, 60, 0.22);
  border-radius: 8px; background: rgba(244, 236, 220, 0.5);
}
body.tutor .nb-tag {
  display: inline-block; margin-bottom: 5px;
  font-size: 8px; font-weight: 800; letter-spacing: 0.12em;
  color: var(--accent-deep);
}
body.tutor .nb-fig svg { display: block; width: 100%; height: auto; }
body.tutor .nb-fig .pg { fill: #fffdf7; }
body.tutor .nb-fig .cell { fill: #e2c48c; stroke: #a5651a; stroke-width: 1.4; }
body.tutor .nb-fig .bulb { fill: #fdf0cc; stroke: #a5651a; stroke-width: 1.4; }
body.tutor .nb-fig .wire { fill: none; stroke: #7c6444; stroke-width: 1.6; stroke-linecap: round; }
body.tutor .nb-fig .pv { fill: #7c6444; }
body.tutor .nb-fig figcaption { font-size: 9px; color: var(--muted); margin-top: 5px; }
body.tutor .nb-acts { display: flex; align-items: center; gap: 6px; margin-top: 9px; }
body.tutor .nb-grid { display: grid; grid-template-columns: repeat(4, minmax(0, 1fr)); gap: 9px; margin-top: 12px; }
body.tutor .nb-nb { min-width: 0; text-align: left; }
body.tutor .nb-cov {
  display: block; width: 82px; aspect-ratio: 3 / 4; border-radius: 4px 8px 8px 4px;
  box-shadow: inset 5px 0 0 rgba(0, 0, 0, 0.15), 0 2px 6px rgba(78, 56, 22, 0.16);
  transition: scale 0.4s var(--write);
}
body.tutor .nb-cov.sm { width: 20px; aspect-ratio: 3 / 4; flex: none; box-shadow: inset 3px 0 0 rgba(0, 0, 0, 0.15); }
body.tutor .nb-cov.c1 { background: linear-gradient(150deg, #e0a94a, #b06f1c); }
body.tutor .nb-cov.c2 { background: linear-gradient(150deg, #9fb8dd, #4d6fa0); }
body.tutor .nb-cov.c3 { background: linear-gradient(150deg, #d79c8c, #a9543f); }
body.tutor .nb-cov.c4 { background: linear-gradient(150deg, #a8c6ad, #4c7d5f); }
body.tutor .nb-nb b { display: block; font-size: 10.5px; color: var(--ink); margin-top: 6px; }
body.tutor .nb-nb > span { display: block; font-size: 9px; font-style: normal; color: var(--muted); }
body.tutor .nb-nb > span i { font-style: normal; }
body.tutor .nb-recent { display: grid; gap: 5px; }
body.tutor .nb-rc {
  display: flex; align-items: center; gap: 8px;
  padding: 6px 9px;
  border: 1px solid rgba(150, 116, 60, 0.18);
  border-radius: 8px; background: #fffdf7;
}
body.tutor .nb-rk {
  flex: none; display: grid; place-items: center;
  width: 20px; height: 20px; border-radius: 6px;
  color: #fff;
}
body.tutor .nb-rk svg { width: 12px; height: 12px; }
body.tutor .nb-rk.a { background: var(--accent); }
body.tutor .nb-rk.b { background: var(--green); }
body.tutor .nb-rk.c { background: var(--blue); }
body.tutor .nb-rc b { display: block; font-size: 10.5px; color: var(--ink); }
body.tutor .nb-rc em { display: block; font-style: normal; font-size: 9px; color: var(--muted); }
body.tutor .nb-dots { display: flex; gap: 3px; margin-top: 4px; }
body.tutor .nb-dots b { width: 5px; height: 5px; border-radius: 50%; }
body.tutor .nb-dots b.a { background: var(--accent); }
body.tutor .nb-dots b.b { background: var(--green); }
body.tutor .nb-dots b.c { background: var(--blue); }
body.tutor .nb-nb.pop .nb-cov { scale: 1.045; }
body.tutor .nb-sheet {
  position: absolute; left: 0; right: 0; bottom: 0;
  translate: 0 102%;
  padding: 12px 13px 14px;
  background: #fffdf7;
  border-top: 1px solid rgba(150, 116, 60, 0.3);
  border-radius: 14px 14px 0 0;
  box-shadow: 0 -10px 24px -14px rgba(78, 56, 22, 0.4);
  transition: translate 0.5s var(--write);
}
body.tutor .nb-sheet.up { translate: 0 0; }
body.tutor .nb-sh { display: block; font-family: var(--display); font-size: 14px; font-weight: 600; color: var(--ink); }
body.tutor .nb-row {
  display: flex; align-items: center; gap: 8px;
  padding: 6px 8px; border-radius: 8px;
  font-size: 11px; color: var(--ink-2);
  transition: background 0.3s var(--write);
}
body.tutor .nb-row em { margin-left: auto; font-style: normal; font-size: 9.5px; color: var(--muted); }
body.tutor .nb-row.pick { background: var(--accent-tint); }
body.tutor .nb-new {
  display: block; margin-top: 6px; padding: 6px 8px;
  border: 1px dashed rgba(150, 116, 60, 0.4);
  border-radius: 8px; font-size: 10.5px; color: var(--accent-deep); font-weight: 600;
}
body.tutor .nb-toast {
  position: absolute; left: 50%; bottom: 14px;
  translate: -50% 14px;
  padding: 6px 13px 7px; border-radius: 999px;
  background: var(--ink); color: #f6efdd;
  font-size: 10.5px;
  opacity: 0;
  transition: opacity 0.35s var(--write), translate 0.35s var(--write);
}
body.tutor .nb-toast b { color: #ffd684; }
body.tutor .nb-toast.show { opacity: 1; translate: -50% 0; }

/* â•â• 08 Â· map â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â• */
body.tutor .ap-map .ap-main { position: relative; }
body.tutor .mq-top { display: flex; align-items: center; gap: 8px; }
/* the field owns the dropdown's width â€” anchored to the whole pane it would
   sail over the graph's right half and hide the nodes it is about to open */
body.tutor .mq-fw { position: relative; flex: 1; min-width: 0; }
body.tutor .mq-find {
  width: 100%;
  display: inline-flex; align-items: center; gap: 6px;
  padding: 5px 10px 6px; border-radius: 999px;
  border: 1px solid rgba(150, 116, 60, 0.3);
  background: #fffdf7; font-size: 11px; color: var(--ink-2);
}
body.tutor .mq-find svg { width: 12px; height: 12px; color: #a2937b; flex: none; }
body.tutor .mq-find em { font-style: normal; }
/* the placeholder only exists while nothing has been typed â€” the typing verb
   empties the <em>, so :empty is the honest test, not a JS class */
body.tutor .mq-ph { display: none; font-weight: 400; color: #a2947c; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
body.tutor .mq-find em:empty ~ .mq-ph { display: block; }
body.tutor .mq-chips { display: flex; gap: 4px; }
body.tutor .mq-chips i {
  display: inline-flex; align-items: center; gap: 4px;
  padding: 3px 8px 4px; border-radius: 999px;
  border: 1px solid rgba(150, 116, 60, 0.24);
  font-size: 9.5px; font-weight: 600; font-style: normal; color: var(--muted);
}
body.tutor .mq-chips i.on { border-color: var(--accent); background: var(--accent-tint); color: var(--accent-deep); }

/* â”€â”€ the type-ahead: floats over the graph, never pushes it â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€ */
body.tutor .mq-res {
  position: absolute; z-index: 3;
  left: 0; right: 0; top: calc(100% + 5px);
  display: grid; gap: 2px;
  padding: 4px;
  border: 1px solid rgba(150, 116, 60, 0.34);
  border-radius: 10px; background: #fffdf7;
  box-shadow: 0 16px 30px -14px rgba(78, 56, 22, 0.5);
}
body.tutor .mq-r {
  display: flex; align-items: center; gap: 7px;
  padding: 5px 8px 6px; border-radius: 7px;
  font-size: 11px; color: var(--ink);
  transition: background 0.25s var(--write);
}
body.tutor .mq-r .g { font-size: 12px; line-height: 1; flex: none; }
body.tutor .mq-r .nm { font-weight: 600; }
body.tutor .mq-r > i { font-size: 9.5px; font-style: normal; color: var(--muted); }
body.tutor .mq-r em {
  margin-left: auto; padding: 2px 7px 3px; border-radius: 999px;
  font-size: 8.5px; font-weight: 700; font-style: normal;
  letter-spacing: 0.03em; text-transform: uppercase;
  border: 1px solid rgba(150, 116, 60, 0.26); color: var(--muted);
}
body.tutor .mq-r em.here { border-color: var(--accent); background: var(--accent-tint); color: var(--accent-deep); }
body.tutor .mq-r em.lk { color: #9c9184; background: rgba(150, 116, 60, 0.09); }
body.tutor .mq-r:first-child { background: var(--accent-tint); }

body.tutor .mq-body {
  flex: 1; min-height: 0;
  display: grid; grid-template-columns: minmax(0, 1fr) 208px; gap: 9px;
  margin-top: 9px;
}
body.tutor .mq-canvas {
  position: relative;
  min-width: 0; display: flex;
  padding: 7px 9px;
  border: 1px solid rgba(150, 116, 60, 0.2);
  border-radius: 10px;
  background: linear-gradient(160deg, #1e2c40, #2b3247);
}
body.tutor .mq-graph { display: block; width: 100%; height: 100%; }

/* the hop columns â€” a faint rule per lane, so the three lanes are a place */
body.tutor .mq-cols line { stroke: rgba(240, 231, 212, 0.1); stroke-width: 1; stroke-dasharray: 2 6; }
body.tutor .mq-lanes text {
  fill: rgba(240, 231, 212, 0.5);
  font-family: var(--font);
  font-size: 11px; font-weight: 800; letter-spacing: 1.3px;
  text-anchor: middle;
}
body.tutor .mq-more {
  fill: rgba(240, 231, 212, 0.3);
  font-family: var(--font); font-size: 9px; font-weight: 600;
  text-anchor: middle;
}

body.tutor .mq-edges path {
  fill: none; stroke: rgba(240, 231, 212, 0.24); stroke-width: 1.4;
  stroke-dasharray: 1; stroke-dashoffset: 0;
}
body.tutor .mq-edges path.q { stroke-dasharray: 3 4; }
body.tutor .mq-edges path.lk { stroke: rgba(240, 231, 212, 0.11); }
body.tutor .mq-edges.draw path.up {
  stroke: #ffd684; stroke-width: 2;
  animation: dkDraw 0.8s var(--write) both;
}
body.tutor .mq-edges.draw path.up:nth-of-type(2) { animation-delay: 0.16s; }
@keyframes dkDraw { from { stroke-dasharray: 200; stroke-dashoffset: 200; } to { stroke-dasharray: 200; stroke-dashoffset: 0; } }

/* â”€â”€ the nodes: a disc, a ring that carries state, a glyph, a name plate â”€â”€ */
body.tutor .mn .fill { fill: #34405a; }
body.tutor .mn .ring { fill: none; stroke: rgba(240, 231, 212, 0.32); stroke-width: 1.4; }
body.tutor .mn .gl {
  font-size: 13px; text-anchor: middle; dominant-baseline: central;
}
body.tutor .mn .gl.big { font-size: 19px; }
body.tutor .mn .star { font-size: 9px; text-anchor: middle; }
body.tutor .mn .nbg { fill: rgba(16, 25, 38, 0.62); }
body.tutor .mn .nm {
  fill: rgba(243, 236, 222, 0.86);
  font-family: var(--font); font-size: 9px; font-weight: 600;
  text-anchor: middle;
}
body.tutor .mn .nm.big { font-size: 10px; fill: #fff3da; font-weight: 700; }
body.tutor .mn.done .fill { fill: #34604c; }
body.tutor .mn.done .ring { stroke: #7fb392; stroke-width: 1.8; }
body.tutor .mn.ready .ring { stroke: rgba(255, 214, 132, 0.5); stroke-dasharray: 3 3; }
body.tutor .mn.lock .fill { fill: #2c3346; }
body.tutor .mn.lock .ring { stroke: rgba(240, 231, 212, 0.14); }
body.tutor .mn.lock .nm { fill: rgba(243, 236, 222, 0.4); }
body.tutor .mn .gb circle { fill: #f0e7d4; }
body.tutor .mn .gb text {
  fill: #2b3247; font-family: var(--font); font-size: 9px; font-weight: 800;
  text-anchor: middle; dominant-baseline: central;
}
/* the chosen idea: dark until the search lands on it, then it lights */
body.tutor .mn.here .halo { fill: rgba(255, 214, 132, 0.14); opacity: 0; transition: opacity 0.5s var(--write); }
body.tutor .mn.here .fill { fill: #6b5220; transition: fill 0.45s var(--write); }
body.tutor .mn.here .ring { stroke: rgba(255, 214, 132, 0.5); stroke-width: 2; transition: stroke 0.45s var(--write), filter 0.45s var(--write); }
body.tutor .mn.here.lit .halo { opacity: 1; }
body.tutor .mn.here.lit .fill { fill: #cf8517; }
body.tutor .mn.here.lit .ring { stroke: #ffd684; filter: drop-shadow(0 0 7px rgba(255, 214, 132, 0.8)); }
body.tutor .mn.next .fill { fill: #34405a; transition: fill 0.5s var(--write); }
body.tutor .mn.next .ring { stroke: rgba(255, 214, 132, 0.4); transition: stroke 0.5s var(--write); }
body.tutor .mn.next.up .fill { fill: #7a642f; }
body.tutor .mn.next.up .ring { stroke: #ffd684; stroke-width: 1.8; }

/* why a greyed node is grey â€” the app says it, so this says it too. It starts
   at 38% so the left column's "drag to explore" count stays readable under it,
   and the graph keeps its bottom row above y=305 in viewBox units to clear it. */
body.tutor .mq-why {
  position: absolute; left: 38%; right: 10px; bottom: 8px;
  padding: 5px 9px 6px; border-radius: 8px;
  background: rgba(15, 23, 36, 0.92);
  border: 1px solid rgba(255, 214, 132, 0.28);
  font-size: 9.5px; line-height: 1.35; color: rgba(243, 236, 222, 0.82);
}
body.tutor .mq-why b { color: #ffd684; font-weight: 700; }

/* â”€â”€ the panel â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€ */
body.tutor .mq-panel {
  align-self: stretch; min-height: 0; overflow: hidden;
  display: flex; flex-direction: column;
  padding: 9px 10px 10px;
  border: 1px solid rgba(150, 116, 60, 0.28);
  border-radius: 10px;
  background: #fffdf7;
  box-shadow: 0 12px 26px -16px rgba(78, 56, 22, 0.5);
  opacity: 0; translate: 12px 0;
}
body.tutor .mq-panel.in {
  opacity: 1; translate: 0 0;
  transition: opacity 0.4s var(--write), translate 0.4s var(--write);
}
body.tutor .mq-sub {
  display: flex; align-items: center; gap: 5px;
  font-size: 8px; font-weight: 800; letter-spacing: 0.09em; color: #a2937b;
}
body.tutor .mq-ttl {
  display: flex; align-items: baseline; gap: 5px; margin-top: 2px;
  font-family: var(--display); font-size: 13.5px; font-weight: 600; color: var(--ink);
}
body.tutor .mq-ttl span { font-size: 12px; }
body.tutor .mq-desc { font-size: 9px; line-height: 1.4; color: var(--muted); margin: 3px 0 5px; }
body.tutor .mq-when { font-size: 9px; line-height: 1.35; color: var(--ink-2); padding: 5px 7px; border-radius: 6px; background: rgba(244, 236, 220, 0.7); }
body.tutor .mq-when b { color: var(--accent-deep); }
body.tutor .mq-lab {
  display: flex; align-items: center; gap: 5px; margin: 6px 0 3px;
  font-size: 8.5px; font-weight: 800; letter-spacing: 0.11em; text-transform: uppercase; color: #a2937b;
}
body.tutor .mq-lab i {
  font-size: 8px; font-style: normal; font-weight: 700; letter-spacing: 0.02em;
  text-transform: none; color: #b3a68e;
}
body.tutor .mq-lab i::before { content: "Â· "; }
body.tutor .mq-li {
  display: flex; align-items: center; gap: 5px;
  margin-bottom: 2.5px; padding: 2px 6px 3px; border-radius: 7px;
  border: 1px solid rgba(150, 116, 60, 0.22);
  font-size: 9px; line-height: 1.25; color: var(--muted);
}
body.tutor .mq-li > b { font-size: 10px; font-weight: 400; flex: none; }
body.tutor .mq-li em {
  margin-left: auto; flex: none;
  font-size: 8px; font-style: normal; font-weight: 700; color: #b3a68e;
}
body.tutor .mq-li.done { border-color: rgba(47, 107, 79, 0.34); background: var(--green-tint); color: var(--green-deep); }
body.tutor .mq-li.done em { color: var(--green); }
body.tutor .mq-li.next { border-color: rgba(207, 133, 23, 0.38); background: var(--accent-tint); color: var(--accent-deep); }
body.tutor .mq-li.next em { color: var(--accent-deep); }
body.tutor .mq-li.lk { opacity: 0.6; }
body.tutor .mq-cta { display: flex; align-items: center; gap: 6px; margin-top: auto; padding-top: 7px; }
body.tutor .mq-panel .ap-btn { font-size: 9px; padding: 4px 9px 5px; }
body.tutor .mq-ghost {
  padding: 4px 9px 5px; border-radius: 999px;
  border: 1px solid rgba(150, 116, 60, 0.32);
  font-size: 9px; font-weight: 700; font-style: normal; color: var(--muted);
  white-space: nowrap;
}

/* â”€â”€ the legend, the app's own "how to read this" â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€ */
body.tutor .mq-legend {
  display: flex; align-items: center; flex-wrap: wrap; gap: 4px 11px;
  margin-top: 8px; padding-top: 8px;
  border-top: 1px dashed rgba(150, 116, 60, 0.3);
}
body.tutor .mq-legend > b {
  font-size: 8.5px; font-weight: 800; letter-spacing: 0.11em;
  text-transform: uppercase; color: #a2937b;
}
body.tutor .mq-legend i {
  display: inline-flex; align-items: center; gap: 5px;
  font-size: 9.5px; font-style: normal; color: var(--muted);
}
body.tutor .mq-legend .sw {
  width: 9px; height: 9px; border-radius: 50%;
  border: 1.4px solid rgba(150, 116, 60, 0.4); background: #efe7d6;
}
body.tutor .mq-legend .sw.done { background: #34604c; border-color: #7fb392; }
body.tutor .mq-legend .sw.here { background: #cf8517; border-color: #e8ae4e; }
body.tutor .mq-legend .sw.next { background: #f6e6c4; border-color: #cf8517; }
body.tutor .mq-legend .sw.lock { background: #ded4c1; border-color: #c2b69f; }
body.tutor .mq-legend .ln { width: 15px; height: 0; border-top: 1.6px solid #a2937b; }
body.tutor .mq-legend .ln.d { border-top-style: dashed; }

/* â•â• 09 Â· pip â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â• */
body.tutor .ap-pip .ap-main { padding: 0; position: relative; overflow: hidden; color: #f3ead6; }
body.tutor .pip-world { position: absolute; inset: 0; width: 100%; height: 100%; }
body.tutor .pip-stars circle { fill: #ffeec2; opacity: 0.75; }
body.tutor .pip-hill.far { fill: #2b3a52; }
body.tutor .pip-hill.near { fill: #1f2c3f; }
body.tutor .pip-trees path { fill: #16212f; }
body.tutor .pip-top { position: absolute; top: 10px; right: 11px; display: flex; gap: 6px; align-items: center; }
body.tutor .pip-glass, body.tutor .pip-orb {
  padding: 3px 9px 4px; border-radius: 999px;
  background: rgba(20, 28, 40, 0.5);
  border: 1px solid rgba(240, 231, 212, 0.22);
  font-size: 9.5px; font-weight: 700; font-style: normal;
  backdrop-filter: blur(3px);
}
body.tutor .pip-orb { position: relative; color: #ffd684; transition: scale 0.35s var(--write); }
body.tutor .pip-orb.pop { scale: 1.13; }
body.tutor .pip-orb b {
  position: absolute; top: 100%; right: 2px;
  font-size: 9px; color: #ffd684; white-space: nowrap;
  opacity: 0; translate: 0 6px;
  transition: opacity 0.4s var(--write), translate 0.6s var(--write);
}
body.tutor .pip-orb.pop b { opacity: 1; translate: 0 4px; }
body.tutor .pip-body {
  position: absolute; left: 50%; top: 44%;
  translate: -50% -50%;
  width: 58px;
  animation: dkFloat 4.6s ease-in-out infinite;
}
@keyframes dkFloat { 0%, 100% { translate: -50% -50%; } 50% { translate: -50% calc(-50% - 6px); } }
body.tutor .pip-body svg { width: 100%; height: auto; display: block; }
body.tutor .pip-body .cs { fill: rgba(255, 244, 214, 0.14); stroke: #ffd684; stroke-width: 2; }
body.tutor .pip-body .hd { fill: none; stroke: #ffd684; stroke-width: 2; stroke-linecap: round; }
body.tutor .pip-body .fl { fill: #ffd684; }
body.tutor .pip-body .ey { fill: #2a2113; }
body.tutor .pip-body .sm { fill: none; stroke: #2a2113; stroke-width: 1.6; stroke-linecap: round; }
body.tutor .pip-quests { position: absolute; left: 11px; top: 10px; display: grid; gap: 6px; width: 148px; }
body.tutor .pip-quest {
  display: block; padding: 7px 9px 8px; border-radius: 9px;
  background: rgba(20, 28, 40, 0.52);
  border: 1px solid rgba(240, 231, 212, 0.2);
  backdrop-filter: blur(3px);
  transition: border-color 0.4s var(--write), background 0.4s var(--write);
}
body.tutor .pip-quest b { display: block; font-size: 10px; }
body.tutor .pip-quest em { display: block; font-style: normal; font-size: 9px; opacity: 0.7; margin-top: 1px; }
body.tutor .pip-qbar { display: block; height: 3px; border-radius: 2px; background: rgba(240, 231, 212, 0.2); margin-top: 5px; overflow: hidden; }
body.tutor .pip-qbar b { display: block; height: 100%; background: #ffd684; transition: width 0.7s var(--write); }
body.tutor .pip-quest.done { border-color: rgba(127, 179, 146, 0.7); background: rgba(38, 74, 55, 0.6); }
body.tutor .pip-mem { position: absolute; left: 11px; bottom: 58px; display: flex; flex-direction: column; gap: 4px; align-items: flex-start; }
body.tutor .pip-mem i {
  padding: 3px 8px 4px; border-radius: 999px;
  background: rgba(20, 28, 40, 0.45);
  border: 1px solid rgba(240, 231, 212, 0.16);
  font-size: 9px; font-style: normal; opacity: 0.85;
}
body.tutor .pip-cap {
  position: absolute; left: 50%; translate: -50% 0; bottom: 56px;
  width: min(64%, 250px);
  padding: 8px 12px 9px; border-radius: 10px;
  background: rgba(16, 24, 34, 0.72);
  border: 1px solid rgba(240, 231, 212, 0.18);
  backdrop-filter: blur(4px);
  font-size: 11px; line-height: 1.5; text-align: center;
  opacity: 0;
}
body.tutor .pip-cap.in { opacity: 1; transition: opacity 0.35s var(--write); }
body.tutor .pip-cap em { font-style: normal; }
body.tutor .pip-dock { position: absolute; left: 0; right: 0; bottom: 12px; display: flex; align-items: center; justify-content: center; gap: 10px; }
body.tutor .pip-wave { display: flex; align-items: center; gap: 3px; height: 20px; }
body.tutor .pip-wave b {
  width: 3px; height: 5px; border-radius: 2px;
  background: rgba(255, 214, 132, 0.55);
  transition: height 0.2s var(--write);
}
body.tutor .pip-dock.live .pip-wave b { animation: dkWave 0.72s ease-in-out infinite; }
body.tutor .pip-dock.live .pip-wave b:nth-child(2) { animation-delay: 0.1s; }
body.tutor .pip-dock.live .pip-wave b:nth-child(3) { animation-delay: 0.2s; }
body.tutor .pip-dock.live .pip-wave b:nth-child(4) { animation-delay: 0.3s; }
@keyframes dkWave { 0%, 100% { height: 5px; } 50% { height: 17px; } }
body.tutor .pip-btn {
  display: grid; place-items: center;
  width: 38px; height: 38px; border-radius: 50%;
  background: #ffd684; color: #2a2113;
  box-shadow: 0 4px 14px rgba(255, 214, 132, 0.28);
  transition: scale 0.25s var(--write), box-shadow 0.35s var(--write);
}
body.tutor .pip-btn svg { width: 18px; height: 18px; }
body.tutor .pip-dock.live .pip-btn { scale: 0.93; box-shadow: 0 0 0 8px rgba(255, 214, 132, 0.18); }

/* â•â• 10 Â· progress â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â• */
body.tutor .pg-seg { margin-left: auto; font-size: 10px; }
body.tutor .pg-pane { display: none; }
body.tutor .pg-pane.on { display: block; }
body.tutor .pg-trail { display: grid; grid-template-columns: repeat(4, minmax(0, 1fr)); gap: 7px; }
body.tutor .pg-step {
  min-width: 0; text-align: center;
  padding: 9px 6px 10px;
  border: 1px solid rgba(150, 116, 60, 0.2);
  border-radius: 9px; background: #fffdf7;
  transition: border-color 0.4s var(--write), background 0.4s var(--write);
}
body.tutor .pg-lamp {
  display: grid; place-items: center; margin: 0 auto 5px;
  width: 26px; height: 26px; border-radius: 50%;
  background: rgba(214, 196, 164, 0.36); color: #b3a186;
  transition: background 0.4s var(--write), color 0.4s var(--write), box-shadow 0.4s var(--write);
}
body.tutor .pg-lamp svg { width: 15px; height: 15px; }
body.tutor .pg-step b { display: block; font-size: 10px; color: var(--ink); }
body.tutor .pg-step em { display: block; font-style: normal; font-size: 9px; color: var(--muted); }
body.tutor .pg-step.lit { border-color: rgba(207, 133, 23, 0.42); background: #fffaef; }
body.tutor .pg-step.lit .pg-lamp { background: var(--accent-tint); color: var(--accent); box-shadow: 0 0 12px rgba(207, 133, 23, 0.28); }
body.tutor .pg-two { display: grid; grid-template-columns: minmax(0, 1.05fr) minmax(0, 1fr); gap: 10px; }
body.tutor .pg-box { min-width: 0; }
body.tutor .pg-rings { display: flex; gap: 10px; }
body.tutor .pg-ring { flex: 1; min-width: 0; text-align: center; }
/* the dial sweep. A conic-gradient stop can only be animated if the custom
   property carrying it is registered with a type â€” an unregistered --fill is a
   string, and strings jump. */
@property --fill { syntax: "<number>"; inherits: false; initial-value: 0; }
body.tutor .pg-ring i {
  position: relative;
  display: grid; place-items: center; margin: 0 auto;
  width: 52px; height: 52px; border-radius: 50%;
  background: conic-gradient(var(--c) calc(var(--fill) * 1%), rgba(150, 116, 60, 0.16) 0);
  transition: --fill 1s var(--write);
}
body.tutor .pg-ring.fill i { --fill: var(--p); }
body.tutor .pg-ring i::before {
  content: ""; position: absolute; width: 40px; height: 40px; border-radius: 50%; background: var(--card);
}
body.tutor .pg-ring i b { position: relative; font-family: var(--display); font-size: 14px; font-weight: 600; color: var(--ink); }
body.tutor .pg-ring i b em { font-style: normal; font-size: 8px; }
body.tutor .pg-ring u { display: block; text-decoration: none; font-size: 9.5px; color: var(--muted); margin-top: 4px; }
body.tutor .pg-badges { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 5px; }
body.tutor .pg-bg {
  display: flex; align-items: center; gap: 5px; min-width: 0;
  padding: 5px 7px; border-radius: 8px;
  border: 1px solid rgba(150, 116, 60, 0.2);
  background: #fffdf7; font-size: 12px;
  opacity: 0; scale: 0.9;
  transition: opacity 0.35s var(--write), scale 0.35s var(--write);
}
body.tutor .pg-bg.in { opacity: 1; scale: 1; }
body.tutor .pg-bg.lock { opacity: 0.5; }
body.tutor .pg-bg.lock.in { opacity: 0.5; }
body.tutor .pg-bg i { font-size: 9px; font-style: normal; color: var(--muted); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
body.tutor .pg-bnote { font-size: 9px; color: #a2937b; margin-top: 5px; }
body.tutor .pg-eb { display: flex; align-items: center; gap: 5px; margin-top: 11px; font-size: 9px; font-weight: 800; letter-spacing: 0.11em; text-transform: uppercase; color: var(--accent-deep); }
body.tutor .pg-eb svg { width: 12px; height: 12px; }
body.tutor .pg-hd { font-family: var(--display); font-size: 17px; font-weight: 600; color: var(--ink); margin: 4px 0 8px; }
body.tutor .pg-hd i { font-family: var(--font); font-size: 9.5px; font-style: normal; font-weight: 500; color: var(--muted); margin-left: 6px; }
body.tutor .pg-para {
  font-size: 12px; line-height: 1.6; color: var(--ink-2);
  clip-path: inset(0 100% 0 0);
}
body.tutor .pg-para.in { clip-path: inset(0 -2% 0 0); transition: clip-path 1s var(--write); }
body.tutor .pg-para b { color: var(--ink); box-shadow: inset 0 -6px 0 rgba(207, 133, 23, 0.18); }
body.tutor .pg-tiles { display: grid; grid-template-columns: repeat(4, minmax(0, 1fr)); gap: 7px; margin-top: 11px; }
body.tutor .pg-tile {
  min-width: 0; padding: 7px 9px 8px;
  border: 1px solid rgba(150, 116, 60, 0.2);
  border-radius: 9px; background: #fffdf7;
}
body.tutor .pg-tile b { display: block; font-family: var(--display); font-size: 19px; font-weight: 600; color: var(--ink); line-height: 1.1; }
body.tutor .pg-tile i { display: block; font-size: 9px; font-style: normal; color: var(--muted); }
/* The grown-up brief carries the most in one frame and the app body is a fixed
   430px that CLIPS rather than scrolls, so everything here runs tighter: the
   whole reading â€” sentence, counts, mastery, misconceptions, tonight â€” has to
   land inside the frame. */
body.tutor .ap-prog .ap-main { padding-top: 9px; padding-bottom: 8px; }
body.tutor [data-g-pane="grown"] .pg-eb { margin-top: 3px; }
body.tutor [data-g-pane="grown"] .pg-hd { font-size: 15.5px; margin: 3px 0 5px; }
body.tutor [data-g-pane="grown"] .pg-para { font-size: 11.5px; line-height: 1.5; }
body.tutor [data-g-pane="grown"] .ap-lab { margin: 7px 0 4px; }
body.tutor [data-g-pane="grown"] .pg-tiles { margin-top: 7px; gap: 6px; }
body.tutor [data-g-pane="grown"] .pg-tile { padding: 5px 8px 6px; }
body.tutor [data-g-pane="grown"] .pg-tile b { font-size: 16px; }
body.tutor [data-g-pane="grown"] .pg-mis { gap: 4px; }
body.tutor [data-g-pane="grown"] .pg-m { padding: 4px 9px; gap: 7px; }
body.tutor [data-g-pane="grown"] .pg-m b { font-size: 13px; line-height: 1.25; }
body.tutor [data-g-pane="grown"] .pg-m em { font-size: 8.5px; }
body.tutor [data-g-pane="grown"] .pg-bars { gap: 3px; }
body.tutor [data-g-pane="grown"] .pg-tonight { margin-top: 7px; padding: 6px 9px; font-size: 10px; }
/* a quieter aside on a section label â€” "vs. the fortnight before" */
body.tutor .pg-lab-n {
  font-style: normal; font-weight: 500; text-transform: none;
  letter-spacing: 0; color: #a2937b; margin-left: 6px;
}
/* mastery by subject: a labelled rule per subject, and where it moved */
body.tutor .pg-bars { display: grid; gap: 5px; }
body.tutor .pg-bar {
  display: grid; align-items: center; gap: 8px;
  grid-template-columns: 52px minmax(0, 1fr) 30px 30px;
}
body.tutor .pg-bar u { text-decoration: none; font-size: 9.5px; color: var(--ink-2); }
body.tutor .pg-bar i {
  height: 6px; border-radius: 99px; overflow: hidden;
  background: rgba(150, 116, 60, 0.16);
}
body.tutor .pg-bar i b {
  display: block; height: 100%; width: 0; border-radius: 99px;
  background: linear-gradient(90deg, #e0a53f, var(--accent));
  transition: width 0.9s var(--write);
}
body.tutor .pg-bar.fill i b { width: calc(var(--p) * 1%); }
body.tutor .pg-bar em { font-style: normal; font-size: 9.5px; font-weight: 700; color: var(--ink); text-align: right; }
body.tutor .pg-bar s { text-decoration: none; font-size: 9px; font-weight: 700; text-align: right; }
body.tutor .pg-bar s.up { color: #2f6b4f; }
body.tutor .pg-bar s.flat { color: #a2937b; }
/* the one thing to do about all of it */
body.tutor .pg-tonight {
  display: flex; gap: 7px; margin-top: 9px;
  padding: 8px 10px; border-radius: 9px;
  border: 1px dashed rgba(207, 133, 23, 0.42);
  background: rgba(207, 133, 23, 0.06);
  font-size: 10.5px; line-height: 1.55; color: var(--ink-2);
  opacity: 0; translate: 0 5px;
  transition: opacity 0.45s var(--write), translate 0.45s var(--write);
}
body.tutor .pg-tonight.in { opacity: 1; translate: 0 0; }
body.tutor .pg-tonight svg { flex: none; width: 13px; height: 13px; margin-top: 1px; color: var(--accent); }
body.tutor .pg-tonight b { color: var(--ink); }
body.tutor .pg-mis { display: grid; gap: 6px; }
body.tutor .pg-m {
  display: flex; align-items: center; gap: 8px;
  padding: 7px 10px;
  border: 1px solid rgba(150, 116, 60, 0.2);
  border-radius: 9px; background: #fffdf7;
}
body.tutor .pg-m > i {
  flex: none; display: grid; place-items: center;
  width: 17px; height: 17px; border-radius: 50%;
  background: #fbeedb; color: #97600d;
  font-size: 10px; font-weight: 800; font-style: normal;
}
body.tutor .pg-m > span { flex: 1; min-width: 0; }
body.tutor .pg-m b { display: block; font-family: var(--hand); font-size: 15px; font-weight: 400; color: var(--pencil); }
body.tutor .pg-m em { display: block; font-style: normal; font-size: 9px; color: var(--muted); }
body.tutor .pg-m u { flex: none; text-decoration: none; font-size: 9.5px; font-weight: 700; color: var(--accent-deep); }

/* â”€â”€ narrow â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€ */
@media (max-width: 1040px) {
  body.tutor .dk { grid-template-columns: 1fr; }
  body.tutor .dk-index {
    flex-direction: row;
    gap: 6px;
    margin: 0 0 -1px;
    padding: 0 0 2px;
    overflow-x: auto;
    scrollbar-width: none;
  }
  body.tutor .dk-index::-webkit-scrollbar { display: none; }
  body.tutor .dk-group { display: none; }
  body.tutor .dk-tab {
    flex: none;
    grid-template-columns: auto;
    grid-template-rows: auto;
    border-radius: 10px 10px 0 0;
    border-right: 1px solid transparent;
    padding: 8px 13px 9px;
  }
  body.tutor .dk-no, body.tutor .dk-sub { display: none; }
  body.tutor .dk-tab[aria-selected="true"] { padding: 8px 13px 9px; box-shadow: 0 -3px 10px -6px rgba(78, 56, 22, 0.28); }
  body.tutor .dk-tab[aria-selected="true"]::after { top: auto; right: 0; left: 0; bottom: -1px; width: auto; height: 2px; }
  body.tutor .dk-run { left: 13px; right: 13px; bottom: 3px; }
  /* the strip scrolls â€” fade its trailing edge so that reads as a promise of
     more tabs rather than as a row that happens to stop at the screen */
  body.tutor .dk-index {
    -webkit-mask-image: linear-gradient(to right, #000 calc(100% - 34px), transparent);
    mask-image: linear-gradient(to right, #000 calc(100% - 34px), transparent);
  }
  body.tutor .dk-page { border-radius: 0 14px 14px 14px; padding: 16px 16px 15px; }
  body.tutor .dk-page::before { display: none; }
}
@media (max-width: 720px) {
  body.tutor .dk .ap { height: 468px; font-size: 11.5px; }
  body.tutor .dk .pc { min-height: 468px; padding: 26px 22px; }
  body.tutor .dk .pc-stamp { top: 14px; right: 14px; }
  body.tutor .dk .ap-rail { width: 30px; }
  body.tutor .hm-res, body.tutor .sc-stats, body.tutor .pg-tiles { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  /* the books row stays four across even here â€” the covers are book-sized now,
     and dropping to three wraps EVS onto a row of its own and pushes the
     Stories shelf out of the frame */
  body.tutor .nb-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  body.tutor .pg-two { grid-template-columns: 1fr; }
  body.tutor .pg-trail { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  /* the graph and its panel cannot sit side by side in a phone's width â€” stack
     them, drop the legend, and shed the panel's two most repeatable parts (the
     blurb, and the prerequisites the graph is already drawing on the left) so
     what's left ends at the CTA instead of being cut off mid-list */
  body.tutor .mq-body { grid-template-columns: 1fr; grid-template-rows: 176px minmax(0, 1fr); }
  body.tutor .mq-panel { translate: 0 8px; }
  body.tutor .mq-desc, body.tutor .mq-grp.bo { display: none; }
  body.tutor .mq-legend { display: none; }
  body.tutor .mq-chips i:nth-child(n + 3) { display: none; }
  body.tutor .sc-tr { grid-template-columns: minmax(0, 1fr) 84px; }
  body.tutor .sc-tr > i:nth-child(2), body.tutor .sc-th i:nth-child(2) { display: none; }
  body.tutor .dk-marg { font-size: 17px; }
  /* the activity's "5 minutes Â· things you already have" is decoration; on a
     phone it steals half the row and wraps the title into two lines */
  body.tutor .ac-head em { display: none; }
  /* a docked panel at 54% is a column of two-word lines on a phone â€” on this
     width the app shows the same drawer over the whole page */
  body.tutor .mk-panel { width: 100%; }
  body.tutor .mk-full { display: none; }
  body.tutor .mk-body.paneled .mk-page { padding-right: 13px; }
}

/* reduced motion: every scene parked on its finished state, tabs still switch */
html.no-motion body.tutor .dk * { transition: none !important; animation: none !important; }
html.no-motion body.tutor .dk .rise { opacity: 1; translate: none; }
/* `.in` matters even here: park() leaves the thinking rows hidden on purpose,
   and a blanket `display: flex` would park "reading your pageâ€¦" forever */
html.no-motion body.tutor .dk .ak2-turn.rise.in { display: flex; }
html.no-motion body.tutor .dk-run { display: none; }

/* â”€â”€ #doors Â· corrections where a shared selector reaches too far â”€â”€â”€â”€â”€â”€â”€â”€
   Each of these is a rule above catching an element it was never meant to:
   a thread turn that must not hold layout while hidden, a progress bar's
   filler <b> inheriting the card title's block metrics, a figure sitting in
   the same slot as the fake text rules. Fixing them at the source would mean
   uglier selectors in the readable blocks, so the corrections live here. */

/* hidden pieces of an answer must leave NO layout footprint â€” the bubble would
   otherwise arrive full-height and empty (same reason as .ak-turn upstream) */
body.tutor .dk .ak2-att.rise,
body.tutor .dk .ak2-guard.rise,
body.tutor .dk .ak2-acts.rise,
body.tutor .dk .ak2-deep.rise,
body.tutor .dk .ak2-offer.rise,
body.tutor .dk .ac-head.rise,
body.tutor .dk .ac-opt.rise,
body.tutor .dk .ac-foot.rise,
body.tutor .dk .ak2-fig.rise,
body.tutor .dk .ac-q.rise,
body.tutor .dk .ak2-say.bot p.rise { display: none; }
body.tutor .dk .ak2-att.rise.in,
body.tutor .dk .ak2-guard.rise.in,
body.tutor .dk .ak2-deep.rise.in,
body.tutor .dk .ac-head.rise.in,
body.tutor .dk .ac-opt.rise.in,
body.tutor .dk .ac-foot.rise.in,
body.tutor .dk .ak2-acts.rise.in { display: flex; animation: tcMsgIn 0.4s var(--write); }
body.tutor .dk .ak2-offer.rise.in { display: inline-flex; animation: tcMsgIn 0.4s var(--write); }
body.tutor .dk .ak2-fig.rise.in,
body.tutor .dk .ac-q.rise.in,
body.tutor .dk .ak2-say.bot p.rise.in { display: block; animation: tcMsgIn 0.4s var(--write); }
/* the story node's question and the answer panel's turns: same rule, same
   reason â€” a hidden turn that still holds layout makes the panel open on a
   full-height empty column, and rollEnd() then measures a thread that isn't
   there yet */
body.tutor .dk .lb2-q.rise,
body.tutor .dk .lb2-fb.rise,
body.tutor .dk .lb2-sub.rise,
body.tutor .dk .mk-quote.rise,
body.tutor .dk .mk-asked.rise,
body.tutor .dk .mk-think.rise,
body.tutor .dk .mk-ans.rise,
body.tutor .dk .mk-u.rise,
body.tutor .dk .mk-acts.rise { display: none; }
body.tutor .dk .lb2-q.rise.in,
body.tutor .dk .lb2-fb.rise.in,
body.tutor .dk .mk-quote.rise.in,
body.tutor .dk .mk-asked.rise.in,
body.tutor .dk .mk-ans.rise.in { display: block; animation: tcMsgIn 0.4s var(--write); }
body.tutor .dk .lb2-sub.rise.in,
body.tutor .dk .mk-think.rise.in { display: inline-flex; animation: tcMsgIn 0.4s var(--write); }
body.tutor .dk .mk-u.rise.in,
body.tutor .dk .mk-acts.rise.in { display: flex; animation: tcMsgIn 0.4s var(--write); }

/* the "drawing thisâ€¦" plate is a cover, not a reveal: it must be gone, not
   merely transparent, or its sweep keeps running over the finished picture */
body.tutor .dk .ak2-gen.rise:not(.in) { display: none; }
html.no-motion body.tutor .dk .ak2-gen { display: none; }

/* solid edges: `stroke-dasharray: 1` would draw them as 1px dots */
body.tutor .mq-edges path { stroke-dasharray: none; }
body.tutor .mq-edges path.q { stroke-dasharray: 3 4; }

/* fillers and dots are not card titles */
body.tutor .hm-prog b,
body.tutor .pip-qbar b { margin: 0; font-size: 0; }
body.tutor .nb-dots b { display: inline-block; margin: 0; font-size: 0; }
body.tutor .lb2-cov b { margin: 0; }

/* the reader's figure shares the fake-text slot; it is not a text rule */
body.tutor .lb2-col > i.lb2-fig { height: auto; background: none; border-radius: 0; }

/* a story card's title must not be clipped to one line by the book-card rule */
body.tutor .lb2-st b { white-space: normal; }

/* ============================================================================
   v26 â€” the sheet opens the way Marble's does. Four moves, one beat:

   Â· AIR. The coverage strip drops well clear of the tiles, and the ground
     runs on long after it before the sheet's edge arrives â€” the emptiness is
     what makes the page-turn an event instead of a margin.
   Â· TAPE. A piece of red tape straddles the sheet's top edge, half on the
     band, half on the paper, like the sheet is pinned down.
   Â· WELCOME. The sheet opens on one centred "Welcome to Lantern." â€” big
     serif, red full stop, no eyebrow, no lede.
   Â· THE RAIL STARTS ON THE BAND. The track's cap and first stretch hang
     above the sheet edge, so the marker reads as already resting on the
     ground when the page turns â€” Marble's rod over the ocean.

   The rail bleed needs .trail to stop clipping vertically. overflow-y goes
   `visible`, and the x axis is clipped on its own with `clip` â€” that pair is
   legal where visible/hidden is not, and x-clipping is what keeps the
   serpentine's lane from ever minting a horizontal scrollbar. The bottom cap
   headroom drawTrail leaves (see motion.js) is now belt-and-braces, not
   load-bearing.
   ========================================================================= */
.cover-strip { margin-top: 110px; }
.mission-in { padding-bottom: clamp(190px, 24vh, 280px); }

.trail { overflow-x: clip; overflow-y: visible; }

/* The rail's containing block moves from .trail-in to .trail â€” done by
   un-positioning .trail-in, NOT by reparenting the markup: the trail knobs
   (--trail-offset and friends) are declared on .trail-in and reach the rail
   by inheritance, which a DOM move would sever, silently resetting every
   media-query retune of them. With .trail as the box, the negative top
   measures from the SHEET's edge, so the cap sits up on the band and the
   rail runs down past the welcome to the steps. The 50% in the rail's left
   calc() refers to .trail now instead of .trail-in â€” both are full-bleed
   sections, so the lane doesn't move.

   The bleed is small on purpose: Marble's rod clears its seam by roughly
   one stroke width, no more. The path starts at y=4 inside the rail box and
   the round cap extends half a stroke past that, so with the 60px stroke a
   -30px top puts the cap's crown ~56px above the edge â€” peeking, not
   towering. (First cut was -190px; the rod read as a red pillar planted in
   the band rather than a marker resting over the page edge.) */
.trail-in { position: static; }
.trail-rail { top: -30px; }

.trail-tape {
  position: absolute; z-index: 5;
  top: -15px; right: 12%;
  width: 118px; height: 34px;
  border-radius: 9px;
  transform: rotate(-3.5deg);
  /* the fold: the part lying up on the band reads a step darker, like the
     tape bends over the sheet's edge. The gradient lives in element space,
     so the crease follows the tape's own tilt, not the viewport's. */
  background: linear-gradient(to bottom, #b9271b 0 42%, var(--accent) 42%);
  box-shadow: 0 2px 8px rgba(60, 30, 15, 0.18);
  pointer-events: none;
}

.trail-head { padding: clamp(110px, 15vh, 170px) 0 clamp(78px, 8vh, 124px); }
.trail-head .trail-welcome { max-width: none; margin: 0; text-align: center; }
.trail-head .trail-welcome h2 {
  font-size: clamp(56px, 9vw, 128px);
  line-height: 1.04; letter-spacing: -0.028em;
  margin-bottom: 0;
}
.trail-head .tw-dot { color: var(--accent); }

@media (max-width: 720px) {
  /* same one-stroke-width peek, scaled to the 16px mobile stroke */
  .trail-rail { top: -14px; }
  .trail-tape { width: 92px; height: 27px; top: -12px; right: 9%; }
}

/* =========================================================================
   WHO'S BEHIND LANTERN  (#team)

   Two claims: who built it, and where they built things before. Both are
   centred on one column because there is nothing to compare here — a grid
   would invite the eye to read the three ex-employers as a ranking.

   LinkedIn is the real asset (logos/linkedin.svg). Microsoft and Atlassian
   are redraws: the four squares and the two chevrons are exact geometry, but
   the wordmarks are set in the site's own face rather than Segoe / Charlie
   Display. They read as the brands at a glance, which is all a "worked here
   before" row needs to do; if a brand kit ever arrives, drop the supplied
   files in beside linkedin.svg and swap the markup.
   ========================================================================= */

.prior-label {
  font-family: var(--mono);
  font-size: 11px; font-weight: 700; letter-spacing: 0.16em;
  text-transform: uppercase; color: var(--muted);
  text-align: center; margin: 0 0 30px;
}

.prior-row {
  list-style: none; margin: 0; padding: 0;
  display: flex; flex-wrap: wrap;
  align-items: center; justify-content: center;
  gap: 22px clamp(38px, 7vw, 76px);
}

/* The three marks carry different natural weights, so they are sized to look
   equal rather than measure equal — Atlassian's caps read widest, Microsoft's
   mixed case lightest, LinkedIn's plate heaviest per square inch. */
.prior-logo {
  display: inline-flex; align-items: center; gap: 11px;
  line-height: 1;
}
.pl-word { font-weight: 700; letter-spacing: -0.02em; white-space: nowrap; }

.pl-word.ms { font-size: 27px; font-weight: 500; color: #58585A; }

/* LinkedIn is the supplied asset rather than a redraw, and it is drawn for a
   white ground — the "in" bug's counters are knocked out, so on the cream band
   the tint would show through the letterforms. Hence the plate: a white
   rectangle sized by padding, not a fixed box, so it tracks the logo at every
   breakpoint. The padding is uneven on purpose — the lockup's own clear space
   is tighter above and below the cap height than beside it. */
.li-lockup {
  background: #fff;
  border-radius: 8px;
  padding: 11px 18px;
  box-shadow: 0 1px 2px rgba(26, 26, 24, 0.05);
}
.li-lockup img { display: block; width: 132px; height: auto; }

.atl-mark { flex: none; }
.pl-word.atl {
  font-size: 21px; color: #172B4D;
  letter-spacing: 0.01em; font-weight: 700;
}

@media (max-width: 720px) {
  .prior-row { gap: 26px 34px; }
  .pl-word.ms { font-size: 23px; }
  .li-lockup { padding: 9px 14px; }
  .li-lockup img { width: 108px; }
  .pl-word.atl { font-size: 18px; }
}
