/* lesson.css — the lesson's own styles (CONTRACT_V2 §2, §5; CONTRACT.md §9.3–9.4 for the scoping rules).

   SCOPED: every rule sits under .vx, the <div class="vx" id="vx"> that holds the rail, the stage and
   the controls, so the same stylesheet works in two hosts — the standalone page (index.html +
   standalone.css) and a topic page on hersimaths.co.uk, whose global stylesheet also styles section,
   p, li, h2, h3, a and :focus-visible. Where a site rule would change the lesson (section padding
   and borders, the 38rem cap on p and li) the .vx rule below states the value, so both hosts render
   alike. Nothing here reaches outside .vx; page-level rules live in standalone.css.
   Audits: tools/audit_css_scope.py, tools/check_topic.py (palette).

   TWO PALETTES, ONE JOB EACH. The chrome around the stage (rail, cue band, buttons) follows the site:
   Newsreader for the cues, IBM Plex Sans for UI, one accent #7cb0ee for what you can press and for the
   current tab and dot, graphite surfaces and lines, 3–4 px radii. INSIDE the stage only the maths
   palette appears: black ground #000000, a #4fc3f7, b #ffb74d, result #a5d6a7, ink #e0e0e0, guides
   #5c5c66 (opacity variants allowed). The stage is a film frame on the site's ground, not a panel.

   ONE SCENE AT A TIME. lesson.js adds .vx-player to the root and hides every scene but the current
   one; without JavaScript that class is never added and every scene is a normal block, stacked in
   order, with the part titles drawn from data-title. */

.vx {
  /* the maths palette: inside the stage only */
  --vx-ground: #000000;
  --vx-a: #4fc3f7;
  --vx-b: #ffb74d;
  --vx-res: #a5d6a7;
  --vx-lab: #e0e0e0;
  --vx-guide: #5c5c66;
  --vx-lab-dim: rgba(224, 224, 224, 0.62);   /* marks and hints on the black stage: 8.9:1 */
  /* the site's tokens (assets/style.css): the chrome */
  --vx-bg: #161a20;
  --vx-surface: #1d222a;
  --vx-raised: #232932;
  --vx-line: #2c333d;
  --vx-heading: #e4e3de;
  --vx-text: #cfd2d6;
  --vx-muted: #99a1ac;
  --vx-accent: #7cb0ee;
  --vx-accent-strong: #9cc4f5;
  --vx-ink: #12161c;
  --vx-control: rgba(124, 176, 238, 0.6);    /* outline of a button: 3.4–3.6:1 */
  --vx-accent-wash: rgba(124, 176, 238, 0.12);
  --vx-serif: Newsreader, Georgia, "Times New Roman", serif;
  --vx-sans: "IBM Plex Sans", system-ui, -apple-system, "Segoe UI", Roboto, sans-serif;
  --vx-radius: 4px;
  /* set by lesson.js: --vx-top = height of a sticky .masthead above the lesson (0 without one) */
  --vx-top: 0px;

  display: block;
  width: 100%;
  max-width: none;                           /* the stage is as wide as the site's .shell */
  color-scheme: dark;
  color: var(--vx-text);
  font-family: var(--vx-sans);
  font-size: 1.0625rem;
  line-height: 1.7;
  overflow-wrap: break-word;
  text-align: left;
}
.vx, .vx *, .vx *::before, .vx *::after { box-sizing: border-box; }
.vx img, .vx svg, .vx video, .vx canvas, .vx iframe { max-width: 100%; }
.vx p { margin: 0 0 0.85rem; max-width: none; }   /* the site caps p and li at 38rem */
.vx li { max-width: none; }
.vx b, .vx strong { font-weight: 600; }
.vx a { color: var(--vx-accent); text-decoration-thickness: 1px; text-underline-offset: 3px; }
.vx a:hover { color: var(--vx-accent-strong); }
.vx em { font-style: normal; font-weight: 600; }   /* only upright faces are loaded */

.vx .sr-only {
  position: absolute !important; width: 1px; height: 1px; margin: -1px; padding: 0;
  overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; border: 0;
}

/* the site gives every <section> padding, a scroll margin and `section + section` a border: the
   parts and scenes are layout only */
.vx .part, .vx .scene { margin: 0; padding: 0; border: 0; background: none; }
.vx .scene { scroll-margin-top: calc(var(--vx-top) + 10px); }
.vx .vx-stage-wrap { margin: 0; padding: 0; }
/* ONE SCENE AT A TIME: the Player marks the current scene; the rest are not drawn */
.vx.vx-player .scene:not(.is-current) { display: none; }

/* ---------- video embed slot (the topic's own video, before the rail) ---------- */
.vx .video-embed {
  margin: 0 0 1.4rem; aspect-ratio: 16 / 9; overflow: hidden;
  border-radius: var(--vx-radius); background: var(--vx-surface); border: 1px solid var(--vx-line);
}
.vx .video-embed[hidden] { display: none; }
.vx .video-embed iframe { display: block; width: 100%; height: 100%; border: 0; }

/* ---------- rail: part tabs + scene dots ---------- */
.vx .rail { margin: 0 0 10px; }
.vx .rail:empty { display: none; }
.vx .part-picker-label { display: none; }
@media (max-width: 760px) {
  .vx .part-picker-label { display: block; font-size: 0.9rem; color: var(--vx-muted); }
  .vx .part-picker { display: block; width: 100%; min-height: 44px; margin: 5px 0 12px; padding: 8px; color: var(--vx-text); background: var(--vx-surface); border: 1px solid var(--vx-control); font: inherit; }
  .vx .rail .tabs { display: none !important; }
}
.vx .rail .tabs {
  display: flex; gap: 2px;
  overflow-x: auto; scrollbar-width: none; -webkit-overflow-scrolling: touch;
  border-bottom: 1px solid var(--vx-line);
}
.vx .rail .tabs::-webkit-scrollbar { display: none; }
.vx .rail .tab {
  flex: 0 0 auto; display: inline-flex; align-items: center; gap: 8px;
  min-height: 44px; margin: 0 0 -1px; padding: 0 12px;
  font: 600 0.875rem/1.2 var(--vx-sans); color: var(--vx-muted);
  background: none; border: 0; border-bottom: 2px solid transparent; border-radius: 0;
  white-space: nowrap; cursor: pointer; -webkit-tap-highlight-color: transparent;
  transition: color 140ms ease, border-color 140ms ease;
}
.vx .rail .tab .n { color: var(--vx-accent); font-variant-numeric: tabular-nums; }
.vx .rail .tab:hover { color: var(--vx-heading); }
.vx .rail .tab[aria-current="true"] { color: var(--vx-heading); border-bottom-color: var(--vx-accent); }
.vx .rail .dots { display: flex; justify-content: center; gap: 2px; padding: 2px 0 0; }
.vx .rail .dot {
  position: relative; width: 32px; height: 32px; margin: 0; padding: 0;
  background: none; border: 0; cursor: pointer; -webkit-tap-highlight-color: transparent;
}
.vx .rail .dot::before {
  content: ""; position: absolute; left: 50%; top: 50%;
  width: 9px; height: 9px; margin: -4.5px 0 0 -4.5px;
  border-radius: 50%; border: 1.5px solid var(--vx-muted);
  transition: background 140ms ease, border-color 140ms ease, transform 140ms ease;
}
.vx .rail .dot:hover::before { border-color: var(--vx-heading); }
.vx .rail .dot[aria-current="true"]::before {
  background: var(--vx-accent); border-color: var(--vx-accent); transform: scale(1.25);
}

/* ---------- the stage: one 16:9 black frame, as wide as the column ---------- */
.vx .stage {
  position: relative; width: 100%; aspect-ratio: 16 / 9;
  background: var(--vx-ground); color: var(--vx-lab);
  border-radius: var(--vx-radius); overflow: clip;
  container-type: inline-size;               /* stage type scales with the frame (cqw) */
  font-size: 1rem; line-height: 1.45;
  /* a sideways swipe on the stage turns the scene (lesson.js reads the pointer events); the
     browser keeps vertical panning, so the page still scrolls from a touch on the stage */
  touch-action: pan-y pinch-zoom;
}
.vx .stage > video, .vx .stage > canvas {
  position: absolute; inset: 0; display: block; width: 100%; height: 100%;
  background: var(--vx-ground); border: 0; border-radius: 0;
}
.vx .stage > video { object-fit: contain; }
.vx .stage > canvas {
  /* a swipe that does not start on a handle turns the scene or scrolls the page; VX.Canvas takes
     the touch (touchstart preventDefault) only when it starts within a handle's grab radius */
  touch-action: pan-y pinch-zoom;
  user-select: none; -webkit-user-select: none; -webkit-tap-highlight-color: transparent;
}
/* a mounted canvas (role=application) draws its own focus ring on the picture */
.vx .stage > canvas[role="application"]:focus-visible { outline: none; }
.vx .scene[data-kind="watch"] .stage { cursor: auto; touch-action: auto; }

/* the replay badge (after the clip) / the Play button (reduced motion or refused autoplay) */
.vx .stage .stage-btn {
  position: absolute; right: 12px; bottom: 12px; z-index: 2;
  display: none; align-items: center; gap: 8px;
  min-height: 44px; margin: 0; padding: 0 14px 0 12px;
  font: 600 0.875rem/1 var(--vx-sans); color: var(--vx-lab);
  background: rgba(0, 0, 0, 0.62); border: 1px solid rgba(224, 224, 224, 0.4); border-radius: 999px;
  cursor: pointer; -webkit-tap-highlight-color: transparent;
  transition: background 140ms ease, border-color 140ms ease;
}
.vx .stage .stage-btn .ico { font-size: 1.3em; line-height: 1; }
.vx .stage .stage-btn:hover { background: rgba(0, 0, 0, 0.85); border-color: var(--vx-lab); }
.vx .stage.is-ended .stage-btn, .vx .stage.is-idle .stage-btn { display: inline-flex; }
.vx .stage.is-idle .stage-btn.is-play {
  right: auto; bottom: auto; left: 50%; top: 50%; transform: translate(-50%, -50%);
  min-height: 56px; padding: 0 26px 0 22px; font-size: 1.0625rem;
}
@media (max-width: 640px) {
  .vx .stage .stage-btn { right: 8px; bottom: 8px; min-height: 40px; padding: 0 12px 0 10px; }
  .vx .stage .stage-btn .lbl { display: none; }   /* the glyph alone on a small frame; aria-label stays */
  .vx .stage.is-idle .stage-btn.is-play .lbl { display: inline; }
}

/* ---------- controls: Back · cue band · Next, directly under the stage ---------- */
.vx .controls {
  display: grid; grid-template-columns: auto minmax(0, 1fr) auto; grid-template-areas: "back cue next";
  align-items: center; gap: 8px 14px; margin: 10px 0 0;
}
.vx .controls .back { grid-area: back; }
.vx .controls .next { grid-area: next; }
.vx .controls .cue {
  grid-area: cue; position: relative;
  display: flex; flex-direction: column; align-items: center; justify-content: center;
  min-height: calc(2 * 1.35em + 16px);       /* two lines: the stage never jumps between cues */
  padding: 4px 6px 12px; text-align: center; text-wrap: balance;
  font-family: var(--vx-serif); font-weight: 400; font-optical-sizing: auto;
  font-size: clamp(1.25rem, 1.05rem + 0.55vw, 1.5rem); line-height: 1.35;
  color: var(--vx-heading);
}
.vx .controls .cue .cue-text { display: block; max-width: 40em; }
.vx .controls .cue.is-prompt { color: var(--vx-muted); font-size: 1.125rem; }
.vx .controls .cue .tick { color: var(--vx-res); font-weight: 700; margin-right: 0.4em; }
.vx .controls .cue .katex { font-size: 1em; }
.vx .controls .cue .cue-line {
  position: absolute; left: 22%; right: 22%; bottom: 0; height: 2px; border-radius: 1px;
  background: var(--vx-line); overflow: hidden;
}
.vx .controls .cue .cue-line::after {
  content: ""; position: absolute; left: 0; top: 0; bottom: 0;
  width: calc(var(--p, 0) * 100%); background: var(--vx-accent);
}
.vx .controls .cue .cue-line[hidden] { display: none; }
@media (max-width: 640px) {
  .vx .controls { grid-template-columns: 1fr 1fr; grid-template-areas: "cue cue" "back next"; gap: 6px 10px; }
  .vx .controls .back { justify-self: start; }
  .vx .controls .next { justify-self: end; }
}

/* the scene's own row under the cue band: Replay · Show me / Show all / Reset · the marks tally */
.vx .scene-bar {
  display: flex; flex-wrap: wrap; align-items: center; justify-content: center; gap: 8px 10px;
  margin: 10px 0 0;
}
.vx .scene-bar:empty { display: none; }
.vx .scene-bar .tally {
  margin-left: 6px; color: var(--vx-muted); font-size: 0.95rem; font-variant-numeric: tabular-nums;
}
.vx .scene.is-complete .scene-bar .tally { color: var(--vx-heading); }

/* buttons: the site's outline button; pressed/ready = filled accent with dark ink */
.vx .controls button, .vx .scene-bar .vx-btn, .vx .toolbar button {
  display: inline-flex; align-items: center; justify-content: center; gap: 0.5em;
  min-height: 44px; margin: 0; padding: 0 16px;
  font: 600 0.9375rem/1 var(--vx-sans); color: var(--vx-accent);
  background: transparent; border: 1px solid var(--vx-control); border-radius: 3px;
  cursor: pointer; -webkit-tap-highlight-color: transparent;
  transition: background 140ms ease, color 140ms ease, border-color 140ms ease;
}
.vx .controls button:hover, .vx .scene-bar .vx-btn:hover, .vx .toolbar button:hover {
  background: var(--vx-accent-wash); color: var(--vx-accent-strong);
}
.vx .controls button:active, .vx .scene-bar .vx-btn:active, .vx .toolbar button:active { transform: translateY(1px); }
.vx .controls button:disabled, .vx .scene-bar .vx-btn:disabled {
  opacity: 0.38; cursor: default; background: transparent; color: var(--vx-accent); transform: none;
}
.vx .controls .back, .vx .controls .next { min-width: 5.5em; }
.vx .controls .next.is-ready, .vx .controls .next.is-ready:hover {
  background: var(--vx-accent); color: var(--vx-ink); border-color: var(--vx-accent);
}
.vx .toolbar button[aria-pressed]::before {
  content: ""; width: 0.6em; height: 0.6em; border-radius: 50%;
  border: 1.5px solid currentColor; flex: 0 0 auto;
}
.vx .toolbar button[aria-pressed="true"], .vx .toolbar button[aria-pressed="true"]:hover {
  background: var(--vx-accent); color: var(--vx-ink); border-color: var(--vx-accent);
}
.vx .toolbar button[aria-pressed="true"]::before { background: currentColor; }

/* ---------- TRY: the widget's toolbar and readout, a compact row under the cue band ---------- */
.vx .toolbar { display: flex; flex-wrap: wrap; align-items: center; justify-content: center; gap: 8px 12px; margin: 10px 0 0; }
.vx .toolbar label { display: inline-flex; align-items: center; gap: 8px; min-height: 44px; font-size: 0.95em; }
.vx .toolbar input[type="range"] { accent-color: var(--vx-accent); flex: 1 1 140px; min-width: 120px; max-width: 260px; min-height: 44px; margin: 0; }
.vx .toolbar .seg { display: inline-flex; border: 1px solid var(--vx-control); border-radius: 3px; }
.vx .toolbar .seg button { border: 0; border-radius: 0; }
.vx .toolbar .seg button:first-child { border-radius: 2px 0 0 2px; }
.vx .toolbar .seg button:last-child { border-radius: 0 2px 2px 0; }
.vx .toolbar .seg button + button { border-left: 1px solid var(--vx-control); }
.vx .toolbar .seg button:focus-visible { position: relative; z-index: 1; }
.vx .readout {
  display: flex; flex-wrap: wrap; align-items: center; justify-content: center; gap: 6px 18px;
  min-height: 40px; margin: 6px 0 0; padding: 2px; font-size: 1.05em; line-height: 1.6;
}
.vx .readout .katex { font-size: 1.15em; }
.vx .hint { margin: 6px 0 0; font-size: 0.88em; line-height: 1.6; color: var(--vx-muted); text-align: center; }
.vx.vx-player .scene > .task { display: none; }           /* the task line lives in the cue band */
@media (max-width: 640px) {
  /* a phone: 16:9 at 312 px is 176 px tall, too little for a picture you drag; the TRY frame is 4:3 */
  .vx .scene[data-kind="try"] .stage { aspect-ratio: 4 / 3; }
}

/* ---------- WATCH: cues ---------- */
.vx.vx-player .scene > .cues { display: block; }
.vx .scene > .cues {
  list-style: none; margin: 10px 0 0; padding: 0;
  font-family: var(--vx-serif); font-size: 1.125rem; line-height: 1.45; color: var(--vx-heading);
}
.vx .scene > .cues li { margin: 0; padding: 12px 0; border-top: 1px solid var(--vx-line); }
/* Notes stay visible and never follow a timer. On phones they precede the clip. */
.vx.vx-player .scene[data-kind="watch"].is-current {
  display: grid; grid-template-columns: minmax(0, 2fr) minmax(0, 3fr);
  grid-template-areas: "notes visual" "controls controls"; gap: 20px 28px; align-items: start;
}
.vx .scene[data-kind="watch"] > .stage { grid-area: visual; }
.vx .scene[data-kind="watch"] > .cues { grid-area: notes; margin: 0; }
.vx .scene[data-kind="watch"] > .controls { grid-area: controls; }
.vx .cue.is-empty { display: none; }
.vx .scene[data-kind="watch"] > .stage:focus-within { outline: 2px solid var(--vx-accent); outline-offset: 3px; }
@media (max-width: 760px) {
  .vx.vx-player .scene[data-kind="watch"].is-current {
    grid-template-columns: minmax(0, 1fr); grid-template-areas: "notes" "visual" "controls";
  }
}

/* ---------- CARD: the result, large, one sentence under it ---------- */
.vx .scene[data-kind="card"] .stage { display: grid; place-items: center; }
.vx .stage > .card {
  display: grid; grid-template-columns: minmax(0, 1fr); justify-items: center; align-content: center;
  gap: 3.5cqw; width: 100%; padding: 6cqw 6cqw; text-align: center;
}
.vx .stage > .card > svg { width: 30cqw; height: auto; }
.vx .stage > .card .result {
  margin: 0; max-width: 100%; overflow-x: auto; overflow-y: hidden;
  font-size: clamp(1.75rem, 4.3cqw, 2.7rem); line-height: 1.3; color: var(--vx-lab);
}
.vx .stage > .card .result .katex-display { margin: 0; padding: 0.15em 0; }
.vx .stage > .card .line {
  margin: 0; max-width: 44em;
  font-family: var(--vx-sans); font-size: clamp(1.05rem, 2.1cqw, 1.375rem); line-height: 1.45; color: var(--vx-lab);
}
@supports selector(:has(a)) {
  @media (min-width: 641px) {
    .vx .stage > .card:has(> svg) { grid-template-columns: minmax(0, 1fr) auto; justify-items: start; text-align: left; }
    .vx .stage > .card:has(> svg) > svg { grid-row: 1 / span 2; grid-column: 2; align-self: center; }
  }
}
@media (max-width: 640px) {
  /* a phone: the card is one column — result, sentence, picture under them. The result's desktop
     floor (1.75rem) overflows a 360 px stage by ~22 px on the longest of them, so the phone gets
     its own clamp; overflow-x stays as the safety net for anything longer still. */
  .vx .stage > .card { gap: 4.5cqw; padding: 5cqw 4.5cqw; }
  .vx .stage > .card .result { font-size: clamp(1.2rem, 6.6cqw, 1.9rem); }
  .vx .stage > .card .line { font-size: clamp(0.95rem, 3.9cqw, 1.15rem); }
  .vx .stage > .card > svg { width: 62cqw; }
}

/* ---------- DO: question top-left, diagram beside it, the working column grows under the question ---------- */
.vx .scene[data-kind="do"] .stage {
  display: grid; grid-template-columns: minmax(0, 1fr); grid-template-rows: auto minmax(0, 1fr);
  align-content: start; row-gap: 1.4cqw;
  padding: 3cqw 3.6cqw;
}
.vx .stage > .q { grid-column: 1; grid-row: 1; margin: 0; font-size: clamp(1.05rem, 2.3cqw, 1.5rem); line-height: 1.5; color: var(--vx-lab); }
.vx .stage > .q p { margin: 0 0 0.45em; display: flow-root; }
.vx .stage > .q p:last-child { margin-bottom: 0; }
.vx .stage > .q .q-part { padding-left: 1.6em; text-indent: -1.6em; }
.vx .stage > .q .marks { float: right; margin-left: 1em; color: var(--vx-lab-dim); font-size: 0.88em; white-space: nowrap; text-indent: 0; }
/* the working column: it scrolls inside the stage only when it overflows the frame (≥ 641 px,
   where the stage keeps its 16:9 height); lesson.js keeps the newest step in view */
.vx .stage > .steps {
  grid-column: 1; grid-row: 2; position: relative; align-self: stretch; min-height: 0;
  overflow-y: auto; overscroll-behavior: contain; scrollbar-width: thin;
  list-style: none; margin: 0; padding: 0;
  font-size: clamp(1.05rem, 2.3cqw, 1.55rem); line-height: 1.45; color: var(--vx-lab);
}
@media (min-width: 641px) {
  /* question + working in the left 60 %, the diagram in the right 40 %, no taller than the frame.
     min-height: 0 drops the aspect-ratio box's automatic minimum, so the stage stays 16:9 and the
     working scrolls instead of stretching the frame. The diagram's height comes from its grid
     area (definite, so max-height: 100 % resolves), not from an auto row (see the phone rule). */
  .vx .scene[data-kind="do"] .stage { min-height: 0; column-gap: 2.6cqw; }
  .vx .stage > .diagram { grid-column: 2; grid-row: 1 / span 2; align-self: start; width: 100%; height: auto; max-height: 100%; margin: 0; }
}
@supports selector(:has(a)) {
  @media (min-width: 641px) {
    /* the second column only exists when there IS a diagram. Applied to every DO, it left the
       right 43 % of the stage black on the questions that are pure algebra — measured at 50 % of
       the stage unused on 7 of differentiation's questions, which is R2's "dead margins". */
    .vx .scene[data-kind="do"] .stage:has(> .diagram) {
      grid-template-columns: minmax(0, 57fr) minmax(0, 43fr);
    }
  }
}
@supports not selector(:has(a)) {
  @media (min-width: 641px) {
    .vx .scene[data-kind="do"] .stage { grid-template-columns: minmax(0, 57fr) minmax(0, 43fr); }
  }
}
/* lesson.js sets these flags while the column overflows; the mask says where it is cut off */
.vx .stage > .steps.is-clipped-top { mask-image: linear-gradient(to bottom, transparent 0, #000 22px); }
.vx .stage > .steps.is-clipped-bottom { mask-image: linear-gradient(to top, transparent 0, #000 22px); }
.vx .stage > .steps.is-clipped-top.is-clipped-bottom {
  mask-image: linear-gradient(to bottom, transparent 0, #000 22px, #000 calc(100% - 22px), transparent 100%);
}
.vx .stage > .steps li {
  /* hanging indent the other way up: the first line sits at 0.8em and any continuation at 2em, so a
     wrapped equation reads as one working line rather than two. lesson.js moves the operator to the
     front of the continuation, so that line opens with the = or + it belongs to. */
  position: relative; margin: 0; padding: 0.28em 0 0.28em 2em; text-indent: -1.2em;
  border-left: 2px solid transparent; display: flow-root;
}
.vx .stage > .steps li .katex-display, .vx .stage > .steps li .words { text-indent: 0; }
.vx .stage > .steps li .katex-display { margin: 0; }
/* <span class="words">: a phrase set BETWEEN two display equations — "when equal in length and
   direction" — which inherits the equation's size unless it is told otherwise. It is prose: the
   sans face, half the size of the maths it joins, dimmed, on its own line. */
.vx .stage > .steps li .words,
.vx .stage > .card .result .words,
.vx .stage > .card .words {
  display: block; margin: 0.42em auto; font-family: var(--vx-sans);
  font-size: max(0.5em, 0.88rem); line-height: 1.45; color: var(--vx-lab-dim); max-width: 26em;
}
.vx .stage > .steps li .words { font-size: max(0.72em, 0.88rem); margin: 0.3em 0; }
.vx .stage > .steps li .mark {
  float: right; margin: 0.15em 0 0 1em; font-family: var(--vx-sans); font-size: 0.78em; color: var(--vx-lab-dim);
  font-variant-numeric: tabular-nums;
}
.vx .stage > .steps li.answer { margin-top: 0.3em; padding-top: 0.4em; }
.vx.vx-player .stage > .steps li[data-step]:not(.is-shown) { display: none; }
.vx .stage > .steps li.is-shown { animation: vx-step-in 320ms ease-out; }
.vx .stage > .steps li.answer.is-shown { color: var(--vx-res); border-left-color: var(--vx-res); }
@keyframes vx-step-in { from { opacity: 0; transform: translateY(6px); } to { opacity: 1; transform: none; } }
@media (max-width: 640px) {
  /* a phone: one column — question, diagram, working — in normal flow, not a grid: Chrome sizes
     an auto grid row holding only an <svg> to 0 px, and the working would paint over the picture */
  .vx .scene[data-kind="do"] .stage { display: block; padding: 14px 14px 16px; }
  /* full bleed: cancelling the stage's 14 px padding puts the diagram at the container's own
     width, so a 13 px label in a 320-unit viewBox renders at 13 px or above instead of ~9 px */
  .vx .stage > .diagram {
    display: block; width: calc(100% + 28px); max-width: none; margin: 12px -14px 4px;
  }
  .vx .stage > .steps { margin-top: 8px; }
}

/* diagrams: letters match KaTeX (bold upright vector names, italic point letters); layers reveal
   with the steps — a stroked outline draws itself on (lesson.js sets its dash), the rest fades in */
.vx .diagram { display: block; height: auto; }
.vx .diagram text { font-family: KaTeX_Main, "Times New Roman", Times, serif; }
.vx.vx-player .diagram .layer[data-step]:not(.is-shown) { visibility: hidden; }
.vx .diagram .layer[data-step] [data-draw] { transition: stroke-dashoffset 700ms ease-out; }
.vx .diagram .layer[data-step] [data-fade] { opacity: 0; transition: opacity 320ms ease 260ms; }
.vx .diagram .layer[data-step].is-shown [data-fade] { opacity: 1; }

/* ---------- maths ---------- */
/* fallback if katex.min.css fails to load: KaTeX emits MathML + HTML; keep the MathML for screen readers only */
.vx .katex .katex-mathml {
  position: absolute; clip: rect(1px, 1px, 1px, 1px); padding: 0; border: 0;
  height: 1px; width: 1px; overflow: hidden;
}
.vx .katex-display { overflow-x: auto; overflow-y: hidden; padding: 0.3em 0; margin: 0.6em 0; }
.vx .katex-display > .katex { white-space: normal; }
/* inline maths + the punctuation after it stay on one line (lesson.js gluePunctuation) */
.vx .m-punct, .vx .m-punct .m-tail { white-space: nowrap; }
.vx .m-punct .katex { white-space: normal; }

/* ---------- focus / motion ---------- */
.vx :focus-visible { outline: 2px solid var(--vx-accent); outline-offset: 2px; }
@media (prefers-reduced-motion: reduce) {
  .vx *, .vx *::before, .vx *::after { animation: none !important; transition: none !important; }
}

/* ---------- without JavaScript: every scene, stacked, in order ---------- */
.vx:not(.vx-player) .controls { display: none; }
.vx.vx-player .fallback-note, .vx.vx-player .part-heading { display: none; }
.vx:not(.vx-player) .scene[data-kind="try"] { display: none; }
.vx .fallback-note { padding: 1rem; border: 1px solid var(--vx-line); border-radius: var(--vx-radius); }
.vx:not(.vx-player) .part { padding: 0 0 1rem; }
.vx:not(.vx-player) .part-heading {
  display: block; margin: 1.6rem 0 0.8rem;
  font-family: var(--vx-serif); font-weight: 500; font-size: 1.6rem; line-height: 1.2; color: var(--vx-heading);
}
.vx:not(.vx-player) .scene { margin: 0 0 1.5rem; padding: 0 0 1.5rem; border-bottom: 1px solid var(--vx-line); }
.vx:not(.vx-player) .scene > video {
  display: block; width: 100%; height: auto; aspect-ratio: 16 / 9;
  background: var(--vx-ground); border-radius: var(--vx-radius);
}
.vx:not(.vx-player) .scene > canvas, .vx:not(.vx-player) .toolbar, .vx:not(.vx-player) .readout { display: none; }
.vx:not(.vx-player) .scene > .card {
  padding: 1.5rem 1rem; text-align: center; background: var(--vx-ground); color: var(--vx-lab); border-radius: var(--vx-radius);
}
.vx:not(.vx-player) .scene > .card .result { font-size: 1.75rem; margin: 0 0 0.5rem; }
.vx:not(.vx-player) .scene > .card .line { margin: 0; }
.vx:not(.vx-player) .scene > .q { margin: 0 0 0.6rem; }
.vx:not(.vx-player) .scene > .diagram { width: min(100%, 360px); margin: 0.5rem 0; }
.vx:not(.vx-player) .scene > .steps { list-style: none; margin: 0.5rem 0 0; padding: 0 0 0 0.8em; border-left: 2px solid var(--vx-line); }
.vx:not(.vx-player) .scene > .steps li.answer { color: var(--vx-res); }
.vx:not(.vx-player) .scene > .task { font-family: var(--vx-serif); font-size: 1.125rem; color: var(--vx-heading); }

/* ---------- print: all written notes, cards, questions and answers ---------- */
@media print {
  @page { margin: 16mm; }
  body { background: #ffffff; color: #000000; }
  .masthead, footer, .skip, main > .band { display: none !important; }
  body * { color: #000000 !important; background-color: transparent !important; border-color: #777777 !important; }
  .vx { font-size: 11pt; }
  .vx .rail, .vx .controls, .vx .scene-bar, .vx .video-embed, .vx .toolbar, .vx .readout, .vx .task,
  .vx .fallback-note, .vx video, .vx canvas, .vx .stage .stage-btn { display: none !important; }
  .vx.vx-player .scene { display: block !important; }
  .vx.vx-player .scene[data-kind="try"], .vx .scene[data-kind="try"],
  .vx .scene[data-kind="watch"] .stage { display: none !important; }
  .vx.vx-player .part-heading, .vx .part-heading {
    display: block; margin: 1.2rem 0 0.6rem; break-after: avoid;
    font-family: var(--vx-serif); font-size: 1.4rem; color: #000000;
  }
  .vx .scene { margin: 0 0 1rem; padding: 0 0 1rem; border-bottom: 1px solid #5c5c66; break-inside: avoid; }
  .vx .stage { background: none; aspect-ratio: auto !important; border-radius: 0; color: #000000; display: block !important; padding: 0 !important; height: auto !important; min-height: 0 !important; max-height: none !important; overflow: visible !important; }
  .vx .stage > .card, .vx .stage > .q, .vx .stage > .steps { color: #000000; padding: 0; }
  .vx .stage > .card .result, .vx .stage > .card .line { color: #000000; }
  .vx .stage > .diagram { width: min(100%, 320px); margin: 0.5rem 0; }
  .vx .steps li[data-step], .vx .cues li { display: list-item !important; opacity: 1 !important; visibility: visible !important; }
  .vx .steps { border-left: 2px solid #777777; padding-left: 1em !important; }
  .vx .stage > .steps { overflow: visible !important; mask-image: none !important; }
  .vx .q, .vx .steps, .vx .card .line { font-size: 11pt !important; }
  .vx .card .result { font-size: 16pt !important; }
  .vx .stage > .card { display: block; }
  .vx .card svg { max-width: 75mm; max-height: 40mm; }
  /* light-grey diagram ink is invisible on paper; the guide colour prints as drawn */
  .vx .diagram [fill="#e0e0e0"] { fill: #000000; }
  .vx .diagram [stroke="#e0e0e0"] { stroke: #000000; }
  .vx .diagram .layer[data-step] { visibility: visible !important; }
  .vx .diagram .layer[data-step] [data-fade] { opacity: 1 !important; }
  .vx .diagram .layer[data-step] [data-draw] { stroke-dashoffset: 0 !important; }
}
