/* ═══════════════════════════════════════════════════════════════════════════
   SHAReT Module 1 — module CSS

   Men in Mind brand layer + the RTL engine, then only what this module adds.
   Branding is recorded as TBD; if it changes, rebind the tokens in the block
   below rather than touching any of the 65 screens.
   ═══════════════════════════════════════════════════════════════════════════ */
@import url('./mim-design-system.css');
@import url('mim-gp-shared.css');

/* The engine declares its own :root AFTER the design system, so the two AA
   purples must be re-stated here or the engine's win. They are the accessible
   pair: --mim-purple itself fails AA as text on white and is decorative only. */
:root {
  --mim-purple-aa:      #8463F8;   /* AA large: 24px+, or 18.66px bold */
  --mim-purple-aa-dark: #5D3CC9;   /* AA normal: small text and links */

  /* The activity library (_components/rtl-activities.css) is brand-agnostic:
     every colour is var(--accent, #5B4FD0) and friends, so WITHOUT these it
     renders in its own fallback purple - close enough to look like a mistake
     rather than a choice, and a different purple from everything around it.

     --mim-purple-aa-dark, not --mim-purple-aa: the library's buttons are
     white text on the accent at 15px, and the AA-large purple is only good
     from 24px up. Same rule that caught the tile labels at 4.1:1.
     The dark-mode pair is the engine's own night purple. */
  --accent:             var(--mim-purple-aa-dark);
  --on-accent:          #FFFFFF;
  --accent-on-dark:     #CDBCFF;
  --on-accent-dark:     #1A1A1A;
}

/* ── ONE TYPE SCALE ────────────────────────────────────────
   Anu, 30 September, twice: "many different font types and sizes ... I am
   still not happy with the typography."

   Measured, the worst screen carried EIGHT sizes - 12, 13, 15, 16, 17, 20,
   23, 28.8 - because every component that arrived brought its own: the arrow
   list at 16, the reveal face at 17, the toolkit at 15, a heading at 28.8, a
   caption at 13. None of them was wrong on its own; together they read as
   noise, and each new component made it worse.

   So there are now FIVE steps and nothing is allowed its own. Each one has a
   job, and a component picks the step that matches its job rather than a
   number that happened to look right in isolation.

     --t-micro  12px   eyebrows, captions, attributions, counts
     --t-body   16px   body copy, list items, everything read at length
     --t-lead   23px   the lead-in, quotes, reveal faces - one voice up
     --t-sub    29px   section headings inside a screen
     --t-title  44px   the screen title (dividers have their own, larger)

   Faces stay at two: Lora carries the title and anything that is a person
   speaking; Overpass carries everything else. */
:root {
  --t-micro: calc(12px * var(--fs-scale));
  --t-body:  calc(16px * var(--fs-scale));
  --t-lead:  var(--fs-subhead);
  --t-sub:   calc(29px * var(--fs-scale));
  --t-title: var(--fs-h1);
}

/* micro */
.eyebrow, .mo-quote figcaption, .refs li, .rtl-toolkit__count,
.rtl-act__hint, .evidence__label, .image-quote__attr,
.mo-diagram figcaption { font-size: var(--t-micro); }

/* body */
.body, .list li, .mo-arrows li, .keypoint-list li, .mo-reasons li,
.evidence__paper, .rtl-toolkit__text, .rtl-recall__got,
.mo-reveal__body .body { font-size: var(--t-body); }

/* lead: one voice up - the lead-in, a person speaking, a reveal's face */
.section-lead, .mo-quote blockquote, .image-quote__text,
.reflect__prompt, .mo-reveal__face, .keypoint,
.rtl-act__prompt, .rtl-act__stem, .mo-beat { font-size: var(--t-lead); }

/* sub: a heading inside a screen */
.heading, .mo-reasons li > strong:first-child,
.mo-arrows--airy li > strong:first-child { font-size: var(--t-sub); }

/* title */
.title { font-size: var(--t-title); }

/* Anu, 30 September: on screen 22 "can we distribute the words in the title
   better? Most of it can be in the first row." Something upstream sets
   text-wrap: balance on every title, which EVENS the lines - measured, 395px
   and 407px inside a 700px column, so both stopped 300px short. Balance is
   right for a centred divider and wrong for a left-aligned sentence, which
   should fill its first line and carry the remainder. `pretty` fills but
   still avoids leaving one word stranded on the last line. */
.screen-inner > .title { text-wrap: pretty; }

/* The divider keeps its own, set on .screen--part .title further down: a
   lesson opening is meant to outrank everything a content screen can do. */

/* ── the reading column ────────────────────────────────────
   Anu, 30 September: "the text alignment looks very left aligned. I want it
   distributed more evenly."

   Measured, it was: the engine's .screen-inner is 820px, but every text block
   inside it is capped at --measure (700px) and aligned left. So a title ran
   the full 772px while the paragraph under it stopped 244px short, and the
   whole screen read as a column pushed against its own left edge.

   Fixed by making the two agree rather than by centring the paragraphs, which
   would have indented them away from the title they belong to. The column
   comes in to the measure, so every block starts AND ends on the same two
   edges and the page's own margins carry the rest. Line length is unchanged,
   which is the part that matters for reading.

   Screens that are a picture rather than a page opt out below. */
.screen-inner { max-width: calc(var(--measure) + 2 * var(--sp-5)); }

/* Full-bleed by design: the hero screens, the image-quote, the outcomes band
   and the covers screen all size themselves and would be squeezed by the
   reading column. */
.screen--hero > .screen-inner,
.screen--multimedia > .screen-inner,
.screen--covers .screen-inner,
.screen--lo .screen-inner,
.screen--objectives .screen-inner { max-width: 1100px; }

/* NOT the nav screen. It is a column of text cards, so it belongs in the
   reading column like any other page of reading - widened to 1100px its
   720px tutorial sat hard left with 526px of empty screen beside it. */
.nav-tutorial { margin-inline: auto; }

/* Screen 7's title is centred over full-width tiles, so its one paragraph -
   capped at the measure inside a 1100px column - was hanging 200px left of
   everything else. Centred as a block; the text itself stays left. */
.screen--covers .body, .screen--covers .section-lead { margin-inline: auto; }

/* The kit caps .section-lead at 38ch, which at the subhead size is 552px
   against the column's 700px. So the right edge stepped 700 / 552 / 700 down
   a screen - title full width, the lead stopping well short, body full width
   again - and that ragged edge is most of what reads as "left aligned".
   The lead now shares the column. ~48ch at that size is still a short measure
   for a lead-in, so nothing is lost in readability. */
.section-lead { max-width: var(--measure); }
/* Anu, 26 October 2026, on screen 51: *"the word sexual can come on the first
   line instead of the second so there is less white space."*
   MEASURED, and it is systematic rather than one screen. --fs-subhead is
   calc(clamp(20px, 1.6vw, 26px)...), so the lead GROWS with the viewport
   while --measure stays fixed at 700px - the wider the screen, the fewer
   words fit on a line and the more ragged the right edge. At 1512px the lead
   was 24.2px and filled 662 of 700px on its longest line. Capping the growth
   at 22px leaves the 20px floor alone, so narrow screens are unchanged and
   only the wide ones gain words. */
.section-lead { font-size: calc(clamp(20px, 1.45vw, 22px) * var(--fs-scale)); }

/* ...and the other half of the same fault, which is the one Anu is actually
   looking at. The in-course TEXT SIZE control scales the type through
   --fs-scale, and --measure stayed at a flat 700px - so at x1.3 the lead
   renders at 26px in a 700px column, about 40 characters, and "sexual" drops
   to the second line. Reproduced at 1512px x1.3 before changing anything.
   Shrinking the type back would fight the accessibility control, so the
   COLUMN grows with it instead and the characters-per-line stay roughly
   constant. min(..., 100%) keeps it inside the viewport on a phone, where
   700px is already wider than the screen. One token, so the figures that
   share --measure stay aligned with the text. */
:root { --measure: min(calc(700px * var(--fs-scale, 1)), 100%); }

/* ── the image-quote screen ──────────────────────────────────
   Plain black, no texture. Anu, 30 September 2026. The kit's .screen--multimedia
   lays the photocopy grain over black for every expressive screen; behind a
   single large photograph it just competes with it, and the grain reads as
   noise around the frame rather than as a surface. */
.screen--multimedia[data-type="image-quote"] {
  background-color: #000;
  background-image: none;
}

/* Anu, 30 September: the accordion text "is too small". The kit sets
   .evidence__paper at 15px against a 16px body, which reads as a footnote on
   a screen whose whole content is inside the accordion. Matched to body size;
   the link keeps its own small uppercase treatment. */
.evidence__paper { font-size: calc(16px * var(--fs-scale)); line-height: 1.6; }

/* ── fewer voices on a screen ──────────────────────────────────
   Anu, 30 September: screen 22 "has many different font types and sizes which
   makes it look odd." Measured, it carried SIX sizes - 12 / 13 / 16 / 19 /
   23 / 44 - and the 19px quote sat between the 16px body and the 23px lead,
   three steps inside a few centimetres.

   The quote now takes the lead's own size, so Lora says two things on a
   screen (the title, and a person speaking) and Overpass says the rest. Five
   sizes, and each one means something. */

/* ⚠ <strong> WAS NOT BOLD. Body copy is weight 300, and the browser's
   default for strong is 700 of a face that only ships 300/400/600 - it
   resolved to 400. One step off the surrounding text is not emphasis, so
   every bolded phrase the deck wrote has been arriving flat: "if it is
   mentioned at all", "The point:", "ask". Semibold is the kit's own emphasis
   weight, and .list strong already used it - this extends it to the places
   the engine missed. */
.body strong, .section-lead strong, .keypoint strong,
.mo-beat strong, .reflect__prompt strong, .mo-reasons li strong,
.evidence__paper strong { font-weight: var(--weight-semibold); }

/* ── click to reveal ────────────────────────────────────────
   Screen 26's three assumptions. Anu, 30 September: "the boxes look odd."
   Three bordered cards stacked on a white screen read as a form to fill in.
   No box now - a rule under each line and a purple marker that turns, which
   is how the rest of the module sets a list, and it stops competing with the
   accordion rows two screens later. Built on the engine's .reveal-item so
   Space/Enter sequencing and audit.js both see it. */
.mo-reveals { display: grid; margin: var(--sp-5) 0; max-width: var(--measure); }
.mo-reveal { border-bottom: 1px solid var(--border, #D8D6E0); }
.mo-reveal:first-child { border-top: 1px solid var(--border, #D8D6E0); }
.reveal-btn--wide {
  display: flex; align-items: baseline; gap: 14px;
  width: 100%; text-align: left; background: none; border: none; cursor: pointer;
  padding: 18px 4px;
}
.mo-reveal__mark {
  flex: none; width: 13px; height: 13px; position: relative; top: -1px;
  background: var(--mim-purple-aa);
  clip-path: polygon(50% 0, 100% 50%, 50% 100%, 0 50%);
  transition: transform var(--dur-base, .22s) var(--ease-standard, ease);
}
.mo-reveal.open .mo-reveal__mark { transform: rotate(90deg); }
.mo-reveal__face {
  font-family: var(--font-feature); font-weight: var(--weight-regular);
  font-size: var(--t-lead); line-height: 1.3; color: var(--fg);
}
.mo-reveal__body { padding: 0 0 20px 27px; }
.mo-reveal__body .body { margin-top: 0; }
.reveal-btn--wide:hover .mo-reveal__face { color: var(--mim-purple-aa-dark); }
body.night-mode .mo-reveal,
body.night-mode .mo-reveal:first-child { border-color: rgba(255,255,255,0.22); }
body.night-mode .mo-reveal__face { color: rgba(255,255,255,0.92); }
body.night-mode .mo-reveal__mark { background: #cdbcff; }

/* ── nothing gives the answer away ─────────────────────────────
   Content after an activity, held until the learner has answered. The hook is
   the library's own: markEngaged() sets data-rtl-engaged="1" on the activity
   root, and every activity calls it, so this one rule covers the estimate,
   the recall and anything added later. A sibling selector, so the held block
   simply follows the activity in the markup - no JS of ours involved. */
.mo-after { display: none; }
.rtl-act[data-rtl-engaged="1"] ~ .mo-after { display: block; }

/* ── the statement screen, in two rows ───────────────────────────
   Measured at four lines in a 700px column. It is the module's one dramatic
   beat, so it gets the divider's width and a size tuned to break twice on its
   68 characters. Anu, 30 September: "can we make the text appear in two
   rows?" */
.screen--statement .screen-inner { max-width: min(1040px, 92vw); }
.screen--statement .title {
  font-size: calc(clamp(30px, 4vw, 56px) * var(--fs-scale));
  line-height: 1.1;
  text-wrap: balance;
}

/* ── the divider's own body copy ───────────────────────────────
   The title centres and the sub-line centres, but lesson 1.2's closing
   paragraph is capped at --measure inside a 1040px divider, so it sat left
   with 316px of empty screen beside it - which is what reads as "not aligned
   to the centre". Centred as a block; the text inside stays centred with the
   rest of the divider. */
.screen--part .screen-inner > .body { margin-inline: auto; }

/* ── room to breathe ────────────────────────────────────────
   Anu, 30 September, on both reflections: "space it out as it appears too
   crowded." The kit pads .reflect at 32/36 and sets its prompts 14px apart,
   which is fine for one short question and cramped for five numbered ones. */
.reflect { padding: clamp(28px, 3.6vw, 44px) clamp(24px, 3.4vw, 46px); }
.reflect__label { margin-bottom: 22px; }
.reflect__prompt + .reflect__prompt { margin-top: 20px; }
.reflect ol { margin-top: 20px; }
.reflect ol li + li { margin-top: 16px; }
.reflect__respond { margin-top: 26px; }
.reflect__actions { margin-top: 16px; gap: 14px; }

/* ── the lesson's references, centred ────────────────────────── */
.evidence--refs { margin-inline: auto; }

/* ── the reveal actually reveals ────────────────────────────────
   ⚠ NOTHING IN THE SUITE HIDES .reveal-item__body. toggleReveal() adds and
   removes `.open` on .reveal-item, and mim-gp-shared.css styles only
   `.try-saying .reveal-item`, which is Pharmacy Part 3's own variant. So the
   rehearsal's phrasings were on screen from the start and the button toggled
   a class no rule listened to - Anu, 30 September: "the phrasings is visible
   already and the button isn't working."

   Written against .reveal-item rather than .rehearsal-panel so any reveal
   this module adds later works the same way. Raised for Tom: the generic
   pair belongs in the engine. */
.reveal-item > .reveal-item__body { display: none; }
.reveal-item.open > .reveal-item__body { display: block; }

/* ── emphasis that reads as emphasis ─────────────────────────────
   Anu, 30 September: on screen 31 "the bold needs to be bolder." The earlier
   pass caught .body and .section-lead but not list items, and screen 31's
   whole structure is three bolded leads inside an arrow list. Bold is also
   what carries the three-way distinction the deck calls the module's most
   transferable idea, so it has to land. */
.mo-arrows li strong, .keypoint-list li strong,
.list li strong, .rtl-act strong { font-weight: var(--weight-semibold); }

/* ── the divider's sub-line ──────────────────────────────────
   Sits directly under the title it qualifies, centred with it, no rule. */
.part-sub {
  font-family: var(--font-primary); font-weight: var(--weight-regular);
  font-size: calc(15px * var(--fs-scale)); line-height: 1.45;
  letter-spacing: 0.02em;
  color: rgba(255,255,255,0.82);
  margin-top: var(--sp-3);
  max-width: none;
}

/* ── the starburst ──────────────────────────────────────────
   MIM Pro's mark, GP Part 1 screen 16: `.mim-starburst` holding an <img> of
   mark-rays.png, sitting behind the content rather than in it. Their CSS is
   in their own module, not the engine, so this is the mechanism rebuilt from
   the markup - same class and same shape, so a later port replaces it
   cleanly.

   Decorative, so aria-hidden in the markup, and the turn stops outright under
   reduced motion. Low opacity because it is a ground: the questions sit on
   it, and anything stronger competes with the type it is behind. */
.mo-rays-wrap { position: relative; }
.mo-rays-wrap > .mo-questions { position: relative; z-index: 1; }
/* Anu, 30 September: "can we move the graphic to top right hand corner of the
   page?" Centred behind the questions it sat under the type; in the corner it
   is a mark on the page, which is how MIM Pro uses it too. It overhangs the
   column deliberately - the screen has room either side and the crop is part
   of the effect. */
.screen[data-starburst] .screen-inner { position: relative; }
.mim-starburst {
  position: absolute; top: -8px; right: -78px; z-index: 0;
  pointer-events: none;
}
.mim-starburst img {
  width: clamp(130px, 17vw, 210px); height: auto;
  opacity: .2;
  animation: starburstTurn 72s linear infinite;
}
@keyframes starburstTurn { to { transform: rotate(360deg); } }
@media (prefers-reduced-motion: reduce) { .mim-starburst img { animation: none; } }
body.reduced-motion .mim-starburst img { animation: none; }
body.night-mode .mim-starburst img { opacity: .26; }

/* ── air under a title, above its picture ─────────────────────────
   Anu, 30 September: "more space needed between title and placeholder image.
   Apply this for all screens that contain images." The engine's stacking
   rhythm gives every block the same gap, which is right for two paragraphs
   and too tight when a 700px picture lands straight under a 44px title. */
.title + .mo-figure,
.title + .mo-diagram,
.heading + .mo-figure { margin-top: var(--sp-6); }

/* Anu, 26 October 2026: *"Screen 21 - fix the spacing between headings. It
   appears too cramped. Do a full sweep of the module."* Measured on screen
   21: the first group heading carried margin-top 24px and every later one
   carried ZERO, so "What to ask" and "What you can explain and offer" sat
   flush against the checkbox list above them. The closers are generated, so
   this is one rule rather than five screens of markup. */
.heading.mo-closer-group { margin-top: var(--sp-7); margin-bottom: var(--sp-3); }

/* Anu, 26 October 2026, on the closers: *"Is there anything else we can do to
   present this better both visually and pedagogically?"* - the third of the
   three answers. The list ran flat under a heading; on a card the groups read
   as groups, and on the four closers that have NO group headings it gives the
   list an edge it did not have. */
.mo-commit-list.list {
  background: var(--bg-mid, #F2F2F4);
  border: 1px solid var(--border, #D8D6E0);
  border-radius: var(--radius-md);
  padding: var(--sp-4);
  margin-block: var(--sp-4);
  display: flex; flex-direction: column; gap: var(--sp-2);
}
body.night-mode .mo-commit-list.list {
  background: rgba(255,255,255,.05);
  border-color: rgba(255,255,255,.18);
}

/* The cumulative list on lesson 4.5's closer. Deliberately quieter than the
   commit list above it: it is a read-out of decisions already made, not
   another thing to do. */
.mo-chosen { margin-block: var(--sp-6); }
.mo-chosen__list { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: var(--sp-2); }
.mo-chosen__item {
  position: relative; padding-left: 26px;
  font-size: calc(16px * var(--fs-scale)); line-height: 1.5;
}
/* /!\ THIS MARKER SHIPPED AS MOJIBAKE. It read content: "¹3" -
   U+00B9 then the digit 3 - from the 26 October closers build, and it
   is what a learner saw in front of every line of "Everything you have
   marked, across the module". No gate can catch it: it is valid CSS,
   valid UTF-8, and audit.js measures contrast rather than meaning.
   Replaced with the tick the rest of the course uses - the same glyph
   as the reflect control's "✓ Saved". */
.mo-chosen__item::before {
  content: "✓"; position: absolute; left: 0; top: 0;
  color: var(--mim-purple-aa-dark); font-weight: var(--weight-semibold);
}
/* /!\ AND IT HAD NO NIGHT COLOUR. --mim-purple-aa-dark is 7.11:1 on
   white and 2.77:1 on the night ground, where 4.5 is needed - on a
   glyph that is the only thing marking a line as committed. Two
   reasons no gate saw it: it is a ::before, and the list is empty
   until a learner has ticked something, so there is nothing rendered
   to measure on a walk-through. #cdbcff is 11.46:1, and is the value
   .mo-accent already flips to. */
body.night-mode .mo-chosen__item::before { color: #cdbcff; }
.mo-chosen__none { font-size: calc(15px * var(--fs-scale)); opacity: .78; margin: 0; }
/* (!) NOT :first-of-type. The closer's groups do not share one parent, so
   :first-of-type matched the first heading INSIDE EACH of them and the
   module rendered 24 / 24 / 48px down one screen. Measured, not assumed.
   One value for all of them: the first follows the h1, where 48px reads
   correctly too. */

/* Anu, 26 October 2026: *"For screen 6, I want the list to be displayed on a
   different coloured background like a card."* */
.list.mo-card-list {
  background: var(--bg-mid, #F2F2F4);
  border: 1px solid var(--border, #D8D6E0);
  border-radius: var(--radius-md);
  padding: var(--sp-5) var(--sp-5) var(--sp-5) calc(var(--sp-5) + 1.1em);
  margin-block: var(--sp-5);
}
body.night-mode .list.mo-card-list {
  background: rgba(255,255,255,.05);
  border-color: rgba(255,255,255,.18);
}

/* ── two things, side by side ─────────────────────────────────
   Screen 42's linear and circular models. The copy already sets them against
   each other - one asks what has stopped working, the other what could work -
   so they face each other instead of running down the page. A purple keyline
   on each, no box: cards stay screen 7's. */
.mo-pair {
  display: grid; grid-template-columns: repeat(2, minmax(0, 1fr));
  column-gap: clamp(28px, 4vw, 56px); row-gap: 28px;
  margin: var(--sp-6) 0; max-width: var(--measure);
}
.mo-pair__side { padding-top: 14px; border-top: 2px solid var(--mim-purple-aa); }
.mo-pair__side > .heading { margin-top: 0; font-size: var(--t-lead); }
.mo-pair__side > .body:first-of-type { margin-top: 10px; }
body.night-mode .mo-pair__side { border-top-color: #cdbcff; }
@media (max-width: 720px) { .mo-pair { grid-template-columns: minmax(0, 1fr); } }

/* ── sequencing ──────────────────────────────────────────
   The library ships its own styling; this only pulls the numbers onto the
   module's purple so the activity does not arrive in the fallback colour. */
.rtl-sequence__num { background: var(--mim-purple-aa); color: #fff; }
body.night-mode .rtl-sequence__num { background: #cdbcff; color: #1A1A1A; }

/* ── right and wrong look different ─────────────────────────────
   MIM Pro's own modifiers, 14 uses across its three GP parts. The engine
   styles .quiz-feedback; the modifier is what tells a learner at a glance
   which of the two panels they are reading. Green and red are the kit's
   semantic pair, not new colour. */
.quiz-feedback--correct { border-left: 3px solid var(--mim-green, #1F7A52); padding-left: 16px; }
.quiz-feedback--incorrect { border-left: 3px solid var(--mim-purple-aa); padding-left: 16px; }
body.night-mode .quiz-feedback--correct { border-left-color: #5EC98F; }
body.night-mode .quiz-feedback--incorrect { border-left-color: #cdbcff; }


/* ── purple bullets ─────────────────────────────────────────
   Anu: "for all the treatment screens, make the bullet points purple", and
   on the last one "make the bullet points like small purple boxes". Squares
   throughout rather than one screen in squares and the rest in dots - a
   marker that changes shape between screens reads as a mistake. */
ul.list li::before {
  background: var(--mim-purple-aa);
  border-radius: 2px;
  width: 8px; height: 8px;
  top: 0.55em;
}
ol.list li::before { color: var(--mim-purple-aa-dark); }
body.night-mode ul.list li::before { background: #cdbcff; }
body.night-mode ol.list li::before { color: #cdbcff; }

/* ── purple lettering ────────────────────────────────────
   A second way to put purple on a screen, for the ones where a smear would be
   the fourth mark in a row. --mim-purple-aa-dark because this is running text
   at reading size, not a 24px display line. */
.mo-accent { color: var(--mim-purple-aa-dark); font-weight: var(--weight-semibold); }
body.night-mode .mo-accent { color: #cdbcff; }

/* ── the treatment tiles ───────────────────────────────────
   Not clickable, and deliberately so - they are a contents list for the four
   screens that follow, not navigation. A purple top edge each, which is the
   device screen 7's tiles already use. */
.mo-treats {
  list-style: none; margin: var(--sp-6) 0 0; padding: 0;
  display: grid; grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: 14px; max-width: var(--measure);
}
.mo-treat {
  padding: 16px 18px;
  border-top: 3px solid var(--mim-purple-aa);
  background: var(--mim-grey-light);
  font-family: var(--font-primary); font-weight: var(--weight-semibold);
  font-size: var(--t-body); line-height: 1.35; color: var(--fg);
}
body.night-mode .mo-treat { background: #17171b; color: rgba(255,255,255,0.92); }
body.night-mode .mo-treat { border-top-color: #cdbcff; }
@media (max-width: 720px) { .mo-treats { grid-template-columns: minmax(0, 1fr); } }

/* ── the film that is not wired yet ─────────────────────────────
   The pause-and-choose tile carries the deck's real Vimeo id but no play
   action, because embedding is not confirmed on the course domain. It reads
   as a placeholder rather than a dead button: no pointer, no hover, and the
   play mark at rest. */
.video-frame--placeholder .video-poster { cursor: default; }
.video-frame--placeholder .video-poster__play { opacity: .55; }

/* The way back to the film, after the choice has been made. */
.mo-back { margin-top: var(--sp-4); }

/* ── the three practice points, as cards ──────────────────────────
   Anu, 30 September: screen 60 "looks too similar to screen 59". It was the
   same reveal component twice in a row - same markup, same "Select each one
   to see how it plays out" instruction - so three cards across read as a
   different interaction. The copy already said "three things".

   The grid drops to one column under 720px: three cards side by side at phone
   width would each be too narrow to read.

   No `all: unset` on the button. The engine gives every `.screen button` a
   box-shadow and a border through its own rules, so those are named and
   cleared explicitly - the same reason .evidence__row needs neutralising
   further down this file. */
.mo-cards {
  list-style: none; margin: var(--sp-5) 0 0; padding: 0;
  display: grid; gap: var(--sp-4);
  grid-template-columns: repeat(3, minmax(0, 1fr));
}
@media (max-width: 720px) { .mo-cards { grid-template-columns: 1fr; } }
.mo-card {
  background: var(--mim-grey-light); border-radius: var(--radius-lg);
  border: 1px solid transparent; display: flex; flex-direction: column;
  overflow: hidden;
}
.mo-card.is-open { border-color: var(--mim-purple-aa); }
.screen .mo-card__btn,
.screen .mo-card__btn:hover {
  background: none; border: 0; box-shadow: none; margin: 0;
  box-sizing: border-box; cursor: pointer; display: block; width: 100%;
  text-align: left; padding: clamp(18px, 2vw, 24px);
  font-family: var(--font-primary); font-weight: var(--weight-semibold);
  font-size: calc(17px * var(--fs-scale)); line-height: 1.3; color: var(--fg);
}
.mo-card__btn:focus-visible {
  outline: 3px solid var(--mim-purple-aa); outline-offset: 2px;
}
/* NOT --mim-purple-aa. Measured on the tile background it is 3.66:1 in day
   and 4.37:1 at night, both under AA, and audit.js fails on it. #5D3CC9 is
   6.34:1 in day and #B7A3FC is 8.23:1 at night - both already in this
   module's palette (they are two of the confetti colours). */
.mo-card__num {
  display: block; font-family: var(--font-primary);
  font-size: calc(13px * var(--fs-scale)); letter-spacing: .14em;
  color: #5D3CC9; margin-bottom: 10px;
}
.mo-card__cue {
  float: right; color: #5D3CC9;
  font-weight: var(--weight-regular); line-height: 1;
}
body.night-mode .mo-card__num,
body.night-mode .mo-card__cue { color: #B7A3FC; }
.mo-card.is-open .mo-card__cue { display: inline-block; transform: rotate(45deg); }
.mo-card__body { padding: 0 clamp(18px, 2vw, 24px) clamp(18px, 2vw, 24px); }
.mo-card__body .body { margin: 0; }
/* var(--fg) does not flip to a light colour inside these components at
   night - it resolved to black on #171719, which audit.js caught at 1.18:1 -
   so the text colour is named for night explicitly. */
body.night-mode .mo-card { background: rgba(255, 255, 255, .05); }
body.night-mode .screen .mo-card__btn,
body.night-mode .mo-card__face { color: rgba(255, 255, 255, .92); }

/* ── "What the research says" as a tile, not a screen ─────────────
   Anu, 30 September: "can we make this similar to Pause for a Mo tile - and
   where possible fold it into the previous screen. The heading is too big
   right now and I still think it doesn't need a screen of its own."

   So the evidence rows sit in a labelled tile the same shape as the reflect
   tile, instead of under a 96px h1 on a screen of their own.

   ⚠ IT IS ITS OWN CLASS, NOT A REUSE OF .reflect. The engine's .reflect
   starts at opacity 0 and waits for a class the engine only ever adds to
   .reflect[data-reflect] (mim-gp-shared.css:520, mim-gp-shared.js:2013), so
   borrowing the class for a tile with no data-reflect would have shipped an
   invisible block - and audit.js counts invisible text, so it would have
   failed the gate rather than reaching a learner. */
.evidence-tile {
  background: var(--mim-grey-light); border-radius: var(--radius-lg);
  padding: clamp(28px, 3.6vw, 44px) clamp(24px, 3.4vw, 46px);
  max-width: var(--measure); margin-top: var(--sp-6);
}
.evidence-tile__label {
  font-family: var(--font-primary); font-weight: var(--weight-semibold);
  font-size: calc(13px * var(--fs-scale)); letter-spacing: 0.14em;
  text-transform: uppercase; color: var(--fg);
  margin-bottom: 10px; display: block;
}
.evidence-tile .mo-instruction { margin-bottom: 18px; }
.evidence-tile .evidence:first-of-type { margin-top: 0; }
body.night-mode .evidence-tile { background: rgba(255, 255, 255, .05); }
body.night-mode .evidence-tile__label { color: rgba(255, 255, 255, .92); }

/* ── the tick and the cross ───────────────────────────────────
   Anu, 30 September: "when a learner chooses one option, that option needs to
   have either a cross or a tick." On the option itself, so it is clear WHICH
   one was chosen - the engine only colours the border, which says something
   happened but not what. Shape as well as colour: a tick and a cross read
   without relying on green and red being distinguishable.

   THE GLYPHS ARE LITERAL CHARACTERS (U+2713 tick, U+2715 cross) AND MUST
   STAY THAT WAY. They reached 2026-09-30 as "¹3" and "¹5", so every chosen
   option showed a stray number instead of a mark. The cause is a Python
   octal escape, not a file encoding: in a non-raw Python string, "\2713"
   is \271 (octal 271 = 185 = ¹) followed by "3". Any script that writes
   this rule must use a raw string, chr(0x2713), or the character itself.
   The symbol faces lead the font stack because Overpass may not carry these
   code points. */
.quiz-option[data-fb] { position: relative; padding-right: 46px; }
.quiz-option[data-fb].correct::after,
.quiz-option[data-fb].incorrect::after {
  position: absolute; right: 16px; top: 50%; transform: translateY(-50%);
  width: 22px; height: 22px; border-radius: 999px;
  display: grid; place-items: center;
  font-family: 'Segoe UI Symbol', 'Apple Symbols', 'Noto Sans Symbols 2',
               var(--font-primary); font-weight: var(--weight-semibold);
  font-size: calc(14px * var(--fs-scale)); line-height: 1; color: #fff;
}
.quiz-option[data-fb].correct::after   { content: "✓"; background: var(--mim-green, #1F7A52); }
.quiz-option[data-fb].incorrect::after { content: "✕"; background: #B3261E; }
/* The right answer, once the tries are used up. */
.quiz-option[data-fb].revealed-correct {
  border-color: var(--mim-green, #1F7A52);
}
.quiz-option[data-fb].revealed-correct::after {
  content: "✓"; position: absolute; right: 16px; top: 50%;
  transform: translateY(-50%); width: 22px; height: 22px; border-radius: 999px;
  display: grid; place-items: center; font-size: calc(14px * var(--fs-scale)); color: #fff;
  background: var(--mim-green, #1F7A52); opacity: .55;
}

/* The chosen option's own feedback. */
.mcq-fb {
  margin-top: var(--sp-5); padding-left: 16px;
  border-left: 3px solid var(--mim-purple-aa);
  font-family: var(--font-primary); font-weight: var(--weight-light);
  font-size: var(--t-body); line-height: 1.55; color: var(--fg);
  max-width: var(--measure);
}
body.night-mode .mcq-fb { color: rgba(255,255,255,0.9); border-left-color: #cdbcff; }
.mcq-after { margin-top: var(--sp-4); }

/* The other options' explanations, under the chosen one. Anu, 30 September:
   "display the remaining feedback after this." Set back from the chosen
   feedback rather than beside it - the one they picked stays the answer to
   their question, and these are the ones they did not ask about. */
.mcq-rest {
  margin-top: var(--sp-4); padding-left: 16px;
  border-left: 3px solid rgba(0,0,0,0.12);
  max-width: var(--measure);
}
.mcq-rest .body { font-size: var(--t-body); line-height: 1.55; margin: 0 0 10px; }
.mcq-rest .body:last-child { margin-bottom: 0; }
.mcq-rest strong { color: var(--mim-purple-aa-dark); }
body.night-mode .mcq-rest { border-left-color: rgba(255,255,255,0.22); }
body.night-mode .mcq-rest strong { color: #cdbcff; }

/* ── click them in the order you would ask them ────────────────
   The third mechanic this screen has worn. Ranking and select-then-place both
   made the learner work out the CONTROL before they could answer the
   QUESTION; this one is a list of buttons, and the only thing to know is that
   the order you press them in is your answer. The number appears in the
   circle as you go, so the answer is visible while it is being made.

   The circle is the screen's purple element - registered in check-purple.py
   as mo-order__num. */
.mo-order { max-width: var(--measure); }
.mo-order__list { list-style: none; margin: 0; padding: 0; counter-reset: none; }
.mo-order__row + .mo-order__row { margin-top: 10px; }

.mo-order__item {
  display: flex; align-items: flex-start; gap: 14px; width: 100%;
  text-align: left; cursor: pointer;
  padding: 14px 16px; border-radius: 10px;
  background: rgba(0,0,0,0.02);
  border: 1px solid rgba(0,0,0,0.14);
  font-family: var(--font-primary); font-weight: var(--weight-light);
  font-size: var(--t-body); line-height: 1.5; color: var(--fg);
  transition: background .15s ease, border-color .15s ease;
}
.mo-order__item:hover:not(:disabled) {
  background: rgba(132,99,248,0.07); border-color: var(--mim-purple-aa);
}
.mo-order__item:focus-visible {
  outline: 3px solid var(--mim-purple-aa); outline-offset: 2px;
}
.mo-order__item:disabled { cursor: default; }

/* Empty until it is chosen: an outline where the number will go, so the list
   reads as six slots rather than six paragraphs. */
.mo-order__num {
  flex: 0 0 auto; width: 30px; height: 30px; border-radius: 999px;
  display: grid; place-items: center;
  font-weight: var(--weight-semibold); font-size: calc(15px * var(--fs-scale));
  border: 2px solid rgba(0,0,0,0.18); color: transparent;
}
.mo-order__item.is-picked { background: rgba(132,99,248,0.09); border-color: var(--mim-purple-aa); }
.mo-order__item.is-picked .mo-order__num {
  background: var(--mim-purple-aa-dark); border-color: var(--mim-purple-aa-dark);
  color: #fff;
}
.mo-order__text { flex: 1 1 auto; padding-top: 3px; }

/* After the sixth. Colour is not the only signal: the wanted position is
   spelled out in words beside the ones that moved. */
.mo-order__item.is-right .mo-order__num {
  background: var(--mim-green, #1F7A52); border-color: var(--mim-green, #1F7A52);
}
.mo-order__item.is-wrong .mo-order__num {
  background: none; color: var(--fg); border-color: rgba(0,0,0,0.3);
}
.mo-order__want {
  flex: 0 0 auto; align-self: center;
  font-size: var(--t-micro); font-weight: var(--weight-semibold);
  letter-spacing: .06em; text-transform: uppercase;
  color: rgba(0,0,0,0.55); white-space: nowrap;
}
.mo-order__reset { margin-top: var(--sp-4); }
.mo-order__fb { margin-top: var(--sp-4); max-width: var(--measure); }
.mo-order__fb[hidden] { display: none; }

body.night-mode .mo-order__item {
  background: rgba(255,255,255,0.04); border-color: rgba(255,255,255,0.22);
  color: rgba(255,255,255,0.92);
}
body.night-mode .mo-order__item:hover:not(:disabled) { border-color: #cdbcff; }
body.night-mode .mo-order__num { border-color: rgba(255,255,255,0.3); }
body.night-mode .mo-order__item.is-picked .mo-order__num { background: #5D3CC9; border-color: #cdbcff; }
body.night-mode .mo-order__item.is-wrong .mo-order__num { color: rgba(255,255,255,0.92); }
body.night-mode .mo-order__want { color: rgba(255,255,255,0.6); }

/* ── the confetti at the end of the module ─────────────────────
   Anu, 30 September: "something celebratory, like confetti falling on the
   screen for completing module 1. After completing every module."

   Purely decorative: it never takes a click, it is aria-hidden, and the
   script removes the canvas when it stops. Below the modals (300+) so a
   lightbox or the mail dialog still sits on top of it. The script does not
   run at all under reduced motion, so there is no animation here to damp. */
/* ── the module contents menu ────────────────────────────────
   Anu, 30 September: "in the menu bar, lesson 1.3 is slightly indented. Fix
   this to make it consistent with other lesson titles."

   Measured rather than guessed: every group header is 36px tall except 1.3's,
   which is 48px. Its label - "1.3 How sex works, and how cancer changes it" -
   is the only one long enough to wrap in the 356px panel, and the header
   inherits text-align: center, so BOTH its lines centre. The first line
   therefore starts further right than the five single-line labels beside it,
   and the orphaned "it" sits in the middle of the second line. Nothing is
   indenting it; it is being centred.

   Left-aligning the label fixes it for any label that wraps, which is the
   real condition - a longer lesson title in a later module would have done
   the same thing. Module-local: .nav-group__label belongs to the engine, and
   the engine is shared by twelve modules. */
#chapterList .nav-group__label { text-align: left; }

.mo-confetti {
  position: fixed; inset: 0; z-index: 250;
  width: 100%; height: 100%;
  pointer-events: none;
}

/* ── the resources checklist ──────────────────────────────────
   The engine styles .resource-item__label and little else, because the whole
   component is left to the module. Rows rather than bullets: these are things
   to tick, not things to read past. */
.resources-list { list-style: none; margin: var(--sp-5) 0 0; padding: 0; max-width: var(--measure); }
.summary-resource-item { border-bottom: 1px solid var(--border, #D8D6E0); }
.summary-resource-item:first-child { border-top: 1px solid var(--border, #D8D6E0); }
.resource-item__label {
  display: flex; gap: 14px; align-items: flex-start;
  padding: 14px 4px; cursor: pointer;
}
.resource-checkbox { margin-top: 3px; width: 18px; height: 18px; accent-color: var(--mim-purple-aa-dark); flex: none; }
.resource-item__text {
  font-family: var(--font-primary); font-weight: var(--weight-light);
  font-size: var(--t-body); line-height: 1.5; color: var(--fg);
}
.summary-resource-item.tagged .resource-item__text { color: var(--fg); font-weight: var(--weight-regular); }
body.night-mode .summary-resource-item,
body.night-mode .summary-resource-item:first-child { border-color: rgba(255,255,255,0.22); }
body.night-mode .resource-item__text { color: rgba(255,255,255,0.9); }

.resources-select-all { margin-top: var(--sp-4); }
/* The deck's own group headings, inside the checklist. */
.resources-group { border: none; padding: var(--sp-5) 0 6px; }
.resources-group:first-child { padding-top: 0; }
.resources-group .heading { margin: 0; font-size: var(--t-lead); }
.rtl-act__btn--ghost {
  background: none; color: var(--mim-purple-aa-dark);
  border-color: var(--mim-purple-aa);
}
body.night-mode .rtl-act__btn--ghost { color: #cdbcff; border-color: #cdbcff; }
.resources-email-section { margin-top: var(--sp-6); display: flex; align-items: center; gap: 14px; flex-wrap: wrap; }
.email-resources-btn:disabled { opacity: .45; cursor: not-allowed; }

/* ── save for later ────────────────────────────────────────
   On the evidence blocks, so "anything you saved as you went along is already
   ticked for you" is true rather than aspirational. */
.res-save {
  display: inline-block; margin-top: 12px;
  background: none; border: 1.5px solid var(--mim-purple-aa); border-radius: var(--radius-pill, 999px);
  padding: 6px 14px; cursor: pointer;
  font-family: var(--font-primary); font-weight: var(--weight-semibold);
  font-size: calc(12px * var(--fs-scale)); letter-spacing: .02em;
  color: var(--mim-purple-aa-dark);
}
.res-save:hover { background: rgba(132,99,248,.10); }
.res-save.is-saved { background: var(--mim-purple-aa); color: #fff; border-color: var(--mim-purple-aa); }
body.night-mode .res-save { color: #cdbcff; border-color: #cdbcff; }
body.night-mode .res-save.is-saved { background: #cdbcff; color: #1A1A1A; }

/* ── tell the learner what to do ──────────────────────────────
   Anu's standing rule of 30 September: every interaction carries an
   instruction. It reads as an instruction rather than as body copy - the
   kit's own .instructional weight and colour - without shouting. */
.mo-instruction {
  font-family: var(--font-primary); font-weight: var(--weight-semibold);
  font-size: var(--t-micro); letter-spacing: 0.08em; text-transform: uppercase;
  color: var(--mim-purple-aa-dark);
  margin-bottom: var(--sp-3);
}
body.night-mode .mo-instruction { color: #cdbcff; }

/* ── the recall list, once it arrives ──────────────────────────
   Plain lines now, not tick buttons - nothing is scored, so a tick was a
   control that did nothing but invite a click. */
.rtl-recall__line {
  position: relative; padding-left: 20px;
  font-size: var(--t-body); line-height: 1.55; color: var(--fg);
}
.rtl-recall__line + .rtl-recall__line { margin-top: 8px; }
.rtl-recall__line::before {
  content: ""; position: absolute; left: 0; top: 0.62em;
  width: 7px; height: 7px; border-radius: 999px; background: var(--mim-purple-aa);
}
body.night-mode .rtl-recall__line { color: rgba(255,255,255,0.9); }
body.night-mode .rtl-recall__line::before { background: #cdbcff; }

/* ── air after a numbered prompt ──────────────────────────────
   Anu, twice: "more space between the last question and the paragraph." Both
   reflections end a numbered list and then carry a closing line, and the
   engine's list spacing runs the two together. */
.reflect ol + .reflect__prompt,
.reflect ol + .body,
.reflect ul + .reflect__prompt,
.reflect ul + .body { margin-top: 26px; }

/* ── hidden means hidden ────────────────────────────────────
   ⚠ AN ENGINE BUG, NOT A MODULE ONE. rtl-activities.css sets
   `.rtl-recall__list { display: flex }` with no [hidden] guard, and a display
   rule beats the hidden attribute's UA `display: none` every time. So the
   free-recall answers were on screen from the start - the learner could read
   the list they were being asked to recall, which is the whole activity gone.
   Measured: hidden=true, display=flex, visible=true.

   The same shape would hit any [hidden] element the library gives a display
   to, so the guard is written broadly rather than for the one class. Raised
   for Tom; this override can come out once rtl-activities.css carries it. */
.rtl-act [hidden], .rtl-act[hidden] { display: none !important; }

/* ── type scale ───────────────────────────────────────────
   Anu, 29 September 2026: "the titles are very big". The kit's --fs-h1 tops
   out at 64px, which is a poster size - it was drawn for a hero line of three
   or four words, and this module's titles are sentences ("The moment that
   mattered had already passed"). At 64px that wraps to three lines and pushes
   everything under it off the first screenful.

   Retuned, not removed: still the largest thing on the screen, still fluid,
   and still scaled by --fs-scale so the accessibility text-size control keeps
   working. The DIVIDER keeps the poster size - see .screen--part below - so
   the contrast between a lesson opening and a content screen gets stronger,
   not weaker.

   Overridden here rather than in mim-design-system.css, which is synced from
   the kit master and checked against it by design-system-check.js. */
:root {
  --fs-h1: calc(clamp(30px, 3.1vw, 44px) * var(--fs-scale));
  --fs-h2: calc(clamp(22px, 2.0vw, 30px) * var(--fs-scale));
}

/* ── the lesson divider ─────────────────────────────────────
   Anu, 30 September 2026: the black textured ground, no photograph, and the
   kit's purple smear behind the title.

   The photograph left plain black bands above and below it, because .mo-hero
   sizes to its image and the screen is taller than a 16:9 crop. Without the
   image there is no band - .screen--dark paints the whole screen, texture and
   all, and the title is the only thing on it.

   Size and centring are still the engine's, off `.part-divider` on the inner
   div; only the smear and the type colour are local. */
.screen--part .screen-inner {
  max-width: min(1040px, 92vw);
  margin-inline: auto;
}
.screen--part .title {
  position: relative;
  display: inline-block;
  padding: clamp(10px, 1.8vh, 22px) clamp(16px, 1.8vw, 30px);
  /* ⚠ THE MARK TAPERS AT BOTH ENDS, so stretching it to exactly 100% puts
     the fade on the first and last letters - measured, mean alpha across the
     height is 240 in the middle but 9 near the right edge, and the right
     taper alone is 5.7% of the artwork's width. Overscanned so both tapers
     fall outside the text, which is also how a real highlighter overshoots. */
  background: url('./assets/mim/mark-highlighter-line-v2.png') center / 118% 104% no-repeat;
  font-size: calc(clamp(26px, 3.2vw, 44px) * var(--fs-scale));
  line-height: 1.08;
  text-wrap: balance;
  /* ⚠ BLACK, NOT THE WHITE .screen--dark GIVES IT. White on the brand
     lavender is about 1.9:1 and fails AA outright; black on it is about 11:1.
     Screen 13 carries the same note for the same mark. */
  color: var(--mim-black);
  text-shadow: none;
}
/* The eyebrow stays on the dark ground, so it keeps the kit's white. Its own
   width cap boxes it well short of the column, so `text-align: center` was
   centring it inside that box rather than on the screen - the label sat left
   of the title it introduces. The box gets centred too. */
.screen--part .eyebrow {
  color: rgba(255,255,255,0.78);
  max-width: none;
  margin-inline: auto;
}

/* ── drawn diagrams ────────────────────────────────────────
   Inline SVG, so they follow the theme instead of being a light rectangle on
   a dark screen. Every colour resolves from the module's own tokens, and the
   night block restates the ones that lean on --fg, which night mode does not
   redefine. Each <svg> is aria-hidden: every word in them is in the copy
   beside them, so a screen reader would otherwise read the screen twice.

   ⚠ WITHOUT THESE RULES THE DIAGRAMS DO NOT DEGRADE GRACEFULLY - they break.
   An unresolved `fill` falls back to SVG's default, which is solid BLACK, and
   unset `text-anchor` puts every label at `start`, so the right-hand ones run
   off the edge. That is exactly what shipped for one build when this block was
   deleted by accident, and it is why the rules live next to the markup that
   needs them rather than anywhere more logical. */
.mo-diagram { max-width: 640px; margin-inline: auto; }
.mo-diagram svg { width: 100%; height: auto; display: block; overflow: visible; }
.mo-diagram figcaption {
  margin-top: 12px;
  font-family: var(--font-primary); font-weight: var(--weight-light);
  font-size: calc(12px * var(--fs-scale)); line-height: 1.45;
  color: var(--fg-muted); text-align: center;
}
body.night-mode .mo-diagram figcaption { color: rgba(255,255,255,0.6); }

.dia-line { stroke: var(--mim-purple-aa); stroke-width: 1.6; opacity: .55; }
.dia-line--rule { stroke-width: 2.5; opacity: .4; }
.dia-hub { fill: var(--mim-purple-aa); }
.dia-hub-t {
  fill: #fff; font-family: var(--font-primary); font-weight: var(--weight-semibold);
  font-size: calc(14px * var(--fs-scale)); text-anchor: middle;
}
.dia-node rect { fill: none; stroke: var(--mim-purple-aa); stroke-width: 1.6; opacity: .75; }
.dia-dot { fill: var(--mim-purple-aa); }
.dia-dot--end { fill: var(--mim-purple-aa-dark); }
.dia-year {
  fill: var(--mim-purple-aa-dark); font-family: var(--font-primary);
  font-weight: var(--weight-semibold); font-size: calc(17px * var(--fs-scale)); text-anchor: middle;
}
/* Sized up from 14/16: these are labels on a 640-unit canvas scaled down to
   fit a 700px column, so the on-screen size is smaller than the number looks.
   Anu, 30 September: "can't see the wording clearly." */
.dia-t {
  fill: var(--fg); font-family: var(--font-primary); font-weight: var(--weight-regular);
  font-size: calc(17px * var(--fs-scale)); text-anchor: middle;
}
.dia-t--lg { font-size: calc(19px * var(--fs-scale)); font-weight: var(--weight-semibold); }
.dia-t--start { text-anchor: start; }
.dia-t--end { text-anchor: end; }
.dia-field circle { fill: var(--mim-purple); opacity: .38; }

body.night-mode .dia-line { stroke: #cdbcff; opacity: .6; }
body.night-mode .dia-hub { fill: #cdbcff; }
body.night-mode .dia-hub-t { fill: #1A1A1A; }
body.night-mode .dia-node rect { stroke: #cdbcff; }
body.night-mode .dia-dot, body.night-mode .dia-dot--end { fill: #cdbcff; }
body.night-mode .dia-year { fill: #cdbcff; }
body.night-mode .dia-t { fill: rgba(255,255,255,0.92); }
body.night-mode .dia-field circle { fill: #b7a3fc; opacity: .34; }

/* ── the lesson's references ─────────────────────────────────
   The engine's evidence accordion carrying the whole lesson's references. The
   list inside it loses the rule and the top margin it needed when it sat
   loose at the foot of a screen. */
.evidence--refs { margin-top: var(--sp-6); }
.evidence--refs .refs { margin-top: 0; padding-top: 0; border-top: none; }

/* ── references ────────────────────────────────────────────
   Anu's rule of 18 September 2026, carried from Module 3: superscript markers,
   no in-text citation. The marker is purple because it is the only thing on a
   reference-carrying screen that could be, and --mim-purple-aa-dark because it
   is small text. The list sits quiet at the foot - it is provenance, not
   reading matter, so it takes the muted colour and a rule to separate it. */
.ref {
  font-size: 0.62em;
  font-weight: var(--weight-semibold);
  color: var(--mim-purple-aa-dark);
  vertical-align: super;
  line-height: 0;
  margin-left: 1px;
}
body.night-mode .ref { color: #cdbcff; }

.refs {
  margin-top: var(--sp-6);
  padding-top: var(--sp-4);
  border-top: 1px solid var(--border, #D8D6E0);
  list-style: decimal;
  padding-left: 1.4em;
  max-width: var(--measure);
}
.refs li {
  font-family: var(--font-primary);
  font-weight: var(--weight-light);
  font-size: calc(12px * var(--fs-scale));
  line-height: 1.5;
  color: var(--fg-muted);
}
.refs li + li { margin-top: 6px; }
body.night-mode .refs li { color: rgba(255,255,255,0.7); }
body.night-mode .refs { border-top-color: rgba(255,255,255,0.22); }

/* ── the estimate's verdict ─────────────────────────────────
   The payoff line of the whole activity, and the library sets it at body size
   where it read as a footnote to the thing the learner had just done. The
   engine writes the sentence as one text node, so the answer cannot be styled
   apart from the guess without an engine change - raised for Tom. Until then
   the whole verdict carries the weight. */
/* Anu, 30 September: "more space between the you guessed and 56% were offered
   no support at all. It's a bit crowded." The verdict and the sentence it
   explains are two separate statements arriving at the same moment, and the
   library stacks them at its own tight gap. */
/* Anu, 30 September, twice on this screen: more air between the slider and
   the answer. Measured, the verdict landed 26px under the result track and
   the whole reveal sat 38px off the button - close enough that the answer
   read as part of the control rather than as the payoff for using it. The
   reveal is a separate beat, so it gets a section's worth of space. */
.rtl-estimate__track { margin-top: 30px; }
.rtl-estimate__reveal { margin-top: var(--sp-7); }
.rtl-estimate__verdict + .body { margin-top: 22px; }
.rtl-estimate__verdict {
  font-family: var(--font-feature);
  font-size: calc(20px * var(--fs-scale));
  line-height: 1.3;
  color: var(--fg);
  border-left: 3px solid var(--mim-purple-aa);
  padding-left: 14px;
  margin-bottom: 18px;
}
body.night-mode .rtl-estimate__verdict { color: rgba(255,255,255,0.92); }

/* ── photocopy texture paths ───────────────────────────────────────────────
   THE KIT MASTER'S PATHS ARE WRONG. design-systems/men-in-mind/
   mim-design-system.css declares all three as
   url('assets/mim/mim/texture-photocopy-*.png') - a doubled "mim/" - and the
   files are at assets/mim/. Confirmed on the review server: the doubled path
   404s, the single one is 200.

   So every module carrying that kit has been rendering with NO photocopy
   texture at all, this one included, wherever --tex-photocopy-* is consumed:
   the engine's light video screens, .surface-white, and this module's
   .screen--lo and .screen--cinematic.

   Overridden here rather than in mim-design-system.css, because that file is a
   copy of the master and design-system-check.js compares the two - a fix there
   would read as drift and be overwritten by --fix. Raised for Tom; it belongs
   in the kit. */
:root {
  --tex-photocopy-black:  url('./assets/mim/texture-photocopy-black.png');
  --tex-photocopy-white:  url('./assets/mim/texture-photocopy-white.png');
  --tex-photocopy-purple: url('./assets/mim/texture-photocopy-purple.png');
  /* The kit ships the quotation mark but declares no token for it. */
  --img-mo-quote:         url('./assets/mim/mark-quotation.png');
}

/* ── lived-experience quotes ────────────────────────────── */
/* A quote and its attribution as one figure, not a blockquote followed by a
   loose paragraph - which is what these were, so nothing tied the voice to the
   words and the attribution rendered as ordinary body copy.

   The kit's own quotation mark opens each one. Two side by side on a wide
   screen, because they are a pair of voices saying the same thing from two
   sides, and stacking them buries the second. */

/* ONE PER ROW, not two abreast. Side by side each quote had half a measure to
   sit in - three or four words a line - and the pair read as a comparison
   table rather than as two men saying the same thing from two sides. */
.mo-quotes {
  display: flex; flex-direction: column;
  gap: clamp(26px, 3.2vh, 40px);
  margin: var(--sp-6) 0 var(--sp-5);
}

/* The mark hangs in the left margin rather than sitting above the words,
   which is what the full measure makes possible. It is absolute, so the
   blockquote and its figcaption stay the flex children and the caption
   still tucks under the quote it belongs to. */
.mo-quote {
  position: relative; margin: 0;
  padding-left: clamp(46px, 4.4vw, 66px);
  display: flex; flex-direction: column; gap: 10px;
}

.mo-quote::before {
  content: ""; position: absolute; left: 0; top: 0.3em;
  width: clamp(34px, 3vw, 46px); height: clamp(19px, 1.7vw, 25px);
  background: var(--img-mo-quote) left top / contain no-repeat;
}

.mo-quote blockquote {
  margin: 0;
  font-family: var(--font-feature); font-weight: var(--weight-regular);
  /* The lead's size, not a third step between it and the body. Screen 22 had
     six text sizes; the 19px quote sat between 16px body and 23px lead. Lora
     now says two things on a screen - the title, and a person speaking. */
  font-size: var(--fs-subhead); line-height: 1.32;
  letter-spacing: -0.01em; color: var(--fg);
}
body.night-mode .mo-quote blockquote { color: rgba(255,255,255,0.92); }

.mo-quote figcaption {
  font-family: var(--font-primary); font-weight: var(--weight-semibold);
  font-size: calc(13px * var(--fs-scale)); line-height: 1.4;
  letter-spacing: 0.04em; color: var(--fg-muted);
}
body.night-mode .mo-quote figcaption { color: rgba(255,255,255,0.7); }

/* ── figures ───────────────────────────────────────────────────────────── */
.mo-figure { margin: 0 0 var(--sp-6); max-width: var(--measure); }
.mo-figure img { width: 100%; height: auto; display: block; border-radius: var(--radius-md); }
/* Two of this module's six pool photographs are portrait, and at the
   column's full 700px they measured 700x1050 - taller than a 900px
   viewport, so the screen's own copy started below the fold. Measured in a
   browser, not guessed. Capping the WIDTH keeps the whole frame: an
   aspect-ratio crop like .mo-figure--band's would cut heads off the group
   shot on screen 75. */
.mo-figure--portrait { max-width: 400px; }

/* Diagrams and clean line illustrations read as reference material, not
   documentary photography - boxed and centred rather than run full-measure. */
.mo-figure--diagram {
  /* ⚠ SET HERE, not overridden earlier in the file. The first attempt at
     this added `.mo-figure--diagram { max-width: var(--measure) }` near the
     top, which this rule then beat - so screen 48's picture stayed small.
     Anu, 30 September, twice. */
  max-width: var(--measure); margin-left: auto; margin-right: auto;
  padding: var(--sp-5); background: var(--mim-grey-light); border-radius: var(--radius-md);
}
.mo-figure--diagram img { border-radius: var(--radius-sm); }
body.night-mode .mo-figure--diagram { background: #17171b; }

/* A portrait band (several faces) runs the full measure at a wide, short
   crop so it reads as one panorama rather than a single photograph. */
.mo-figure--band img { aspect-ratio: 21 / 6; object-fit: cover; }

/* A banner that runs the full width of the screen. The 820px measure and the
   side padding both live on .screen-inner, and .screen itself has neither, so
   this works by sitting OUTSIDE .screen-inner in the markup rather than by
   clawing its way back out with negative margins or 100vw - which would add a
   horizontal scrollbar as wide as the vertical one.

   Height is capped so the banner does not eat the screen on a wide monitor;
   object-fit crops rather than letterboxing. Square corners, because a rounded
   one against the viewport edge reads as a mistake. */
.mo-figure--bleed { max-width: none; margin: 0; }
.mo-figure--bleed img {
  border-radius: 0;
  aspect-ratio: 21 / 6;
  max-height: 340px;
  object-fit: cover;
}
@media (max-width: 680px) {
  .mo-figure--bleed img { aspect-ratio: 16 / 9; max-height: 220px; }
}

/* ── the statement screen's mark ─────────────────────────── */
/* The mark is the kit's own PNG, with its alpha intact, painted behind the
   line as the storyboard briefs it.

   ⚠ DO NOT PUT THIS BACK IN AN <img>. It was a flattened JPEG in a figure
   above the title, which is what the missing alpha forced: JPEG has no alpha
   channel, so the transparency became a black plate and mix-blend-mode:
   screen had to drop the black back out. That is also why it could not sit
   under the type - a blended plate greys whatever is over it.

   Decorative, so it is a background and carries no alt: the sentence is the
   content and a screen reader gaining "highlighter mark" here learns
   nothing. */
.screen--statement .title {
  position: relative;
  padding: clamp(10px, 1.6vh, 20px) clamp(14px, 1.6vw, 26px);
  /* ⚠ THE MARK TAPERS AT BOTH ENDS, so stretching it to exactly 100% puts
     the fade on the first and last letters - measured, mean alpha across the
     height is 240 in the middle but 9 near the right edge, and the right
     taper alone is 5.7% of the artwork's width. Overscanned so both tapers
     fall outside the text, which is also how a real highlighter overshoots. */
  background: url('./assets/mim/mark-highlighter-line-v2.png') center / 118% 104% no-repeat;
  /* ⚠ BLACK TYPE, NOT WHITE. The engine's .screen--dark .title is white, which
     is right on the dark ground and wrong on the mark: white on the brand
     lavender is about 1.9:1 and fails AA outright. Black on it is about 11:1.
     This is also the module's own convention - .mo-hl sets no colour because
     every other screen using the mark already has dark type on a light
     surface. */
  color: var(--mim-black);
}

/* A single portrait keeps an upright crop instead of being stretched to the
   shared measure width like a landscape photograph. */
.mo-figure--portrait { max-width: 320px; }
.mo-figure--portrait img { aspect-ratio: 3 / 4; object-fit: cover; }

/* ── pull quotes ───────────────────────────────────────────────────────────
   *italic* blocks that are ENTIRELY italic (rehearsal example phrasings, a
   divider's own aside) read as a beat apart from body copy, not just an
   emphasised sentence. Night mode does not redefine --fg, so this needs its
   own dark counterpart like every other class that leans on it. */
.pull-quote {
  margin: var(--sp-5) 0; padding-left: var(--sp-5); max-width: var(--measure);
  border-left: 3px solid var(--mim-purple-aa); color: var(--fg);
}
body.night-mode .pull-quote { color: rgba(255,255,255,.92); border-left-color: var(--mim-purple); }

/* ── hero ──────────────────────────────────────────────────────────────── */
.screen--hero.active { display: flex; padding: 0; max-width: none; overflow: hidden; }
.screen--hero { background: var(--mim-black); }
.mo-hero { position: relative; flex: 1; display: flex; align-items: flex-end; background: var(--mim-black); }
.mo-hero__img { position: absolute; inset: 0; width: 100%; height: 100%; object-fit: cover; object-position: center 58%; }
.mo-hero::after { content: ""; position: absolute; inset: 0;
  background: linear-gradient(180deg, rgba(0,0,0,.25) 0%, rgba(0,0,0,.15) 40%, rgba(0,0,0,.88) 100%); }
.mo-hero__inner { position: relative; z-index: 2; padding: clamp(28px,5vw,64px); max-width: 900px; }
.mo-hero__inner .title, .mo-hero__inner .heading, .mo-hero__inner .section-lead { color: #fff; }
.mo-hero__inner li { color: rgba(255,255,255,.94); }
/* The module line on a part divider, in MiM purple. It sits on the dark hero,
   where the kit's own --mim-purple has the contrast for it; the AA-dark
   recolour used on light surfaces would disappear here. */
.mo-hero__inner .body { color: var(--mim-purple); }

/* The hero backdrop is a permanent black scrim regardless of theme, so its
   part-label/pull-quote need the same fixed light-on-dark treatment as the
   title/heading/body above rather than the site-theme-driven variants. */
.mo-hero__inner .part-label { color: var(--mim-purple); }
.mo-hero__inner .pull-quote { color: rgba(255,255,255,.94); border-left-color: var(--mim-purple); }

/* ── cinematic landing (screen 1 only) ─────────────────────── */
/* Screen 1 opens the way MIM Pro for GPs Part 1 does: full-bleed image, scrim,
   photocopy texture, copy stacked bottom-left. Scoped to .screen--cinematic
   rather than .screen--hero, because the part-divider screens share
   .screen--hero and .mo-hero above and must keep that treatment.

   The [data-rise] entrance sits inside prefers-reduced-motion: no-preference,
   so a learner with reduced motion gets the copy already visible instead of
   stranded at opacity 0. The in-app reduced-motion setting is handled at the
   foot of this file, which compresses durations rather than killing the
   animation. */

.screen--cinematic {
  position: relative;
  height: calc(100vh - var(--header-h) - var(--footer-h));
  overflow: hidden;
  background: var(--mim-black);
  color: var(--mim-white);
}
.screen--cinematic.active { display: block; padding: 0; max-width: none; animation: none; }

.screen--cinematic .hero__media { position: absolute; inset: 0; }
.screen--cinematic .hero__media img {
  width: 100%; height: 100%;
  object-fit: cover; object-position: 42% 42%;
  filter: saturate(0.96) contrast(1.03) brightness(0.94);
}

.screen--cinematic .hero__scrim {
  position: absolute; inset: 0;
  background: linear-gradient(0deg, rgba(0,0,0,0.92) 0%, rgba(0,0,0,0.72) 30%, rgba(0,0,0,0.30) 62%, rgba(0,0,0,0.12) 100%);
}

.screen--cinematic .hero__tex {
  position: absolute; inset: 0;
  background: var(--tex-photocopy-black) center/cover;
  opacity: 0.45; mix-blend-mode: multiply;
}

.screen--cinematic .hero__inner {
  position: absolute; inset: 0; z-index: 2;
  display: flex; align-items: flex-start;
  overflow: auto;
}
/* margin-top:auto bottom-aligns the copy the way MIM Pro's does, but collapses
   to 0 once the stack is taller than the screen. align-items:flex-end would
   instead push the overflow off the top edge, out of reach of the scroll -
   which clipped the course title, because this landing carries the four
   objectives and MIM Pro's does not. */
.screen--cinematic .hero__inner-wrap {
  width: 100%; max-width: 1240px;
  margin: auto auto 0;
  padding: 40px 56px 36px;
}
.screen--cinematic .hero__copy {
  display: flex; flex-direction: column;
  align-items: flex-start; gap: 14px;
  max-width: 760px;
}

.hx-title {
  font-family: var(--font-feature); font-weight: var(--weight-regular);
  /* Anu, 26 October 2026: *"I don't want the scroll bar on screen 1."*
     .hero__inner carries overflow-y:auto, and at 1366x768 the stack measured
     833px against 640px of room - 193px over. The title was the single
     biggest item at 249px, so it comes down first; the copy is untouched. */
  font-size: clamp(36px, 4.1vw, 62px);
  line-height: 1.04; letter-spacing: -0.028em; color: #fff;
  margin: 0; text-wrap: balance;
}
/* The same purple rule screen 7 puts under its title, aligned left here
   because this copy is stacked bottom-left rather than centred. The brand
   lavender, not the AA-dark recolour: this sits on the scrim, where the dark
   variant reads as near-black. */
.hx-title::after {
  content: "";
  display: block;
  width: clamp(48px, 5vw, 72px); height: 3px;
  margin: clamp(10px, 1.2vh, 16px) 0 0;
  border-radius: 2px;
  background: var(--mim-purple);
}

.hx-sub {
  font-family: var(--font-primary); font-weight: var(--weight-light);
  font-size: calc(clamp(18px, 1.9vw, 23px) * var(--fs-scale)); line-height: 1.32;
  color: rgba(255,255,255,0.86); letter-spacing: -0.01em;
  margin: 0; max-width: 44ch;
}
.hx-bodies { display: flex; flex-direction: column; gap: 8px; }
.hx-body {
  font-family: var(--font-primary); font-weight: var(--weight-light);
  font-size: calc(15px * var(--fs-scale)); line-height: 1.6;
  color: rgba(255,255,255,0.72);
  /* Anu, 26 October 2026: *"Screen 1 - can we increase the width of the
     content after the line what you have covered so far."* Measured at
     1366px before changing it: the title runs the full 760px of
     .hero__copy and these paragraphs stopped at 491px, so the block read
     as a narrow column under a wide heading. 72ch takes them to ~680px.
     NOT the full 760px: .hero__copy is the hard cap and matching it
     exactly puts the measure at ~80 characters, past the readable range,
     on type that is already 15px light-on-dark. */
  margin: 0; max-width: 72ch;
}

/* This landing carries the four course objectives, which MIM Pro's does not.
   Same type as .hx-body so the list reads as part of that stack, with the MiM
   purple marker used elsewhere in the module. */
.hx-body--lead-in { color: rgba(255,255,255,0.86); }
.hx-list {
  list-style: none; margin: 0; padding: 0;
  display: flex; flex-direction: column; gap: 9px;
  max-width: 62ch;
}
.hx-list li {
  position: relative; padding-left: 18px;
  font-family: var(--font-primary); font-weight: var(--weight-light);
  font-size: calc(15px * var(--fs-scale)); line-height: 1.6;
  color: rgba(255,255,255,0.72);
}
.hx-list li::before {
  content: ""; position: absolute; left: 0; top: 0.62em;
  width: 6px; height: 6px; border-radius: 50%;
  background: var(--mim-purple);
}

@keyframes hxRise { to { opacity: 1; transform: none; } }
@keyframes hxImg  { from { transform: scale(1.06); opacity: 0.4; } to { transform: scale(1); opacity: 1; } }

@media (prefers-reduced-motion: no-preference) {
  .screen--cinematic [data-rise] {
    opacity: 0; transform: translateY(16px);
    animation: hxRise 0.66s var(--ease-out) forwards;
    animation-delay: var(--d, 0ms);
  }
  .screen--cinematic .hero__media img { animation: hxImg 1.2s var(--ease-out) both; }
}

/* A 1366x768 laptop is the tightest real case and the one that still showed
   a scrollbar after the type came down - 11px over. Height, not width, is
   what decides this, so it keys off max-height. */
@media (max-height: 820px) {
  .screen--cinematic .hero__inner-wrap { padding: 26px 56px 24px; }
  .screen--cinematic .hero__copy { gap: 11px; }
}

/* 1280x720 is the shortest desktop viewport in common use and was still
   17px over after the tier above. */
@media (max-height: 740px) {
  .screen--cinematic .hero__inner-wrap { padding: 18px 56px 16px; }
  .screen--cinematic .hero__copy { gap: 10px; }
  .hx-bodies { gap: 6px; }
}

@media (max-width: 600px) {
  .screen--cinematic .hero__inner-wrap { padding: 60px 22px 36px; }
  .hx-title { font-size: clamp(36px, 12vw, 54px); }
}

/* ── module tiles (what this course covers) ─────────────────── */
/* The four modules as rectangular tiles rather than a run of headings and
   paragraphs. Two up on desktop, one up below 680px. Tiles size to their own
   content and the row stretches them to match, so the four read as a set. */

.mo-tiles {
  list-style: none; margin: 28px 0 32px; padding: 0;
  display: grid; grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: 18px;
}
@media (max-width: 680px) {
  .mo-tiles { grid-template-columns: minmax(0, 1fr); gap: 14px; }
}

.mo-tile {
  display: flex; flex-direction: column; gap: 8px;
  padding: 22px 24px 24px;
  background: #fff;
  border: 1px solid var(--border);
  border-top: 3px solid var(--mim-purple-aa);
  border-radius: var(--radius-md);
}
body.night-mode .mo-tile {
  background: rgba(255,255,255,0.04);
  border-color: rgba(255,255,255,0.16);
  border-top-color: var(--mim-purple);
}

.mo-tile__num {
  font-family: var(--font-primary); font-weight: var(--weight-semibold);
  font-size: calc(12px * var(--fs-scale));
  letter-spacing: 0.13em; text-transform: uppercase;
  /* --mim-purple-aa is AA for 24px+ only; this label is 12px, so it takes the
     dark variant. audit.js caught it at 4.1:1 against the tile's white. */
  color: var(--mim-purple-aa-dark); margin: 0;
}
body.night-mode .mo-tile__num { color: var(--mim-purple); }

.mo-tile__title {
  font-family: var(--font-feature); font-weight: var(--weight-regular);
  font-size: calc(24px * var(--fs-scale));
  line-height: 1.15; letter-spacing: -0.015em;
  margin: 0; color: var(--fg); text-wrap: balance;
}
body.night-mode .mo-tile__title { color: rgba(255,255,255,0.94); }

.mo-tile__body {
  font-family: var(--font-primary); font-weight: var(--weight-light);
  font-size: calc(15px * var(--fs-scale)); line-height: 1.55;
  margin: 0; color: var(--fg-muted);
}
body.night-mode .mo-tile__body { color: rgba(255,255,255,0.78); }

/* ── the eyebrow, in purple ─────────────────────── */
/* Four screens carry an eyebrow and none of them had any other purple in the
   type. This is one rule instead of a mark per screen.

   The kit's `.screen--dark .eyebrow` is two classes deep and still wins, so
   eyebrows on dark surfaces stay white - which is right, purple on the
   cinematic scrim would disappear. AA-dark, not the brand lavender: an
   eyebrow is 12px and the lavender is an AA-large token. */
.eyebrow { color: var(--mim-purple-aa-dark); }
body.night-mode .eyebrow { color: var(--mim-purple); }

/* ── editorial beat ──────────────────────────────────────── */
/* A line where the course steps out and speaks to the learner about why a
   screen matters. Display face, a size up from body, air either side - and
   NO left rule, deliberately. .mo-stat and .keypoint-wrap both carry a purple
   rule, and a third on the same screen stops any of them meaning anything.

   Smaller than .keypoint (22px) so the screen's conclusion still outranks it. */
.mo-beat {
  font-family: var(--font-feature); font-weight: var(--weight-regular);
  font-size: calc(20px * var(--fs-scale));
  line-height: 1.35; letter-spacing: -0.01em;
  color: var(--fg);
  max-width: var(--measure);
  /* Anu, 30 September: the space around "Some days he doesn't feel like much
     of a man" on screen 12 "is too much white space." --sp-6 top and bottom
     is the gap between SECTIONS; the beat is a line inside one, and it was
     reading as though the screen had ended. */
  margin: var(--sp-5) 0 var(--sp-4);
}
body.night-mode .mo-beat { color: rgba(255,255,255,0.92); }

/* --pull is the beat given its full weight, for a screen that has no other
   purple rule competing with it. Screen 12's pivot line is the emotional turn
   of the whole scenario and it was reading at a size and colour that let the
   eye pass straight over it.

   The type stays --fg and the purple is the rule, not the words: a full
   sentence set in purple at this size reads as a system message rather than
   as Anthony. The brand lavender is too light for a 3px rule on white, so
   this is the AA-dark recolour, and night mode swaps back. */
.mo-beat--pull {
  font-size: calc(24px * var(--fs-scale));
  line-height: 1.3;
  padding-left: clamp(20px, 2.2vw, 30px);
  border-left: 3px solid var(--mim-purple-aa-dark);
  margin: var(--sp-7) 0;
}
body.night-mode .mo-beat--pull { border-left-color: var(--mim-purple); }

/* ── pulled-out statistic ──────────────────────────────── */
/* A figure set large beside the sentence it comes from.

   THE BIG NUMBER IS aria-hidden AND THE SENTENCE IS VERBATIM. The figure
   restates what the sentence already says, so it is decoration for the eye and
   a screen reader hears the claim once. Pulling the number out and shortening
   the sentence around it would read better and would be a rewrite of
   signed-off copy, so it is not done. */

.mo-stat {
  display: flex; align-items: baseline; gap: clamp(18px, 2.4vw, 32px);
  margin: var(--sp-6) 0;
  padding-left: 18px;
  border-left: 3px solid var(--mim-purple-aa);
  max-width: var(--measure);
}
.mo-stat__figure {
  flex: 0 0 auto; margin: 0;
  font-family: var(--font-feature); font-weight: var(--weight-regular);
  font-size: calc(clamp(44px, 5vw, 72px) * var(--fs-scale));
  line-height: 0.95; letter-spacing: -0.03em;
  color: var(--mim-purple-aa-dark);
  font-variant-numeric: tabular-nums;
}
body.night-mode .mo-stat__figure { color: var(--mim-purple); }
.mo-stat__text {
  margin: 0;
  font-family: var(--font-primary); font-weight: var(--weight-light);
  font-size: var(--fs-body); line-height: 1.55; color: var(--fg);
}
body.night-mode .mo-stat__text { color: rgba(255,255,255,0.92); }

@media (max-width: 600px) {
  .mo-stat { flex-direction: column; gap: 10px; }
}

/* ── video tile ────────────────────────────────────────── */
/* MIM Pro for GPs Part 1 screen 14 - Tom's "LE Tiles" design of 20 September
   2026. A 16:9 still with a purple rule round it and a white light travelling
   that rule, then a solid caption strip beneath carrying the instruction and a
   filled purple play disc with a ring pulsing out of it.

   One still, not a cross-fading reel. MIM Pro rotates nine lived-experience
   portraits through some of its tiles and uses a single static image for the
   rest - Tom: "just use this static image" - and that is this case, because
   there is one frame from Anthony's film rather than a set.

   The engine's own .video-poster is absolutely positioned and .video-poster
   --styled adds big side padding to centre serif copy over a dark plate, so
   both have to be undone here or the picture is inset from its own frame. */

/* The engine's .video-frame is locked to aspect-ratio 16/9 with
   overflow: hidden, sized for a bare player. This tile is a 16:9 picture PLUS
   a caption strip, so the frame has to size to its content or the strip is
   clipped off at exactly the 16:9 line - which is what it did: the box
   measured 700x112 at y=839 while the frame ended at 839.

   MIM Pro gets there with a floated ::before of padding-top: 56.25%, which
   reserves a 16:9 minimum without setting aspect-ratio. The ratio here already
   comes from .vp-stage, so the frame just stops constraining. */
.video-frame[data-happy-frame] {
  aspect-ratio: auto;
  height: auto;
  overflow: visible;
}

.video-frame[data-happy-frame] > .video-poster {
  position: relative; inset: auto;
  display: block; height: auto;
  padding: 0;
  background: #0b1519;
  aspect-ratio: auto;
  overflow: hidden;
}

/* The picture window. No picture in it: the poster is a black screen, because a
   still of the wrong person is worse than no still at all - the only frames
   available are of other men, and the copy beside this one names Anthony.
   The 16:9 window stays, so the tile keeps its shape and a real frame can be
   dropped straight back in. */
.vp-stage {
  position: relative; aspect-ratio: 16 / 9; overflow: hidden;
  background: #000;
}
.happy-reel { position: absolute; inset: 0; }
.happy-reel img {
  position: absolute; inset: 0;
  width: 100%; height: 100%;
  /* the engine's img { max-width: 100% } clamps this otherwise */
  max-width: none;
  object-fit: cover;
  filter: saturate(0.82) contrast(1.06);
}
.happy-reel[data-n="1"] img { opacity: 1; }

/* The purple rule, and the white light handed from edge to edge. Each edge
   holds the light for a quarter of the 5200ms lap and is invisible for the
   other three quarters.

   .video-poster .vp-rule rather than .vp-rule alone: the engine's
   `.video-poster > div` is 0,2,0 and outranks a single class, which pulls the
   rule into the flow - an empty div with a 2px border, giving a stripe above
   the picture and no rule around it. */
.video-poster .vp-rule {
  position: absolute; inset: 0; z-index: 2;
  pointer-events: none;
  border: 2px solid rgba(183, 163, 252, 0.45);
  animation: vpRuleDraw 1600ms cubic-bezier(0.2, 0.7, 0.2, 1) 600ms both;
}
.vp-rule span {
  position: absolute; opacity: 0;
  background-repeat: no-repeat;
  animation-duration: 5200ms;
  animation-timing-function: linear;
  animation-iteration-count: infinite;
}
.vp-rule__t, .vp-rule__b {
  left: 0; right: 0; height: 2px;
  background-image: linear-gradient(90deg, transparent, #fff, transparent);
  background-size: 18% 100%;
}
.vp-rule__r, .vp-rule__l {
  top: 0; bottom: 0; width: 2px;
  background-image: linear-gradient(180deg, transparent, #fff, transparent);
  background-size: 100% 18%;
}
.vp-rule__t { top: -2px;    animation-name: vpTraceR; animation-delay: 2200ms; }
.vp-rule__r { right: -2px;  animation-name: vpTraceD; animation-delay: 3500ms; }
.vp-rule__b { bottom: -2px; animation-name: vpTraceL; animation-delay: 4800ms; }
.vp-rule__l { left: -2px;   animation-name: vpTraceU; animation-delay: 6100ms; }

/* The caption strip. Solid, under the picture, never over it. */
.video-poster .vp-box {
  position: relative; z-index: 1;
  display: flex; flex-direction: row; align-items: center;
  justify-content: space-between; gap: 28px;
  width: auto; margin: 0;
  padding: 20px 26px;
  background: #0D0D0D;
  border-radius: 0;
  text-align: left;
}
.video-poster .vp-box .video-poster__instruct {
  margin: 0; flex: 1 1 auto; max-width: 62ch;
  color: #E8E8E8;
  font-family: var(--font-primary); font-weight: 300;
  /* scaled, not the artboard's hard-coded 16px, or the text-size control
     silently stops working on this one component */
  font-size: calc(16px * var(--fs-scale));
  line-height: 1.45;
  /* the engine sets text-align: center on this element, so inheriting left
     from .vp-box never reaches it */
  text-align: left;
}
.video-poster .vp-box .video-poster__play {
  position: relative; flex: 0 0 auto;
  width: 72px; height: 72px;
  display: grid; place-items: center;
  border-radius: 50%;
  border: 2px solid var(--mim-purple);
  background: var(--mim-purple);
  box-shadow: none;
  transition: transform 220ms var(--ease-standard),
              background-color 220ms var(--ease-standard),
              border-color 220ms var(--ease-standard);
}
.video-poster .vp-box .video-poster__play::after {
  content: ""; position: absolute; inset: -2px;
  border-radius: 50%; border: 2px solid var(--mim-purple);
  animation: vpPlayPulse 2600ms ease-out infinite;
  pointer-events: none;
}
.video-poster .vp-box .video-poster__play svg {
  width: 30px; height: 30px; fill: #000; margin-left: 4px;
}
.video-poster:hover .vp-box .video-poster__play {
  transform: scale(1.06);
  background: #fff; border-color: #fff;
}

@keyframes vpRuleDraw {
  from { clip-path: inset(0 100% 0 0); }
  to   { clip-path: inset(0 0 0 0); }
}
@keyframes vpTraceR {
  0%           { opacity: 1; background-position: -20% 0; }
  25%          { opacity: 1; background-position: 120% 0; }
  25.01%, 100% { opacity: 0; background-position: 120% 0; }
}
@keyframes vpTraceD {
  0%           { opacity: 1; background-position: 0 -20%; }
  25%          { opacity: 1; background-position: 0 120%; }
  25.01%, 100% { opacity: 0; background-position: 0 120%; }
}
@keyframes vpTraceL {
  0%           { opacity: 1; background-position: 120% 0; }
  25%          { opacity: 1; background-position: -20% 0; }
  25.01%, 100% { opacity: 0; background-position: -20% 0; }
}
@keyframes vpTraceU {
  0%           { opacity: 1; background-position: 0 120%; }
  25%          { opacity: 1; background-position: 0 -20%; }
  25.01%, 100% { opacity: 0; background-position: 0 -20%; }
}
@keyframes vpPlayPulse {
  0%, 100% { transform: scale(1);    opacity: 0.5; }
  50%      { transform: scale(1.22); opacity: 0; }
}

/* Nothing here hides content at rest, so the reduced-motion handling only has
   to stop things moving. The still is opaque either way. */
@media (prefers-reduced-motion: reduce) {
  .video-poster .vp-rule { animation: none; clip-path: none; }
  .vp-rule span { animation: none; opacity: 0; }
  .video-poster .vp-box .video-poster__play::after { animation: none; opacity: 0; }
  .video-poster:hover .vp-box .video-poster__play { transform: none; }
}
body.reduced-motion .video-poster .vp-rule { animation: none; clip-path: none; }
body.reduced-motion .vp-rule span { animation: none; opacity: 0; }
body.reduced-motion .video-poster .vp-box .video-poster__play::after { animation: none; opacity: 0; }

@media (max-width: 600px) {
  .video-poster .vp-box { flex-direction: column; align-items: flex-start; gap: 16px; padding: 16px 18px; }
  .video-poster .vp-box .video-poster__play { width: 60px; height: 60px; }
}

/* ── learning outcomes (MIM Pro .screen--lo) ──────────────── */
/* MIM Pro for GPs Part 1 screen 6: dark photocopy surface, a portrait band
   across the top, display title with the purple highlighter mark behind one
   word, arrow bullets in two columns.

   THE BAND IS FIVE PANELS OFF ONE PHOTOGRAPH. MIM Pro cycles nine separate
   images through each of its five panels; this shows five slices of a single
   image, positioned across it, so the hairline gaps and the divided-strip look
   are MIM Pro's while the storyboard's single brief - PLACEHOLDER-16,
   "portrait band, five faces" - stays a single file.

   display goes on .screen--lo.active, never on the bare class: the engine's
   .screen { display: none } is equal specificity and earlier in the cascade,
   so a bare display leaves this screen rendered while inactive. */

.screen--lo {
  padding: 0;
  background: var(--mim-black) var(--tex-photocopy-black) center / cover;
}
.screen--lo.active { display: flex; align-items: stretch; justify-content: flex-start; }
.lo-wrap { display: flex; flex-direction: column; width: 100%; }

/* MIM Pro's own height. The portraits are cut at 600x750, so a shallow band
   crops them through the chest and, at the height this was, through the top of
   a head. */
.lo-band {
  position: relative; flex: none;
  display: flex; gap: 2px; overflow: hidden;
  height: clamp(240px, 40vh, 420px);
  background: #0b1519;
}
.lo-portrait {
  position: relative; flex: 1 1 0; min-width: 0; height: 100%;
  overflow: hidden; background: #0b1519;
}
.lo-portrait img {
  position: absolute; inset: 0;
  width: 100%; height: 100%; max-width: none;
  object-fit: cover;
  filter: contrast(1.04) saturate(0.9);
}
/* FIVE PANELS, NINE PORTRAITS EACH, CYCLING - MIM Pro's band as built.

   Every panel carries all nine portraits and cross-fades through them, eight
   seconds a picture, so one turn is 72s. The stagger is what keeps the strip
   from changing all at once, and what keeps a face off it twice over: panel i
   reaches picture j at t = 8j + 1.6i, so two panels can only coincide when i
   is the same, and no two are.

   ⚠ THE DELAYS ARE NEGATIVE, so every panel is already mid-cycle at load.
   Tom's note records what a positive stagger did: panels 3, 4 and 5 sat blank
   for their first seconds, and the first render was two photographs and three
   dark boxes.

   ⚠ THE CROSS-FADE PERCENTAGES ARE COMPUTED FROM THE NUMBER OF PICTURES.
   loSwap is nine: each is visible 11.111% of the turn and fades in over
   1.111%. A set of a different size needs its own keyframes, or the reel shows
   a black gap once a lap.

   object-position: center 22% frames the faces, because the portraits carry
   headroom above them. */
.lo-portrait img { object-position: center 22%; opacity: 0; }

@keyframes loSwap {
  0%      { opacity: 0; }
  1.111%  { opacity: 1; }
  11.111% { opacity: 1; }
  12.222% { opacity: 0; }
  100%    { opacity: 0; }
}

@media (prefers-reduced-motion: no-preference) {
  .lo-portrait img {
    animation: loSwap 72s linear infinite;
    animation-delay: calc(var(--j) * -8s - var(--i) * 1.6s);
  }
}
/* Nothing cycles for a learner who has asked for less motion, in either the
   OS setting or the module's own: the first portrait in each panel simply
   stays. Without this the reel leaves every image at opacity 0, because that
   is where loSwap starts and where the base rule above puts them. */
@media (prefers-reduced-motion: reduce) {
  .lo-portrait img:first-child { opacity: 1; }
}
body.reduced-motion .lo-portrait img { animation: none; opacity: 0; }
body.reduced-motion .lo-portrait img:first-child { opacity: 1; }

.lo-band::before {
  content: ""; position: absolute; inset: auto 0 0 0; z-index: 1;
  height: clamp(30px, 4.5vw, 64px);
  background: linear-gradient(to bottom, rgba(11,11,13,0), rgba(11,11,13,0.9) 78%, #0b0b0d);
  pointer-events: none;
}
.lo-band::after {
  content: ""; position: absolute; left: 0; right: 0; bottom: 0; z-index: 2;
  height: 2px; background: var(--mim-purple);
  transform-origin: left center;
}
@media (max-width: 680px) {
  .lo-band { height: clamp(120px, 20vh, 190px); }
  .lo-portrait:nth-child(n+4) { display: none; }
}

.lo-body {
  flex: 1 1 auto;
  display: flex; flex-direction: column; justify-content: center;
  padding: clamp(20px, 3vh, 40px) clamp(24px, 5.5vw, 80px) clamp(24px, 3.5vh, 48px);
}

.lo-title {
  font-family: var(--font-feature); font-weight: var(--weight-regular);
  font-size: calc(clamp(28px, 3.4vw, 48px) * var(--fs-scale));
  line-height: 1.08; letter-spacing: -0.02em;
  margin: 0 0 clamp(8px, 1.2vw, 16px); color: var(--mim-white);
  text-wrap: balance;
}
.lo-hl {
  background: url('./assets/mim/mark-highlighter-line-v2.png') center 64% / 100% 76% no-repeat;
  padding: 0 0.12em 0.04em;
  -webkit-box-decoration-break: clone; box-decoration-break: clone;
}

.lo-intro {
  font-family: var(--font-primary); font-weight: var(--weight-light);
  font-size: calc(clamp(16px, 1.5vw, 21px) * var(--fs-scale));
  line-height: 1.3; letter-spacing: -0.01em;
  margin: 0 0 clamp(8px, 1.2vw, 16px); max-width: 82ch;
  color: rgba(255,255,255,0.9);
}
.lo-leadin {
  font-family: var(--font-primary); font-weight: var(--weight-light);
  font-size: calc(16px * var(--fs-scale)); line-height: 1.4;
  margin: 0 0 clamp(10px, 1.4vw, 20px); color: rgba(255,255,255,1);
}

.lo-list-wrap {
  display: grid; grid-template-columns: repeat(2, minmax(0, 1fr));
  column-gap: clamp(24px, 3vw, 52px); row-gap: clamp(8px, 1vw, 14px);
  align-items: start;
}
@media (max-width: 680px) { .lo-list-wrap { grid-template-columns: minmax(0, 1fr); } }

.lo-list {
  list-style: none; margin: 0; padding: 0;
  display: flex; flex-direction: column; gap: clamp(8px, 1vw, 14px);
}
.lo-list li {
  position: relative;
  font-family: var(--font-primary); font-weight: var(--weight-light);
  font-size: calc(16px * var(--fs-scale)); line-height: 1.42;
  padding-left: clamp(18px, 2vw, 26px); color: rgba(255,255,255,0.85);
}
.lo-list li::before {
  content: ""; position: absolute; left: 0; top: 0.3em;
  width: clamp(9px, 1vw, 13px); height: clamp(7px, 0.8vw, 10px);
  background: url('./assets/mim/mark-arrow-1.png') center / contain no-repeat;
}

@keyframes lo-rise { from { opacity: 0; transform: translateY(12px); } to { opacity: 1; transform: none; } }
@keyframes lo-fade { from { opacity: 0; transform: scale(1.04); } to { opacity: 1; transform: none; } }
@keyframes mimRule { from { transform: scaleX(0); } to { transform: scaleX(1); } }

/* The engine adds .lo-play on entry to any screen carrying .screen--lo, keyed
   off the class rather than a screen number. Every reveal sits inside
   no-preference: these start at opacity 0 with fill `both`, the shape that
   strands content when the animation never runs, and qa-reduced-motion.js does
   not inspect any of it. Written for any list length, unlike MIM Pro's fixed
   3 + 2 nth-child chain. */
@media (prefers-reduced-motion: no-preference) {
  /* The panel's own entrance. The pictures inside it are on loSwap, which is a
     separate animation on a separate element, so the two do not fight. */
  .screen--lo.lo-play .lo-portrait { animation: lo-fade 0.8s cubic-bezier(0.16,1,0.3,1) both; }
  .screen--lo.lo-play .lo-portrait:nth-child(1) { animation-delay: 0.05s; }
  .screen--lo.lo-play .lo-portrait:nth-child(2) { animation-delay: 0.14s; }
  .screen--lo.lo-play .lo-portrait:nth-child(3) { animation-delay: 0.23s; }
  .screen--lo.lo-play .lo-portrait:nth-child(4) { animation-delay: 0.34s; }
  .screen--lo.lo-play .lo-portrait:nth-child(5) { animation-delay: 0.45s; }
  .screen--lo.lo-play .lo-band::after { animation: mimRule 900ms cubic-bezier(0.2,0.7,0.2,1) 700ms both; }
  .screen--lo.lo-play .lo-title  { animation: lo-rise 0.55s cubic-bezier(0.16,1,0.3,1) 0.23s both; }
  .screen--lo.lo-play .lo-intro  { animation: lo-rise 0.55s cubic-bezier(0.16,1,0.3,1) 0.34s both; }
  .screen--lo.lo-play .lo-leadin { animation: lo-rise 0.55s cubic-bezier(0.16,1,0.3,1) 0.45s both; }
  .screen--lo.lo-play .lo-list li {
    animation: lo-rise 0.55s cubic-bezier(0.16,1,0.3,1) both;
    animation-delay: calc(0.5s + (var(--k, 0) * 0.06s));
  }
}

/* ── "what this course covers" ───────────────────────── */
/* A full-screen photograph with the four module tiles over it, sized so the
   whole screen arrives at once with nothing below the fold.

   The image is an <img> rather than a CSS background so the design brief can
   live in its alt, where the designer reads it, and so it keeps the
   placeholder-first convention - a real file dropped in at the same name needs
   no code change.

   display goes on .screen--covers.active, never on the bare class: the
   engine's .screen { display: none } is equal specificity and earlier in the
   cascade, so a bare display leaves this screen rendered while inactive. Third
   time this trap has come up in this build; mim-gp-shared.css:191 documents it.

   NOT sized with overflow: hidden. Every size here scales through --fs-scale,
   and a learner who turns the text up has to be able to reach the last tile,
   so this fits by being smaller and scrolls again once the text outgrows it. */
/* The dark ground is on the SCREEN, not only in the scrim. Two reasons, and
   neither is cosmetic: audit.js resolves an element's effective background by
   walking its ancestors, and an absolutely-positioned scrim is a sibling
   rather than an ancestor - so it read white type on the white page and called
   the title invisible. And if the photograph ever fails to load, the type is
   still on something it can be read against. */
.screen--covers { background-color: #08080a; }

/* No overflow: hidden here. On a flex container that centres its content, it
   clips at BOTH ends once the content is taller than the box, and the top of
   it becomes unreachable - the trap that cost the landing its title at v0.11.
   The image is clipped by its own wrapper instead, which is what actually
   needs clipping. */
.screen--covers.active {
  display: flex; flex-direction: column; justify-content: center;
  position: relative;
  padding: 0;
}

.covers__media { position: absolute; inset: 0; z-index: 0; overflow: hidden; }
.covers__media img {
  width: 100%; height: 100%; max-width: none;
  object-fit: cover; object-position: center;
}
/* Dark enough for white type at AA, light enough that the photograph still
   reads as a photograph. */
.covers__scrim {
  position: absolute; inset: 0; z-index: 1;
  background: linear-gradient(180deg, rgba(8,8,10,0.86) 0%, rgba(8,8,10,0.72) 45%, rgba(8,8,10,0.88) 100%);
}

.screen--covers .screen-inner {
  position: relative; z-index: 2;
  flex: none;
  max-width: 1100px;
  padding-top: clamp(20px, 3vh, 40px); padding-bottom: clamp(20px, 3vh, 40px);
}

/* The screen is a dark surface now, and the engine colours .title and the
   closing line from var(--fg), which night mode does not redefine either. */
/* Standard heading size - the engine's --fs-h1 - not a size cut down to help
   the screen fit. */
.screen--covers .title { color: var(--mim-white); text-align: center; }

/* A short purple rule under the title. Drawn rather than borrowed: the
   highlighter mark and the circle are both used elsewhere in the module, and a
   third word-level mark would start to read as a tic. A rule instead echoes
   the purple top edge on the four tiles below it and the rule under the
   outcomes band.

   The kit's --mim-purple, not the AA-dark recolour: this sits on the scrim,
   where the darker purple disappears. The tiles keep the dark one because they
   are light cards. Decoration, so contrast does not apply either way. */
.screen--covers .title::after {
  content: "";
  display: block;
  width: clamp(48px, 5vw, 72px); height: 3px;
  margin: clamp(14px, 1.6vh, 22px) auto 0;
  border-radius: 2px;
  background: var(--mim-purple);
}

/* margin-inline: auto as well as text-align. The engine gives .body
   max-width: var(--measure), so its BOX is narrower than this screen's 1100px
   container and sits hard left; centring the text inside that box still leaves
   the line off-centre on the screen. */
.screen--covers .body {
  color: rgba(255,255,255,0.82);
  text-align: center;
  margin-inline: auto;
}

/* Tighter than the default tile, because four of them plus a heading and the
   closing line have to share one screen. */
.screen--covers .mo-tiles { margin: clamp(18px, 2.6vh, 30px) 0; gap: 14px; }
.screen--covers .mo-tile { padding: 16px 18px; gap: 6px; }
.screen--covers .mo-tile__title { font-size: calc(20px * var(--fs-scale)); }
.screen--covers .mo-tile__body { font-size: calc(14px * var(--fs-scale)); line-height: 1.5; }

/* ── acknowledgement of country ────────────────────────── */
/* MIM Pro for GPs Part 1 screen 4, and deliberately plain there: flat black
   with the photocopy texture switched off, a purple title, white body copy
   centred on a 60ch measure. No figure and no animation. */

.screen--aoc { background-image: none; background-color: var(--mim-black); }

.aoc { max-width: 60ch; margin: 0 auto; }

.aoc__title {
  font-family: var(--font-feature);
  font-weight: 400;
  font-size: calc(clamp(28px, 3.4vw, 48px) * var(--fs-scale));
  line-height: 1.08;
  letter-spacing: -0.02em;
  color: var(--mim-purple);
  margin: 0 0 clamp(18px, 2.2vw, 32px);
}

.aoc__text {
  font-family: var(--font-primary);
  font-weight: 300;
  font-size: calc(clamp(15px, 1.42vw, 21px) * var(--fs-scale));
  line-height: 1.55;
  letter-spacing: -0.005em;
  color: rgba(255,255,255,0.84);
  margin: 0;
}
.aoc__text + .aoc__text { margin-top: 1.1em; }

/* ── FAQ accordion ────────────────────────────────────── */
/* Matching MIM Pro for GPs Part 1 screen 3. The card geometry is already
   right - this module uses the engine's promoted FAQ, which is the same look
   MIM Pro spends a block of its own stylesheet getting back to. Three things
   differ.

   1. THE SHADOW BAND UNDER AN OPEN QUESTION.
   The engine gives every pressable thing depth at `.screen button` (0,1,1),
   and that OUTRANKS its own `.accordion__summary { box-shadow: none }`
   (0,1,0). The summary is a button inside a card with overflow: hidden, so
   its shadow is clipped into a hard grey band across the card, lying over the
   answer as soon as the question is expanded.

   Fixed by specificity rather than !important, as Tom does in MIM Pro: each
   selector beats exactly the engine rule it has to beat and disturbs nothing
   else.
     .screen .accordion__summary                  0,2,0  beats  .screen button
                                                                 0,1,1
     body.night-mode .screen .accordion__summary  0,3,0  beats  body.night-mode
                                                                 .screen button
                                                                 0,2,1
   Both halves, because the engine ships a night variant of the depth rule. */
.screen .accordion__summary,
.screen .accordion__summary:hover { box-shadow: none; }
body.night-mode .screen .accordion__summary,
body.night-mode .screen .accordion__summary:hover { box-shadow: none; }

/* THE SAME BAND, ON THE EVIDENCE PANEL. Tom's audit of 19 September found this
   in all 15 modules: the engine gives .evidence__row depth both through
   `.screen button` and through its own rule, and .evidence clips, so the
   shadow is cropped into a grey band lying across the citations as soon as the
   panel opens. Measured here before the fix: a 14px/30px shadow on the row of
   an open panel with the pointer nowhere near it.

   The rule Tom states, worth keeping rather than patching a third component
   later: A LIFTED, CLIPPING PANEL CARRIES THE ONLY SHADOW, and any control
   nested inside it is flat. Both halves again, because night mode ships its
   own variant of the depth rule. */
.screen .evidence__row,
.screen .evidence__row:hover { box-shadow: none; }
body.night-mode .screen .evidence__row,
body.night-mode .screen .evidence__row:hover { box-shadow: none; }

/* 2. NIGHT MODE.
   The engine's card takes `background: var(--bg)`, which stays white, while
   its own night rule turns the question text white - so the FAQ is a column of
   white cards carrying white text. The value is MIM Pro's. */
body.night-mode .accordion__item { background: #17171b; }

/* 3. THE PURPLE MOUSTACHE, Tom's round of 15 September 2026: "for the
   accordion buttons throughout the entire suite of modules - i'd like to
   implement a purple moustache on the left hand side".

   --img-mo-mark is an SVG, and an SVG loaded as a background cannot be
   recoloured, so it is used as a MASK with purple painted through it. Same
   artwork, same token, one colour different.

   The spacer it replaces is load-bearing: the engine puts a 22px pseudo
   element here purely to mirror the 22px toggle on the right, so the centred
   question centres on the CARD rather than on whatever the toggle left over.
   Widening the left without widening the right shifts every question 11px off
   centre, so the toggle matches. */
.accordion__summary::before {
  content: "";
  flex: 0 0 44px;
  height: 28px;
  background-color: var(--mim-purple);
  -webkit-mask: var(--img-mo-mark) left center / contain no-repeat;
  mask: var(--img-mo-mark) left center / contain no-repeat;
}
.accordion__toggle { flex: 0 0 44px; }

/* The purple has to lift off a near-black card, and the engine's night rules
   never touch a mask. */
body.night-mode .accordion__summary::before { background-color: #cdbcff; }

/* A feedback accordion is a different component that happens to share the
   class - a single tinted panel with its own left rule, not a question in a
   card - so it keeps the plain spacer and takes no moustache. This module has
   none today; the rule is here so that adding one later does not quietly grow
   a moustache. */
.accordion--feedback .accordion__summary::before {
  background: none;
  -webkit-mask: none;
  mask: none;
  flex-basis: 0;
}

@media (max-width: 600px) {
  .accordion__summary::before { flex-basis: 32px; height: 20px; }
  .accordion__toggle { flex: 0 0 32px; }
}

/* ── the navigation screen's surface ──────────────────────── */
/* Light, with the kit's white photocopy grain. Its field measures a mean of
   252 with a standard deviation of about 4, so the grain is real but quiet -
   a paper texture rather than a colour.

   Third attempt at this surface. Black (v0.38) read as too dark; the purple
   texture (v0.40) separated it from the FAQ but the lavender was too much.
   This separates them less, and the FAQ is flat white, so the difference is
   the grain rather than a tone. Worth knowing that the two may still read as
   siblings - the cards and the moustache marks are what actually distinguish
   them now.

   background-blend-mode: multiply over --mim-white is a no-op today and is
   there as the handle for a tint if a tonal step is wanted later: change the
   background-color and the grain survives instead of being flooded by it. */
.screen--nav {
  background-color: var(--mim-white);
  background-image: var(--tex-photocopy-white);
  background-blend-mode: multiply;
  background-size: cover;
  background-position: center;
}

/* ── navigation tutorial ─────────────────────────────── */
/* The engine styles the boxes themselves - .tutorial-step, __icon, __body,
   __title, __text, the hover and the focus ring. Everything here is what MIM
   Pro for GPs Part 1 adds on top, from Tom's round of 15 September 2026: each
   icon is a REPLICA of the control its row describes, so the learner is
   looking at the thing they will be looking for, rather than at seven
   identical purple chips.

   The header controls are transparent glyphs on a near-black bar, so a
   faithful copy on this light screen has to bring its own dark plate with it -
   otherwise it is a white glyph on white.

   Values are the live controls' own, per Tom's note:
     Continue  #btnNext   black pill, white 13px/600, radius 999px, 12px 26px
     Back      #btnPrev   #F1F2F2 on #4A4A4A, 1px rgba(0,0,0,.12), same pill
     the four  .btn-icon  40px, radius 8px, white-on-near-black */

.tutorial-step__icon--pill {
  width: auto; min-width: 0;
  padding: 12px 26px;
  border-radius: 999px;
  font-family: var(--font-primary);
  font-size: calc(13px * var(--fs-scale));
  font-weight: var(--weight-semibold);
  line-height: 1;
  white-space: nowrap;
  box-shadow: var(--depth-1);
}

.tutorial-step[data-target="next"] .tutorial-step__icon--pill {
  background: var(--mim-black); color: var(--mim-white); border: none;
}
.tutorial-step[data-target="prev"] .tutorial-step__icon--pill {
  background: #F1F2F2; color: #4A4A4A; border: 1px solid rgba(0,0,0,0.12);
}

.tutorial-step[data-target="menu"] .tutorial-step__icon,
.tutorial-step[data-target="support"] .tutorial-step__icon,
.tutorial-step[data-target="bug"] .tutorial-step__icon,
.tutorial-step[data-target="settings"] .tutorial-step__icon {
  width: 40px; height: 40px; flex: 0 0 40px;
  border-radius: 8px;
  background: rgba(10,10,12,0.92);
  border: 1px solid rgba(255,255,255,0.2);
  color: rgba(255,255,255,0.65);
  box-shadow: var(--depth-1);
}
.tutorial-step[data-target="menu"] .tutorial-step__icon svg,
.tutorial-step[data-target="support"] .tutorial-step__icon svg,
.tutorial-step[data-target="bug"] .tutorial-step__icon svg,
.tutorial-step[data-target="settings"] .tutorial-step__icon svg { width: 18px; height: 18px; }

.tutorial-step[data-target="support"] .tutorial-step__icon--text {
  font-size: calc(17px * var(--fs-scale));
  font-weight: var(--weight-semibold);
  color: rgba(255,255,255,0.85);
}

/* The seventh step points at a GESTURE, so there is no control to replicate.
   It keeps the engine's purple chip - which is now the thing that says so -
   and takes the same depth as the rest so it does not read as the one row
   nobody styled. */
.tutorial-step[data-target="none"] .tutorial-step__icon { box-shadow: var(--depth-1); }

.tutorial-step:hover .tutorial-step__icon { box-shadow: var(--depth-2); }

/* Night mode. The engine has no rules for these boxes, so without this the
   card stays var(--mim-grey-light) on a dark screen - a light panel on black.
   Darkening the card means the title and body have to come with it: the engine
   sets them from var(--fg) and var(--fg-muted), and night mode redefines
   neither, which is the dark-on-dark trap. */
body.night-mode .tutorial-step { background: #17171b; border-color: rgba(255,255,255,0.18); }
body.night-mode .tutorial-step__title { color: rgba(255,255,255,0.92); }
body.night-mode .tutorial-step__text { color: rgba(255,255,255,0.7); }

/* Stack the icon above the copy on a phone, as MIM Pro does. */
@media (max-width: 640px) {
  .tutorial-step { flex-direction: column; gap: 12px; }
}

/* ── "how this course works" ─────────────────────────── */
/* Three statements and no image. After the two duplicated bullets came out at
   v0.29 it was a narrow column of black text hanging off the top of a wide
   white screen, so this is a composition as much as a colour.

   The purple is ATTACHED to content rather than floated as decoration - a
   highlighter mark behind one word of the heading, and the three lead
   sentences in the AA-dark purple. Every mark in the MiM kit is attached to
   something; a smear in an empty corner reads as a stray graphic.

   --mim-purple-aa-dark, not --mim-purple: these are 16px sentences on white,
   which is the small-text case. The kit's own purple measures about 2:1 here. */

.screen--works.active {
  display: flex; flex-direction: column; justify-content: center;
}
.screen--works .screen-inner {
  flex: none;
  max-width: 900px;
  padding-top: clamp(24px, 4vh, 56px); padding-bottom: clamp(24px, 4vh, 56px);
}

.screen--works .body strong { color: var(--mim-purple-aa-dark); }
body.night-mode .screen--works .body strong { color: var(--mim-purple); }

/* The kit's artwork as a background behind a word, stretched past the text
   box so the mark reads as a stroke across it rather than a block under it.
   Named for the mark rather than for one screen, because more than one screen
   wants it. */
.mo-hl {
  background: url('./assets/mim/mark-highlighter-line-v2.png') center 66% / 100% 74% no-repeat;
  padding: 0 0.14em 0.04em;
  -webkit-box-decoration-break: clone; box-decoration-break: clone;
}

/* ── course objectives ────────────────────────────────── */
/* Arrow-marked text, no cards. The front matter had grown four boxed screens
   in a row and the boxes had stopped meaning anything, so the arrangement
   stays and the boxes go.

   One class, two shapes: a single column by default, two with --split. The
   marker is the outcomes screen's arrow in the AA-dark recolour, because these
   sit on light surfaces. Naming it for the arrow rather than for the
   objectives screen is what stops the next list minting a third class for the
   same rule - the mistake .works-hl made before it became .mo-hl. */

.mo-arrows {
  /* ⚠ NOT `margin: 0`. That is a shorthand, so it also zeroes the top, and
     the engine's stacking rhythm for blocks inside .screen-inner is a
     `margin-top` at the same specificity in an earlier file - which this
     would silently win. The list then sits flush against the paragraph
     introducing it, which is the bug this replaced. */
  list-style: none; margin: var(--sp-5) 0; padding: 0;
  display: grid; grid-template-columns: minmax(0, 1fr);
  row-gap: clamp(14px, 1.8vh, 24px);
  align-items: start;
}
/* Anu, 29 September 2026: screen 15 "is very busy". Four reasons, each a bold
   lead plus two or three lines of explanation, stacked tight enough that the
   leads stopped doing their job. The list stays fully visible - it is clinical
   content and putting it behind clicks would be the wrong fix - and instead
   each lead breaks onto its own line with real air between the items, so the
   screen reads as four short sections rather than four dense paragraphs. */
/* Second pass, 29 September: airier bullets were still bullets. The markers
   come off and the four reasons run two across, each its own short section.
   No box and no fill - a text grid, not cards, which the standing decision
   keeps for screen 7's tiles. */
.mo-reasons {
  list-style: none; margin: var(--sp-6) 0; padding: 0;
  display: grid; grid-template-columns: repeat(2, minmax(0, 1fr));
  column-gap: clamp(30px, 4vw, 60px);
  row-gap: clamp(26px, 3.2vh, 40px);
  align-items: start;
}
.mo-reasons li {
  font-family: var(--font-primary); font-weight: var(--weight-light);
  font-size: calc(15px * var(--fs-scale)); line-height: 1.55;
  color: var(--fg);
  padding-top: 13px;
  border-top: 2px solid var(--mim-purple-aa);
}
.mo-reasons li > strong:first-child {
  display: block;
  font-family: var(--font-feature);
  font-weight: var(--weight-regular);
  font-size: calc(17px * var(--fs-scale));
  line-height: 1.25;
  letter-spacing: -0.01em;
  margin-bottom: 7px;
  color: var(--fg);
}
body.night-mode .mo-reasons li,
body.night-mode .mo-reasons li > strong:first-child { color: rgba(255,255,255,0.9); }
body.night-mode .mo-reasons li { border-top-color: #cdbcff; }
@media (max-width: 720px) {
  .mo-reasons { grid-template-columns: minmax(0, 1fr); row-gap: 24px; }
}

/* The five actions run two across on a wide screen, so the take-home point is
   a band under the grid rather than another tall column. */
.keypoint-list--wide { grid-template-columns: repeat(2, minmax(0, 1fr)); column-gap: clamp(24px, 3vw, 48px); }
@media (max-width: 720px) {
  .keypoint-list--wide { grid-template-columns: minmax(0, 1fr); }
}

/* The take-home point: five instructions that were one paragraph. Same words,
   one per line, inside the same purple-ruled block. */
.keypoint--label { margin-bottom: 10px; }
.keypoint-list {
  list-style: none;
  margin: 0; padding: 0;
  display: grid; row-gap: 9px;
  max-width: var(--measure);
}
.keypoint-list li {
  position: relative; padding-left: 18px;
  font-family: var(--font-primary); font-weight: var(--weight-light);
  font-size: calc(16px * var(--fs-scale)); line-height: 1.5;
  color: var(--fg);
}
.keypoint-list li::before {
  content: ""; position: absolute; left: 0; top: 0.62em;
  width: 7px; height: 7px; border-radius: 999px;
  background: var(--mim-purple-aa);
}
body.night-mode .keypoint-list li { color: rgba(255,255,255,0.9); }
body.night-mode .keypoint-list li::before { background: #cdbcff; }

.mo-arrows--split { grid-template-columns: repeat(2, minmax(0, 1fr)); column-gap: clamp(28px, 4vw, 64px); row-gap: clamp(16px, 2.2vh, 30px); }
@media (max-width: 680px) {
  .mo-arrows--split { grid-template-columns: minmax(0, 1fr); row-gap: 16px; }
}

.mo-arrows li {
  position: relative;
  padding-left: clamp(22px, 2.2vw, 30px);
  font-family: var(--font-primary); font-weight: var(--weight-light);
  font-size: calc(16px * var(--fs-scale)); line-height: 1.5;
  color: var(--fg);
}
body.night-mode .mo-arrows li { color: rgba(255,255,255,0.88); }

.mo-arrows li::before {
  content: ""; position: absolute; left: 0; top: 0.34em;
  width: 24px; height: 19px;
  background: url('./assets/mim/mark-arrow-1-aa.png') center / contain no-repeat;
}
body.night-mode .mo-arrows li::before {
  background-image: url('./assets/mim/mark-arrow-1.png');
}

/* ── the objectives screen, sized to one view ───────────────── */
/* Banner, title and four tiles inside calc(100vh - header - footer) at the
   default text size, so the screen arrives whole with nothing below the fold.

   NOT done with overflow: hidden. The engine's text-size control scales every
   size here through --fs-scale, and a learner who turns it up has to be able to
   reach the last tile. So this fits by being smaller, and still scrolls when
   the text outgrows it - which is the reflow that accessibility actually
   requires. */
/* Scales with viewport HEIGHT, not width: what this screen is short of is
   vertical room. A short laptop gets a shallow banner so the last tile stays
   clear of the footer; a tall monitor gets a deeper one so the screen is not
   two thirds empty. */
/* The banner grows to fill whatever the text block does not use, instead of
   the slack sitting between them as white space. Centring the block put equal
   space above the heading and below the tiles, and the space above read as a
   gap between two things rather than as breathing room.
   
   So: the figure flexes, .screen-inner is sized to its content, and the text
   stays exactly where centring put it. The band above the heading becomes
   photograph.

   display goes on .screen--objectives.active, not on the class alone: the
   engine's .screen { display: none } is equal specificity and earlier in the
   cascade, so a bare display here would leave this screen rendered while
   inactive, below the live one. Same trap .screen--lo hit at v0.13.

   The banner keeps a min-height and yields space back as the text grows, so a
   learner on a larger text size loses banner rather than losing tiles. */
.screen--objectives.active { display: flex; flex-direction: column; }

/* flex-basis 0, not auto. With auto the figure's base size is the image's own
   height at full width - 411px for a 21:6 crop on a 1440px screen - and
   flex-grow then adds the leftover on top, which overflowed the screen and
   brought back the scrollbar. Based at 0 it takes only what the text leaves,
   with min-height as the floor. */
.screen--objectives .mo-figure--bleed {
  flex: 1 1 0;
  min-height: clamp(110px, 16vh, 240px);
  overflow: hidden;
}
.screen--objectives .mo-figure--bleed img {
  height: 100%; max-height: none; aspect-ratio: auto;
  object-fit: cover;
}

.screen--objectives .screen-inner {
  flex: none;
  max-width: 1100px;
  padding-top: clamp(22px, 3vh, 40px);
  padding-bottom: clamp(30px, 6vh, 92px);
}

/* This screen is sized to one view, so the list takes no bottom margin: it is
   the last block on it, and the default would push the screen into scroll. */
.screen--objectives .mo-arrows { margin: clamp(24px, 4vh, 52px) 0 0; }

/* ── rehearsal ─────────────────────────────────────────────────────────────
   Practice is the spine of this course: 3 rehearsals here, 14 across the four
   modules. Authored once as a pattern so every later one matches. */
.reveal-btn {
  display: inline-block; margin: var(--sp-5) 0;
  padding: 12px 22px; border: 2px solid var(--mim-purple-aa);
  border-radius: var(--radius-pill); background: none; cursor: pointer;
  font-family: var(--font-primary); font-weight: var(--weight-semibold);
  font-size: calc(15px * var(--fs-scale)); color: var(--mim-purple-aa-dark);
}
.reveal-btn:hover { background: rgba(132,99,248,.10); }
.rehearsal-panel[hidden] { display: none; }
.rehearsal-panel {
  border-left: 3px solid var(--mim-purple-aa); padding: var(--sp-4) 0 var(--sp-2) var(--sp-5);
  max-width: var(--measure);
}
/* Example phrasings are pull-quotes now (see above), nested one indent
   further in - drop the border so two purple bars don't sit side by side. */
.rehearsal-panel .pull-quote {
  border-left: none; padding-left: 0; margin: var(--sp-3) 0;
  font-family: var(--font-feature);
  font-size: calc(clamp(17px,1.7vw,20px) * var(--fs-scale)); line-height: 1.45;
}
body.night-mode .reveal-btn { color: var(--mim-purple); border-color: var(--mim-purple); }
body.night-mode .rehearsal-panel { border-left-color: var(--mim-purple); }

/* ── authored quiz ─────────────────────────────────────────────────────────
   A wrong answer ALWAYS reveals the correct one, in purple. Never green/red. */
.quiz-question { font-family: var(--font-primary); font-weight: var(--weight-semibold);
  font-size: calc(17px * var(--fs-scale)); color: var(--fg); max-width: var(--measure); }
.quiz-options { display: flex; flex-direction: column; gap: var(--sp-3);
  max-width: var(--measure); margin: var(--sp-5) 0; }
.quiz-option {
  text-align: left; padding: 16px 20px; cursor: pointer;
  border: 1px solid var(--border); border-radius: var(--radius-md);
  background: var(--mim-grey-light); font-family: var(--font-primary);
  font-size: calc(15px * var(--fs-scale)); line-height: 1.45; color: var(--fg);
}
.quiz-option:hover:not(:disabled) { border-color: var(--mim-purple-aa); }
.quiz-option:disabled { cursor: default; }
.quiz-option.correct,
.quiz-option.revealed-correct {
  border-color: var(--mim-purple-aa); background: rgba(132,99,248,.14);
  color: var(--mim-purple-aa-dark); font-weight: var(--weight-semibold);
}
.quiz-option.incorrect { border-color: var(--border-strong); opacity: .75; }
/* qa-quiz.js requires the revealed answer to carry a tick as well as the purple:
   colour alone must not be the only signal (WCAG 1.4.1). It looks for a
   ::after painted with mark-tick, which ships with the MiM design system. */
.quiz-option.correct, .quiz-option.revealed-correct { position: relative; padding-right: 56px; }
.quiz-option.correct::after, .quiz-option.revealed-correct::after {
  content: ""; position: absolute; right: 18px; top: 50%; transform: translateY(-50%);
  width: 22px; height: 22px;
  background: url("./assets/mim/mark-tick.png") center / contain no-repeat;
}
.quiz-feedback { max-width: var(--measure); margin-top: var(--sp-4);
  padding: var(--sp-4) var(--sp-5); border-radius: var(--radius-md);
  background: var(--mim-grey-light); }
body.night-mode .quiz-question { color: rgba(255,255,255,.92); }
body.night-mode .quiz-option { background: #17171b; border-color: rgba(255,255,255,.18); color: rgba(255,255,255,.9); }
body.night-mode .quiz-option.correct,
body.night-mode .quiz-option.revealed-correct { color: var(--mim-purple); border-color: var(--mim-purple); background: rgba(183,163,252,.16); }
body.night-mode .quiz-feedback { background: #17171b; }

/* ── part divider ──────────────────────────────────────────────────────── */

/* ── reduced motion ────────────────────────────────────────────────────────
   Never a blanket animation:none - it permanently hides opacity:0 reveals.
   Compress instead. Both entry points must stay in step. */
@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after { animation-duration: .01ms !important; animation-delay: 0ms !important; transition-duration: .01ms !important; }
}
body.reduced-motion *, body.reduced-motion *::before, body.reduced-motion *::after {
  animation-duration: .01ms !important; animation-delay: 0ms !important; transition-duration: .01ms !important;
}

/* ── SHAReT Module 2: the six-item level match ──────────────────
   Deck screen 20. Six things a clinician said, each one matched to a PLISSIT
   level. The deck replaced a drag-and-drop tile match with this at v3.0 -
   "drag and drop is not accessible" - so it is six ordinary button groups,
   which are keyboard-operable for nothing, and the engine's own .quiz-option
   styling does the marking, the tick and the purple.

   Everything here is layout plus one small label. The four options are short
   ("Permission", "Limited information"), so they go two across instead of
   taking a full-measure row each and making the screen six screens long. */
.lvl-set { display: flex; flex-direction: column; gap: var(--sp-6); margin-top: var(--sp-5); }

.lvl-item {
  max-width: var(--measure);
  padding-top: var(--sp-4);
  border-top: 1px solid var(--border);
}
.lvl-item:first-child { border-top: 0; padding-top: 0; }
body.night-mode .lvl-item { border-top-color: rgba(255,255,255,0.18); }

/* "1 of 6". Small text, so the AA-normal purple, not the large-text one. */
.lvl-item__num {
  font-family: var(--font-primary); font-weight: var(--weight-semibold);
  font-size: calc(12px * var(--fs-scale)); letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--mim-purple-aa-dark);
  margin: 0 0 var(--sp-3);
}
body.night-mode .lvl-item__num { color: #cdbcff; }

/* The quote being classified. .pull-quote already carries the feature face and
   the purple rule; inside an item it only needs its margins tightened so the
   options read as belonging to it. */
.lvl-item .pull-quote { margin-top: 0; margin-bottom: var(--sp-4); }

/* Two across, and one across on a narrow screen. Overrides the engine's
   `.quiz-options { flex-direction: column }` for this variant only. */
.quiz-options--levels {
  display: grid; grid-template-columns: 1fr 1fr;
  gap: var(--sp-3); margin: 0;
}
.quiz-options--levels .quiz-option { padding: 13px 16px; }
@media (max-width: 640px) {
  .quiz-options--levels { grid-template-columns: 1fr; }
}

/* The feedback sits under the four, not under one of them. */
.lvl-item .mcq-fb { margin-top: var(--sp-4); }

/* ── Anu's review, 1 October 2026 ──────────────────────────────────────────
   Six fixes from the first click-through of batch 1. Each one is here rather
   than in the markup because each is a measure or an alignment, not content. */

/* [screen 3] The statistic sat on `align-items: baseline`, which pins the
   first line of a five-line paragraph to the baseline of a 72px number and
   leaves the rest hanging below it - "the alignment of this number looks a bit
   off". Centred against the paragraph instead. `baseline` is right when the
   text beside a figure is one line, which is the case the device was drawn
   for, so this is the module's call and not a change to the component. */
.mo-stat { align-items: center; }

/* [screen 5] The film screen centres everything: title, lead, body and the
   tile. Anu: "Align the whole content including the title, video etc to the
   centre."

   ⚠ The engine caps `.body`, `.section-lead` and `.title` at `--measure` and
   aligns them left, so centring the text needs `margin-inline: auto` on each
   as well as `text-align: center` - without the auto margins the block stays
   left and only its text centres inside it, which reads as a mistake rather
   than a decision. The video stack is positioned by `--video-ml` on `.screen`,
   which is the engine's single source of truth for video width and offset, so
   it is set to `auto` here rather than re-declared on the frame. CLAUDE.md
   forbids re-declaring a width or margin on a video wrapper, and this is the
   supported way to move it. */
/* ⚠ CENTRING THE CHILDREN IS NOT CENTRING THE SCREEN. `.screen-inner` is
   capped at the measure and sits LEFT in the column, so with only the children
   centred the whole block still read as left-aligned, just with ragged edges -
   Anu, 1 October 2026: "not properly centred yet". The wrapper has to be
   centred too, and then everything inside it lines up on the page's centre. */
.screen--film .screen-inner { text-align: center; margin-inline: auto; }
.screen--film .title,
.screen--film .section-lead,
.screen--film .body,
.screen--film .eyebrow { margin-inline: auto; }
.screen--film { --video-ml: auto; }
.screen--film .video-frame { margin-inline: auto; }

/* [screen 7] The key point wrapped a word earlier than the paragraphs above
   it, so a two-line statement ran to three. `.keypoint-wrap` is capped at
   `--measure` AND carries an 18px left padding for its purple rule, so the
   text inside only ever had `--measure` minus 18px. Give the wrap the rule's
   width on top of the measure and the type inside lines up with `.body`.
   Anu: "the statement needs to wrap at the same level as the content above". */
.keypoint-wrap { max-width: calc(var(--measure) + 18px); }
@media (max-width: 600px) { .keypoint-wrap { max-width: calc(var(--measure) + 14px); } }

/* [screen 8] The save control on an example phrasing. Same class and same
   wording as the bookmark button Module 1 puts on an evidence paper, so a
   learner meets one control in both modules and the resources screen at deck
   80 reads one attribute. */
.mo-savable { max-width: var(--measure); margin: var(--sp-5) 0; }
.mo-savable .pull-quote { margin-bottom: 10px; }
.mo-savable .res-save { margin-left: 22px; }

/* [screen 10] MIM Pro's image accordion, ported from its GP Part 1 module.css
   (screen 22). Behaviour is the engine's own toggleReveal on `.reveal-item`.

   ⚠ SCOPED TO `.reveal-stack`. MIM Pro declares these on a bare `.reveal-item`,
   which in this module is also what every `.mo-reveal` is built on - an
   unscoped copy would have put a 240px photograph slot, a border and a grey
   ground on all 21 of the module's plain reveals. */
.reveal-stack { display: flex; flex-direction: column; gap: 16px; max-width: var(--measure); }
.reveal-stack .reveal-item {
  border: 1px solid var(--border); border-radius: var(--radius-lg);
  overflow: hidden; background: var(--mim-grey-light);
}
.reveal-stack .reveal-item__head {
  position: relative; display: block; width: 100%;
  padding: 0; border: none; background: none; cursor: pointer;
}
.reveal-stack .reveal-item__img {
  width: 100%; height: 240px; object-fit: cover; display: block;
  transition: transform var(--dur-base) var(--ease-standard);
}
.reveal-stack .reveal-item__head:hover .reveal-item__img { transform: scale(1.02); }
.reveal-stack .reveal-item__head::after {
  content: ""; position: absolute; inset: 0;
  background: linear-gradient(180deg, rgba(0,0,0,0) 35%, rgba(0,0,0,0.55));
}
/* ⚠ THE TITLE CARRIES ITS OWN PLATE, NOT JUST MIM PRO'S GRADIENT SCRIM. Two
   reasons, and the second is the one that matters:
     1. `audit.js` cannot resolve a gradient painted on a ::after - it walks up
        to the first opaque background and measured white on the card's own
        #F1F2F2, reporting all three titles as invisible text. Three real gate
        hits on the first run.
     2. More to the point, the gate was not wrong to worry. MIM Pro's images
        are shot and known; these three are placeholders, and the brief asks
        for a quiet lower third but cannot guarantee a dark one. A gradient
        over a pale photograph is white on pale.
   A near-opaque plate across the foot of the frame is legible over any image,
   measurable by the gate, and close enough to MIM Pro's treatment that the two
   read as the same component. The gradient stays above it, softening the join. */
.reveal-stack .reveal-item__title {
  position: absolute; left: 0; right: 0; bottom: 0; z-index: 2;
  padding: 18px 24px;
  background: rgba(17, 17, 20, 0.82);
  font-family: var(--font-feature);
  font-size: calc(24px * var(--fs-scale)); line-height: 1.1;
  color: #fff; text-align: left;
}
.reveal-stack .reveal-item__chev {
  position: absolute; right: 20px; bottom: 18px; z-index: 2; color: #fff;
  font-size: 30px; line-height: 1;
  transition: transform var(--dur-base) var(--ease-standard);
}
.reveal-stack .reveal-item.open .reveal-item__chev { transform: rotate(45deg); }
.reveal-stack .reveal-item__body { display: none; padding: 24px 28px 28px; }
.reveal-stack .reveal-item.open .reveal-item__body { display: block; }
.reveal-stack .reveal-item__body > * + * { margin-top: var(--sp-4); }
.reveal-stack .reveal-item__body .body,
.reveal-stack .reveal-item__body .list { max-width: none; }
body.night-mode .reveal-stack .reveal-item { background: #17171b; }

/* ── Anu's second review, 1 October 2026 ───────────────────────────────────
   "There are many different ways information is presented in the MimPro
   module - adapt this." So no two consecutive screens in this module now use
   the same device: hotspots, image rows, phrase cards, flipcards, a plain
   accordion, tabs and a select-and-swap panel, each one either already in the
   engine or ported here from MIM Pro's own module CSS. */

/* [screens 8, 11, 17, 21] Example phrasings, ported from MIM Pro's
   components/annotated-portrait/annotated-portrait.css. Its own note is worth
   keeping: no cursor, no hover, no focus ring - a phrase card is something to
   read and say, not a control. Where this module adds a save button, that
   button is the control and the card still is not. */
.mo-phrases__label {
  font-family: var(--font-primary); font-weight: var(--weight-semibold);
  font-size: calc(11px * var(--fs-scale)); letter-spacing: 0.14em;
  text-transform: uppercase; color: var(--mim-purple-aa-dark);
  margin: 18px 0 8px;
}
body.night-mode .mo-phrases__label { color: #cdbcff; }
.mo-phrases {
  list-style: none; display: flex; flex-direction: column; gap: 10px;
  margin: 0 0 var(--sp-5); padding: 0; max-width: var(--measure);
}
.mo-phrases__label + .mo-phrases { margin-top: 0; }
.mo-phrase {
  display: flex; align-items: center; gap: 14px;
  padding: 14px 16px;
  border: 1px solid var(--border); border-radius: 10px;
  background: var(--mim-white);
}
/* ⚠ NOT ITALIC, AND NOT THE FEATURE FACE. MIM Pro sets a phrase in Lora
   italic at 15px, which is what was ported, and Anu, 1 October 2026: *"the
   curly text inside the boxes - is that MimPro style. I think it's a bit hard
   to read."* It is MIM Pro's style, and she is still right: a whole clinical
   script in 15px italic serif is a different proposition from MIM Pro's
   one-line openers, and these run to two and three lines. The quotation marks
   the deck already puts round every phrasing are what mark it as speech, so
   the italic was doing nothing the copy was not already doing.

   Primary face, upright, 16px. The card, the label and the border are MIM
   Pro's and unchanged, so the two still read as the same component. */
.mo-phrase__text {
  flex: 1;
  font-family: var(--font-primary); font-weight: var(--weight-regular);
  font-size: calc(16px * var(--fs-scale)); line-height: 1.5; color: var(--fg);
}
/* ⚠ `.mo-phrase__text` declares `color: var(--fg)`, and night mode does not
   redefine --fg - the trap this module has now hit three times. The card gets
   a night ground AND the type inside it gets a night colour. */
body.night-mode .mo-phrase { background: #17171b; border-color: rgba(255,255,255,0.18); }
body.night-mode .mo-phrase__text { color: rgba(255,255,255,0.92); }

/* ⚠ ONE ACCENT, NOT THREE. There were three - --rule, --dot and --plain -
   under "Vary this slightly so it doesn't look monotonous" (Anu, 1 October
   2026). Anu, 7 October 2026: *"All questions should be in a similar format
   box and design. Keep it consistent throughout the module."* So --dot's
   bullet and --plain's grey fill are gone, and every phrase set in the module
   is this one card. See PHRASE_VARIANT in build_m2_batch.py - the build emits
   only this class now, so adding a rule for either of the other two here
   would style nothing. */
.mo-phrases--rule .mo-phrase { border-left: 3px solid var(--mim-purple-aa); }

/* One save control for the whole set, after the last of them. */
.mo-saveall {
  display: flex; align-items: center; gap: 16px; flex-wrap: wrap;
  margin: var(--sp-4) 0 var(--sp-5); max-width: var(--measure);
}
.mo-saveall__note {
  flex: 1 1 260px; margin: 0;
  font-family: var(--font-primary); font-weight: var(--weight-light);
  font-size: calc(14px * var(--fs-scale)); line-height: 1.45;
  color: var(--fg-muted);
}
body.night-mode .mo-saveall__note { color: rgba(255,255,255,0.68); }
.res-save--all { flex: 0 0 auto; }

/* Even spacing round a phrase set. The label, the cards and whatever follows
   were each bringing their own margin, which is what "spacing is not even"
   was: 18px above a label that followed a 32px paragraph gap, and a 4px tail.
   One rhythm for the whole block. */
.mo-phrases__label { margin: var(--sp-5) 0 10px; }
.mo-phrases { margin-bottom: var(--sp-5); }
.mo-phrases:last-child { margin-bottom: 0; }

/* [screen 13] MIM Pro's clickable bullets, ported from its GP Part 1
   module.css. Two long outcomes become two tiles. */
.reveal-list {
  display: grid; grid-template-columns: repeat(2, 1fr); gap: 12px;
  max-width: var(--measure); margin: var(--sp-4) 0 var(--sp-5);
}
@media (max-width: 640px) { .reveal-list { grid-template-columns: 1fr; } }
.reveal-bullet {
  min-height: 72px; display: flex; align-items: center; gap: 12px;
  padding: 16px 20px; text-align: left;
  background: var(--mim-grey-light);
  border: 1.5px solid var(--border); border-radius: var(--radius-md);
  font-family: var(--font-primary); font-size: var(--fs-body); line-height: 1.45;
  color: var(--fg); cursor: pointer;
  transition: border-color var(--dur-base) var(--ease-standard),
              background var(--dur-base) var(--ease-standard);
}
.reveal-bullet:hover:not(.revealed) { border-color: var(--mim-purple-aa); background: rgba(132,99,248,0.07); }
.reveal-bullet__face {
  font-weight: var(--weight-semibold); letter-spacing: 0.06em;
  text-transform: uppercase; font-size: calc(12px * var(--fs-scale));
  color: var(--mim-purple-aa-dark);
}
.reveal-bullet.revealed {
  cursor: default; background: rgba(132,99,248,0.10);
  border-color: var(--mim-purple-aa); font-weight: var(--weight-light);
  align-items: flex-start;
}
.reveal-bullet.revealed::before {
  content: ""; flex-shrink: 0; width: 8px; height: 8px; border-radius: 50%;
  background: var(--mim-purple-aa); margin-top: 8px;
}
/* ⚠ revealBullet REPLACES the button's textContent, so .reveal-bullet__face is
   gone by then and the revealed type is the BUTTON's own. Night mode has to
   colour the button, not the face. */
body.night-mode .reveal-bullet { background: #17171b; color: rgba(255,255,255,0.92); }
body.night-mode .reveal-bullet__face { color: #cdbcff; }

/* [screen 22] The hotspot markers, restyled as a row of boxes. The component
   starts closed, which is the whole reason this screen is not tabs. */
/* ⚠ ONE COLUMN PER TILE, NOT repeat(3, 1fr) AND NOT auto-fit. This was
   hardcoded to three while the only screen using it had three tiles; deck 37
   brought two, and a fixed three-column grid leaves the second tile
   half-width with a hole beside it. `auto-fit` with a minmax was the first
   try and it laid out 342px 342px 0px - an empty third track and a trailing
   gap. `grid-auto-flow: column` makes exactly as many equal columns as
   there are tiles, whatever a later screen brings. */
.mim-hotspots--row .mim-hotspots__row {
  display: grid; grid-auto-flow: column; grid-auto-columns: 1fr;
  gap: var(--sp-4); max-width: var(--measure); margin-bottom: var(--sp-3);
}
@media (max-width: 760px) {
  .mim-hotspots--row .mim-hotspots__row {
    grid-auto-flow: row; grid-auto-columns: auto;
  }
}
/* Every geometric property of the circle is undone: it is a box here. */
.mim-hotspots--row .mim-hotspots__marker {
  position: static; transform: none;
  width: auto; height: auto; min-height: 64px; border-radius: var(--radius-md);
  padding: 16px 18px; text-align: center;
  border-width: 1.5px; border-color: var(--border);
  background: var(--mim-grey-light); color: var(--fg);
  font-family: var(--font-primary); font-weight: var(--weight-semibold);
  font-size: calc(17px * var(--fs-scale)); line-height: 1.25;
  box-shadow: none;
}
.mim-hotspots--row .mim-hotspots__marker:hover {
  transform: none; border-color: var(--mim-purple-aa);
}
.mim-hotspots--row .mim-hotspots__marker.is-active {
  transform: none; background: var(--mim-black); color: var(--mim-white);
  border-color: var(--mim-black);
}
body.night-mode .mim-hotspots--row .mim-hotspots__marker {
  background: #17171b; color: rgba(255,255,255,0.92); border-color: rgba(255,255,255,0.22);
}
body.night-mode .mim-hotspots--row .mim-hotspots__marker.is-active {
  background: var(--mim-purple); color: var(--mim-black); border-color: var(--mim-purple);
}

/* [screen 7] Markers over a drawn diagram rather than a photograph. The SVG
   sits where the <img> normally would, so the figure needs the height the
   image would have given it. */
.mim-hotspots--drawn .mim-hotspots__figure { line-height: 0; }
.mim-hotspots--drawn .mim-hotspots__figure svg { width: 100%; height: auto; display: block; }
/* On a white ground the marker's drop shadow is the only thing separating it
   from the diagram, and the diagram is pale purple - so it takes a solid
   purple fill instead of white, to read against the fields it sits in. */
.mim-hotspots--drawn .mim-hotspots__marker {
  background: var(--mim-purple-aa-dark); color: var(--mim-white);
  border-color: var(--mim-white);
}
.mim-hotspots--drawn .mim-hotspots__marker.is-active {
  background: var(--mim-black); color: var(--mim-white); border-color: var(--mim-white);
}

/* ── the landing and the outcomes page, 1 October 2026 ─────────────────────
   Anu asked for Module 1's screen 1 and an outcomes page after it. Both
   components are already in this file - it is Module 1's module.css, copied
   whole - so these are the two differences Module 2 needs.

   [screen 1] A TEXTURE HERO, NOT A PHOTOGRAPH, which is deck 1's own brief.
   ⚠ `.hero__tex` CANNOT DO IT. It paints the photocopy texture with
   `mix-blend-mode: multiply`, and multiply against the hero's black ground is
   black - the texture would have been invisible, which is the kind of thing
   that looks like a missing asset rather than a wrong blend mode. The dividers
   get it right by painting colour and image together on one background
   (module.css line ~1633), and that is what is used here, on `.hero__media`
   so the scrim still sits over it. */
.screen--hero-tex .hero__media {
  background: var(--mim-black) var(--tex-photocopy-black) center / cover;
}
/* The scrim is tuned for a photograph; over a texture it only needs to sink
   the foot of the frame enough for the copy to hold. */
.screen--hero-tex .hero__scrim {
  background: linear-gradient(0deg, rgba(0,0,0,0.92) 0%, rgba(0,0,0,0.74) 42%, rgba(0,0,0,0.34) 100%);
}
/* "MODULE 2" over the title, and "Where we have got to" over its paragraphs:
   the deck's own two labels, set as the kicker and a small label so neither
   competes with the title. */
.screen--cinematic .hx-kicker {
  font-family: var(--font-primary); font-weight: var(--weight-semibold);
  font-size: calc(12px * var(--fs-scale)); letter-spacing: 0.18em;
  text-transform: uppercase; color: rgba(255,255,255,0.72);
  margin: 0 0 10px;
}
.screen--cinematic .hx-label {
  font-family: var(--font-primary); font-weight: var(--weight-semibold);
  font-size: calc(11px * var(--fs-scale)); letter-spacing: 0.14em;
  text-transform: uppercase; color: var(--mim-purple);
  margin: 0 0 8px;
}

/* [screen 2] Deck 3 closes on a sentence about practising four times, which is
   neither an outcome nor the lead-in to the list. It sits under the two
   columns at body size rather than inside them. */
.lo-after {
  font-family: var(--font-primary); font-weight: var(--weight-light);
  font-size: var(--fs-body); line-height: 1.55;
  margin-top: var(--sp-4); max-width: var(--measure);
  /* ⚠ NOT `var(--fg)`. THE OUTCOMES SCREEN IS DARK IN BOTH MODES - the
     band sits on #0b1519 and the body on near-black - so `--fg` put BLACK TEXT
     ON A BLACK SCREEN, and the closing sentence about practising four times
     was simply not there. Its neighbours already knew: `.lo-leadin` is
     rgba(255,255,255,1) and `.lo-list li` is 0.85, both hardcoded for exactly
     this reason. Night mode is irrelevant here; the screen never has a light
     state to switch from.

     ⚠ AND `audit.js` DID NOT CATCH IT. `backdrop()` returns `{img:true}`
     and skips the check as soon as it meets an ancestor with a
     background-image, which `.screen--lo` has - so the gate passed clean while
     the text was invisible. A screen that paints a texture is outside that
     gate's reach, and on those the eye is the only check. */
  color: rgba(255,255,255,0.85);
}
.lo-after strong { color: #fff; font-weight: var(--weight-semibold); }

/* [screen 6] One accordion per row. Anu, 1 October 2026: *"Can't we make this
   one accordion per row?"* `.mo-cards` is a grid - Module 1 uses it three
   across - so four problems across one row turned each into a narrow column
   of stacked words. Nothing else about the card changes. */
.mo-cards--stack { display: flex; flex-direction: column; gap: var(--sp-3); }
.mo-cards--stack .mo-card { width: 100%; }

/* [screen 16] The timeline.

   ⚠ THE SHADOW IS THE ENGINE'S, AND IT IS ON EVERY BUTTON IN EVERY SCREEN.
   `mim-gp-shared.css:1641` reads `.screen button, .screen summary, .screen
   [role="button"] ... { box-shadow: var(--depth-1) }`. A timeline toggle is a
   bare transparent button, so it was casting a 14px/30px drop shadow with no
   surface to cast it - a shadow hanging in mid-air, still there with the step
   open, which is what Anu saw: *"the shadow still lingers when we expand the
   accordion."*

   Elevation is a fact about the ground a control renders ON, and there is no
   raised ground here. The engine rule is shared by twelve modules, so it is
   overridden for this component rather than changed.

   Also, *"move the icons slightly inwards to fit them nicely. They are sitting
   right at the borders."* Both were: the `+` is `margin-left: auto` with
   nothing after it, so it sat hard on the right edge of the measure, and the
   step's circle is at `left: -29px` against a 34px rail, which left it 5px
   from the left edge. The rail widens and the toggle gains a right inset. */
/* ⚠ AND IT HAS TO OUT-SPECIFY `.screen button`, WHICH IS (0,1,1). The first
   attempt at this was a bare `.mim-timeline__toggle` - (0,1,0), one class
   against the engine's one class plus one element - so the engine kept
   winning and the shadow was still there on the next build. Anu had already
   told me once; a second "it's still there" would have been mine to own. Every
   rule here is scoped with `.screen` to reach (0,2,0).

   The three controls below are the ones where the shadow is plainly wrong:
   all three have a TRANSPARENT background, so there is no raised surface to
   cast it and the shadow hangs in mid-air under the type. `.ap-note` sits over
   a photograph, which makes it worse.

   The ones NOT touched have real surfaces and keep their elevation:
   `.bob__brick`, `.reveal-bullet`, the level tiles, the hotspot markers. And
   `.reveal-btn` is Module 1's own row, shipping like that in Module 1 - not
   this module's call to change. */
.screen .mim-timeline__toggle,
.screen .mim-timeline__toggle:hover,
.screen .ap-note,
.screen .ap-note:hover,
.screen .reveal-stack .reveal-item__head { box-shadow: none; }

.screen .mim-timeline__toggle { padding-right: 14px; }
.mim-timeline { padding-left: 46px; }
.mim-timeline::before { left: 20px; }
.mim-timeline__step::before { left: -34px; }
@media (max-width: 600px) {
  .mim-timeline { padding-left: 38px; }
  .mim-timeline::before { left: 15px; }
  .mim-timeline__step::before { left: -30px; }
}

/* The timeline's step marker carries the framework's letters, so it needs room
   for two characters rather than a dot. */
.mim-timeline__date {
  font-family: var(--font-primary); font-weight: var(--weight-semibold);
  letter-spacing: 0.08em; margin-right: 8px;
}

/* ── the practical toolkit, from MIM Pro for GPs Part 2 ────────────────────
   Anu, 1 October 2026: *"Can you refer to this module which is Part 2 of the
   same course and adapt this to our course as well?"*

   `.ap` is the one pattern in Part 2 with a home the storyboard has already
   asked for: deck 19's media brief reads *"Downloadable card carrying the 6
   scripts on this screen ... Format to match the MiM Pro resource card"*, and
   deck 23's says *"Downloadable script card, as for screen 19."* So this is
   the briefed component rather than a borrowed one, and building it closes an
   open item instead of adding one.

   The BEHAVIOUR is already ours: `_components/annotated-portrait.js` is in
   this repo, shared by GP2/3, Pharmacy2/3 and Nurses2/3, and its own header
   says never to keep a per-module copy. It injects the "TRY THIS" label, the
   phrase rows, the research blocks, the progress arrow and the completion
   tick, and sets `data-ap-complete` for ENGAGE_GATE. Nothing is authored for
   any of that.

   What is NOT ours is the CSS: it lives in MIM Pro's own
   components/annotated-portrait/annotated-portrait.css, not in the engine, so
   it is ported here.

   ⚠ FOUR TOKENS IN THAT FILE DO NOT EXIST IN THIS KIT, and a declaration
   using an undefined custom property is DROPPED SILENTLY - it does not fall
   back, it just stops applying. MIM Pro writes `--s4`/`--s5` where this kit
   writes `--sp-4`/`--sp-5`, and `--mo-grey` is its own. `--ap-pos` is set
   inline per instance and is fine. Each one is translated below rather than
   copied, which is the whole reason this port is read line by line instead of
   pasted. */
.ap { --ap-pos: 66% 24%; margin: var(--sp-5) 0 var(--sp-4); max-width: var(--measure); }

.ap-frame {
  position: relative; margin: 0; height: 660px;
  border-radius: var(--radius-lg, 16px); overflow: hidden;
  box-shadow: var(--shadow-md);
}
.ap-photo {
  position: absolute; inset: 0; width: 100%; height: 100%;
  object-fit: cover; object-position: var(--ap-pos);
}
/* ⚠ THE SCRIM MOVES UP ONE LEVEL, FROM `.ap-scrim` TO `.ap-content`. MIM Pro
   paints it on `.ap-scrim`, which is a SIBLING of the element holding the
   type - so nothing in the type's own ancestor chain is dark, and `audit.js`
   walked straight past it to the page's white and reported the eyebrow, the
   headline and all four move titles as invisible white-on-white. Seven hits.
   (`backdrop()` at `tools/audit.js:78` walks ancestors and composites them; a
   sibling is invisible to it, and correctly so - it cannot know what paints
   over what.)

   Both layers cover the frame identically, so moving the gradient to
   `.ap-content` renders the same and puts it where the gate can see it. It
   lands as a `background-image`, which is the case audit.js deliberately skips
   rather than guesses at - the honest answer for type over a photograph.
   `.ap-scrim` stays in the markup, because the authoring contract in
   `annotated-portrait.js` includes it, and paints nothing.

   The left band is also darkened from MIM Pro's 0.90/0.62 to 0.93/0.74,
   because their photographs are shot and cleared and PLACEHOLDER-25 is a pale
   stand-in. A gradient tuned to a known image is not a guarantee on an
   unknown one. */
.ap-scrim { position: absolute; inset: 0; }
.ap-content {
  position: absolute; inset: 0; display: flex; flex-direction: column;
  justify-content: space-between; padding: 36px 40px;
  background-image:
    linear-gradient(90deg, rgba(0,0,0,0.93) 0%, rgba(0,0,0,0.74) 42%, rgba(0,0,0,0.08) 80%),
    linear-gradient(0deg,  rgba(0,0,0,0.82) 0%, rgba(0,0,0,0) 48%);
}
.ap-head { max-width: 60%; }
.ap-eyebrow {
  font-family: var(--font-primary); font-weight: var(--weight-semibold);
  font-size: calc(12px * var(--fs-scale)); letter-spacing: 0.16em;
  text-transform: uppercase; color: rgba(255,255,255,0.72); margin: 0 0 10px;
}
.ap-headline {
  font-family: var(--font-feature); font-weight: var(--weight-medium);
  font-size: calc(clamp(30px, 4.4vw, 40px) * var(--fs-scale)); line-height: 1.08;
  color: var(--mim-white); margin: 0;
}
.ap-hl { position: relative; display: inline-block; padding: 0 2px; }
/* ⚠ THE MARK PATH IS RELATIVE TO THE STYLESHEET, and MIM Pro's sits one
   folder deeper than this one - its `../../assets/mim/...` resolves to
   nothing from a module root. The kit's own highlighter PNG is here. */
.ap-hl::before {
  content: ""; position: absolute; left: -4px; right: -4px; bottom: 0.08em;
  height: 0.5em; z-index: -1;
  background: url('./assets/mim/mark-highlighter-line-v2.png') center/100% 100% no-repeat;
}
.ap-notes-label {
  font-family: var(--font-primary); font-weight: var(--weight-semibold);
  font-size: calc(13px * var(--fs-scale)); letter-spacing: 0.16em;
  text-transform: uppercase; color: var(--mim-purple); margin: 0 0 12px;
}
.ap-notes { display: flex; flex-direction: column; gap: 4px; max-width: 460px; }
.ap-note {
  position: relative; display: flex; align-items: center; gap: 16px;
  width: 100%; padding: 10px 12px; border: 0; background: transparent;
  border-radius: 12px; text-align: left; cursor: pointer;
  transition: background 0.16s var(--ease-standard);
  -webkit-tap-highlight-color: transparent;
}
.ap-note:hover { background: rgba(255,255,255,0.09); }
.ap-note:focus-visible { outline: 2px solid var(--mim-purple); outline-offset: 2px; }
.ap-num {
  position: relative; flex: 0 0 auto; width: 52px; height: 52px;
  display: grid; place-items: center;
  background: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 52 52'%3E%3Cpath d='M27 5 C41 5.5 48 15 47 27 C46 39 36 48.5 24 47 C11.5 45.5 5 35 6 23 C7 12.5 15.5 5.5 28.5 5.2' fill='none' stroke='%23B7A3FC' stroke-width='2.3' stroke-linecap='round' opacity='0.82'/%3E%3C/svg%3E") center/contain no-repeat;
  font-family: var(--font-feature); font-size: calc(19px * var(--fs-scale));
  color: var(--mim-white); letter-spacing: 0.02em;
  animation: ap-num-pulse 2.2s ease-in-out infinite;
}
@keyframes ap-num-pulse { 0%, 100% { transform: scale(1); } 50% { transform: scale(1.08); } }
/* ⚠ BOTH REDUCED-MOTION ENTRY POINTS, kept in step. A body class cannot
   un-match a media query, so the setting and the query are authored in
   parallel - the engine's standing rule. */
@media (prefers-reduced-motion: reduce) { .ap-num { animation: none; } }
body.reduced-motion .ap-num { animation: none; }
.ap-note-title {
  font-family: var(--font-primary); font-weight: var(--weight-semibold);
  font-size: calc(16px * var(--fs-scale)); line-height: 1.25; color: var(--mim-white);
}

/* The modal the shared script opens. */
.ap-modal-backdrop {
  position: fixed; inset: 0; background: rgba(0,0,0,0.55);
  display: flex; align-items: center; justify-content: center;
  padding: 24px; z-index: 1000;
  opacity: 0; visibility: hidden; pointer-events: none;
  transition: opacity 0.18s var(--ease-standard), visibility 0.18s var(--ease-standard);
}
.ap-modal-backdrop.is-open { opacity: 1; visibility: visible; pointer-events: auto; }
.ap-modal {
  background: var(--mim-white); border-radius: 14px; box-shadow: var(--shadow-lg);
  width: 100%; max-width: 560px; max-height: 86vh; overflow-y: auto;
  padding: 32px 34px 34px; position: relative;
  transform: translateY(8px); transition: transform 0.2s var(--ease-out);
}
.ap-modal-backdrop.is-open .ap-modal { transform: none; }
.ap-modal__close {
  position: absolute; top: 16px; right: 16px; width: 34px; height: 34px;
  display: grid; place-items: center;
  border: 1px solid var(--border); background: var(--mim-white);
  border-radius: 999px; cursor: pointer; color: var(--fg-muted);
  font-size: 18px; line-height: 1;
  transition: background 0.16s var(--ease-standard), color 0.16s var(--ease-standard);
}
.ap-modal__close:hover { background: var(--mim-black); color: var(--mim-white); }
.ap-modal__num {
  font-family: var(--font-mono); font-size: calc(13px * var(--fs-scale));
  letter-spacing: 0.1em; color: var(--mim-purple-aa-dark); margin: 0 0 6px;
}
.ap-modal__title {
  font-family: var(--font-feature); font-weight: var(--weight-semibold);
  font-size: calc(24px * var(--fs-scale)); line-height: 1.15;
  color: var(--mim-black); margin: 0 0 18px; padding-right: 40px;
}
.ap-modal__body p {
  font-family: var(--font-primary); font-weight: var(--weight-light);
  font-size: calc(15px * var(--fs-scale)); line-height: 1.6;
  color: var(--fg-muted); margin: 0 0 14px; max-width: none;
}
.ap-modal__body ul.ap-body-list,
.ap-modal__body ul.list { margin: 0 0 14px; padding-left: 20px; list-style: disc; max-width: none; }
.ap-modal__body ul li {
  font-family: var(--font-primary); font-weight: var(--weight-light);
  font-size: calc(15px * var(--fs-scale)); line-height: 1.55;
  color: var(--fg-muted); margin: 0 0 6px;
}
body.night-mode .ap-modal__body ul li { color: rgba(255,255,255,0.75); }

/* The phrase rows the shared script injects. ⚠ IT USES `.ap-phrase`, NOT
   `.mo-phrase`, so Anu's legibility change has to be made here as well or the
   toolkit would be the one place in the module still setting a clinical script
   in 15px Lora italic - the exact thing she asked to be fixed an hour earlier.
   Same treatment as `.mo-phrase__text`; the card, the border and the TRY THIS
   label stay MIM Pro's. */
.ap-phrases__label {
  font-family: var(--font-primary); font-weight: var(--weight-semibold);
  font-size: calc(11px * var(--fs-scale)); letter-spacing: 0.14em;
  text-transform: uppercase; color: var(--mim-purple-aa-dark); margin: 18px 0 8px;
}
.ap-phrases__label + .ap-phrases { margin-top: 0; }
.ap-phrases {
  list-style: none; display: flex; flex-direction: column; gap: 10px;
  margin: 18px 0 4px; padding: 0;
}
.ap-phrase {
  display: flex; align-items: center; gap: 14px; width: 100%; text-align: left;
  padding: 14px 16px; border: 1px solid var(--border); border-radius: 10px;
  background: var(--mim-white);
}
.ap-phrase__text {
  flex: 1;
  font-family: var(--font-primary); font-weight: var(--weight-regular);
  font-size: calc(16px * var(--fs-scale)); line-height: 1.5; color: var(--fg);
}

/* Progress: the blinking arrow on the first unopened move, and the tick over
   one already seen. Both injected by the shared script. */
.ap-arrow {
  position: absolute; left: -22px; top: 50%; width: 18px; height: 18px;
  margin-top: -9px; color: var(--mim-purple); pointer-events: none;
  animation: ap-arrow-blink 1.15s ease-in-out infinite;
}
.ap-arrow svg { width: 100%; height: 100%; display: block; }
/* ⚠ ENDS ON opacity 1. The arrow carries meaning, so when reduced motion
   compresses the animation to a stop it has to stay VISIBLE and keep pointing.
   A blanket `animation: none` on an opacity-0 keyframe is how a meaningful
   mark disappears for the people who most need it. */
@keyframes ap-arrow-blink { 0%, 100% { opacity: 1; } 50% { opacity: 0.2; } }
@media (max-width: 720px) {
  .ap-arrow { left: -16px; width: 15px; height: 15px; margin-top: -7.5px; }
}
.ap-num__tick {
  position: absolute; top: -3px; right: -3px; width: 20px; height: 20px;
  border-radius: 50%; background: var(--mim-purple); color: #000;
  display: grid; place-items: center; box-shadow: 0 0 0 2px rgba(0,0,0,0.55);
}
.ap-num__tick svg { width: 11px; height: 11px; display: block; }
.ap-note.is-done .ap-num { animation: none; opacity: 0.72; }

/* Research blocks inside a modal. `--mo-grey` is MIM Pro's token; this kit's
   equivalent is --mim-grey-light. */
.ap-research { margin-top: 20px; display: flex; flex-direction: column; gap: 12px; }
.ap-research__label {
  font-family: var(--font-primary); font-weight: var(--weight-semibold);
  font-size: calc(11px * var(--fs-scale)); letter-spacing: 0.14em;
  text-transform: uppercase; color: var(--mim-purple-aa-dark); margin: 0;
}
.ap-research__item { background: var(--mim-grey-light); border-radius: 10px; padding: 14px 16px; }
.ap-research__item p {
  font-family: var(--font-primary); font-weight: var(--weight-light);
  font-size: calc(13.5px * var(--fs-scale)); line-height: 1.55;
  color: var(--fg-muted); margin: 0;
}
.ap-research__item a {
  display: block; margin-top: 6px; color: var(--mim-purple-aa-dark);
  font-weight: var(--weight-semibold); text-decoration: underline;
}

@media (max-width: 620px) {
  .ap-frame { height: auto; min-height: 560px; }
  .ap-content { padding: 28px 24px; }
  .ap-head { max-width: 100%; }
  /* On a narrow frame the moves sit under the header rather than beside the
     subject, so the scrim goes bottom-up instead of left-in. Same reason it is
     on .ap-content and not .ap-scrim. */
  .ap-content {
    background-image: linear-gradient(0deg, rgba(0,0,0,0.90) 0%, rgba(0,0,0,0.45) 55%, rgba(0,0,0,0.20) 100%);
  }
  .ap-note-title { font-size: calc(15px * var(--fs-scale)); }
  .ap-num { width: 46px; height: 46px; font-size: calc(17px * var(--fs-scale)); }
  .ap-modal { padding: 26px 22px 28px; }
}

body.night-mode .ap-modal { background: #1a1a1a; }
body.night-mode .ap-modal__title { color: #fff; }
body.night-mode .ap-modal__body p { color: rgba(255,255,255,0.75); }
body.night-mode .ap-phrase { background: rgba(255,255,255,0.04); border-color: rgba(255,255,255,0.14); }
body.night-mode .ap-phrase__text { color: #fff; }
body.night-mode .ap-research__item { background: rgba(255,255,255,0.05); }
body.night-mode .ap-research__item p { color: rgba(255,255,255,0.72); }
body.night-mode .ap-modal__close { background: #1a1a1a; color: #fff; border-color: rgba(255,255,255,0.2); }

/* [screen 16] Four flipcards across. The engine supplies .flipcard whole; all
   this adds is the column count and the two bits of type on the faces. */
.flipcard-grid--four { grid-template-columns: repeat(4, 1fr); }
@media (max-width: 900px) { .flipcard-grid--four { grid-template-columns: repeat(2, 1fr); } }
/* ⚠ THE FLIPCARD RULES ARE GONE, AND THAT IS THE POINT. Deck 18 was built as
   four flipcards, Anu replaced the interaction with the timeline on 1 October
   2026, and the CSS stayed behind - `.flipcard-grid--four`, `.flipcard__face`,
   `.flipcard__cue` and two night-mode rules styling a component no screen in
   the module renders any more. Module 1's own review found a dead reveal panel
   exactly this way. If flipcards come back on a later lesson, the rules come
   back with the screen that needs them. */

/* [screens 17 and 20] Anu's artwork, 13 October 2026, replacing two inline
   SVGs. `.mo-diagram` sizes `svg` and nothing else - the rule inherited from
   Module 1 is `.mo-diagram svg { width: 100% }` - so a raster diagram dropped
   into the same figure would render at its natural 1557px and overflow the
   640px column. These give `img` the same treatment.

   The Ex-PLISSIT crop is PORTRAIT, 787x879. At the full 640px it would stand
   715px tall and push the whole of screen 20's explanation below the fold, so
   it gets a narrower ceiling. The staircase is landscape and takes the full
   width. */
.mo-diagram--art img { width: 100%; height: auto; display: block; }

/* [screen 8] Module 1's biopsychosocial diagram is nearly square (760x756),
   where the SVG it replaces was 640x370. Left to fill the column it would
   stand twice as tall as the figure it replaced and push the hotspot panels
   further below the fold than they already were. Capped, and centred. */
.mim-hotspots__img { display: block; width: 100%; height: auto; }
.mim-hotspots--drawn .mim-hotspots__figure:has(.mim-hotspots__img) {
  max-width: 440px; margin-inline: auto;
}
.mo-diagram--portrait { max-width: 420px; margin-inline: auto; }

/* [screen 19] The tiles were black. Anu, 13 October 2026: *"can we replace the
   black colour on the tiles with monochrome light purple gradient"*. They were
   black because she asked for black on 1 October - *"brilliant layout, change
   the tile colours from light grey to black"* - so this supersedes that.

   MONOCHROME MEANS ONE HUE. Every value below is #B7A3FC at a different
   alpha over the ground beneath it. No second colour is introduced.

   ⚠ CHANGING THE GROUND MEANS REDRAWING EVERY ANSWER STATE, AND NIGHT MODE
   NOW NEEDS ITS OWN. The old note said "night mode needs no override because
   the tiles are already black in both" - that stops being true the moment the
   tile is light, which is exactly the trap CLAUDE.md names.

   ⚠ AND THE CORRECT STATE KEEPS BLACK TYPE, NOT PURPLE. Purple type
   (`--mim-purple-aa-dark`, #5D3CC9) on the deep end of this gradient measures
   4.14:1, under the 4.5 AA needs, so the obvious "purple means right" would
   have shipped a failure. Measured across the range before choosing:

       type            on 16% tint   on 38%   on 44%   on 72%
       black             18.7          16.0     15.3     12.2
       #5D3CC9            6.3           5.4      5.2      4.1  <- fails

   So "right" is carried by a deeper fill, the dark purple border, the
   semibold weight and the tick, and the type stays black at 12:1 or better.
   Four signals, none of them colour alone. */
.quiz-options--levels .quiz-option {
  background: linear-gradient(160deg,
              rgba(var(--mim-purple-rgb), 0.34),
              rgba(var(--mim-purple-rgb), 0.14));
  color: var(--mim-black);
  border-color: rgba(var(--mim-purple-rgb), 0.55);
}
.quiz-options--levels .quiz-option:hover:not(:disabled) {
  border-color: var(--mim-purple-aa-dark);
}
.quiz-options--levels .quiz-option.correct,
.quiz-options--levels .quiz-option.revealed-correct {
  background: linear-gradient(160deg,
              rgba(var(--mim-purple-rgb), 0.66),
              rgba(var(--mim-purple-rgb), 0.40));
  color: var(--mim-black);
  border-color: var(--mim-purple-aa-dark); font-weight: var(--weight-semibold);
}
.quiz-options--levels .quiz-option.incorrect {
  border-color: rgba(var(--mim-purple-rgb), 0.38);
  color: rgba(0,0,0,0.62);
  opacity: 1;
}
/* mark-tick is a dark glyph on transparency and the tile is now light, so the
   engine's own mark would work - but the cross and tick stay drawn in type,
   because that also survives a missing asset. Both are dark now: a
   `--mim-purple` tick on a purple tile is invisible. */
.quiz-options--levels .quiz-option.correct::after,
.quiz-options--levels .quiz-option.revealed-correct::after {
  background: none; content: "\2713"; color: var(--mim-purple-aa-dark);
  font-size: calc(19px * var(--fs-scale)); line-height: 1; width: auto; height: auto;
}
.quiz-options--levels .quiz-option.incorrect { position: relative; padding-right: 56px; }
.quiz-options--levels .quiz-option.incorrect::after {
  content: "\00D7"; position: absolute; right: 18px; top: 50%;
  transform: translateY(-50%);
  color: rgba(0,0,0,0.62); font-size: calc(22px * var(--fs-scale)); line-height: 1;
}
/* NIGHT MODE. A light purple tile on the #0b0b0d ground would glare, so the
   same hue runs the other way: a purple-tinted dark tile with light type. The
   gradient still reads, the tile still belongs to the purple family, and the
   measured values are 12.1:1 for the body type and 9.5:1 on the deeper
   correct fill. */
body.night-mode .quiz-options--levels .quiz-option {
  background: linear-gradient(160deg,
              rgba(var(--mim-purple-rgb), 0.22),
              rgba(var(--mim-purple-rgb), 0.08));
  color: rgba(255,255,255,0.92);
  border-color: rgba(var(--mim-purple-rgb), 0.38);
}
body.night-mode .quiz-options--levels .quiz-option:hover:not(:disabled) {
  border-color: var(--mim-purple);
}
body.night-mode .quiz-options--levels .quiz-option.correct,
body.night-mode .quiz-options--levels .quiz-option.revealed-correct {
  background: linear-gradient(160deg,
              rgba(var(--mim-purple-rgb), 0.34),
              rgba(var(--mim-purple-rgb), 0.18));
  color: rgba(255,255,255,0.96);
  border-color: var(--mim-purple);
}
body.night-mode .quiz-options--levels .quiz-option.incorrect {
  border-color: rgba(255,255,255,0.26); color: rgba(255,255,255,0.62);
}
body.night-mode .quiz-options--levels .quiz-option.correct::after,
body.night-mode .quiz-options--levels .quiz-option.revealed-correct::after {
  color: var(--mim-purple);
}
body.night-mode .quiz-options--levels .quiz-option.incorrect::after {
  color: rgba(255,255,255,0.72);
}

/* [screen 21] MIM Pro's select-and-swap, ported from its GP Part 1 module.css
   (screen 15). The backpack illustration is deliberately not ported - see
   swap_panel(). The dark panel is the kit's black photocopy texture, which is
   the same ground the dividers use, so the two read as one system. */
.bob { margin-top: var(--sp-4); max-width: var(--measure); }
.bob__stage {
  display: grid; grid-template-columns: 1fr 1.05fr;
  gap: clamp(28px, 4vw, 48px); align-items: stretch;
}
@media (max-width: 760px) {
  .bob__stage { grid-template-columns: 1fr; gap: 28px; align-items: start; }
}
.bob__left, .bob__right { display: flex; }
.bob__trait {
  background-color: var(--mim-black);
  background-image: url('./assets/mim/texture-photocopy-black.png');
  background-size: cover; background-position: center;
  background-blend-mode: screen;
  border-radius: var(--radius-lg); padding: 26px 28px 30px; flex: 1; min-height: 0;
}
.bob__eyebrow {
  display: block; margin-bottom: 14px;
  font-family: var(--font-primary); font-weight: var(--weight-semibold);
  font-size: calc(12px * var(--fs-scale)); letter-spacing: 0.16em;
  text-transform: uppercase; color: rgba(255,255,255,0.62);
}
.bob__trait-title {
  font-family: var(--font-feature); font-weight: var(--weight-medium);
  font-size: calc(clamp(24px, 2.6vw, 30px) * var(--fs-scale)); line-height: 1.08;
  margin: 0 0 12px; color: var(--mim-white);
}
.bob__trait-desc, .bob__trait-desc .body, .bob__trait-desc .list li {
  font-family: var(--font-primary); font-weight: var(--weight-light);
  font-size: calc(16px * var(--fs-scale)); line-height: 1.5;
  color: rgba(255,255,255,0.86); max-width: none;
}
.bob__trait-desc > * + * { margin-top: var(--sp-3); }
/* The phrase cards inside the dark panel need their own ground and type. */
.bob__trait-desc .mo-phrase { background: rgba(255,255,255,0.08); border-color: rgba(255,255,255,0.22); }
.bob__trait-desc .mo-phrase__text { color: rgba(255,255,255,0.94); }
.bob__trait-desc .mo-phrases__label { color: #cdbcff; }
.bob__swap { animation: bobSwapIn 320ms var(--ease-standard); }
@keyframes bobSwapIn { from { opacity: 0; transform: translateY(8px); } to { opacity: 1; transform: none; } }
.bob__bricks { display: flex; flex-direction: column; gap: 12px; flex: 1; justify-content: space-between; }
.bob__brick {
  appearance: none; text-align: left; font-family: var(--font-primary);
  border: 1px solid var(--border); background: var(--mim-grey-light);
  border-radius: var(--radius-md); padding: 16px 20px; cursor: pointer;
  color: var(--fg-muted); font-weight: var(--weight-semibold);
  font-size: calc(clamp(16px, 1.4vw, 18px) * var(--fs-scale));
  display: flex; align-items: center; gap: 14px; flex: 1 1 0; min-height: 60px;
  transition: transform var(--dur-base) var(--ease-standard),
              background var(--dur-base) var(--ease-standard),
              border-color var(--dur-base) var(--ease-standard),
              color var(--dur-base) var(--ease-standard);
}
.bob__brick:hover { transform: translateY(-2px); border-color: var(--border-strong); color: var(--fg); }
.bob__dot {
  flex: 0 0 auto; width: 13px; height: 13px; border-radius: 3px;
  border: 1.5px solid currentColor; opacity: 0.5;
}
.bob__label { flex: 1; }
.bob__brick.is-packed { background: var(--mim-white); color: var(--fg); border-color: rgba(183,163,252,0.55); }
.bob__brick.is-packed .bob__dot { background: var(--mim-purple-aa); border-color: var(--mim-purple-aa); opacity: 1; }
.bob__brick.is-active { background: rgba(132,99,248,0.16); border-color: var(--mim-purple-aa); color: var(--fg); }
.bob__brick.is-active .bob__dot { background: var(--mim-purple-aa); border-color: var(--mim-purple-aa); opacity: 1; }
/* ⚠ `.bob__brick` takes --fg-muted and `.bob__count` takes --fg, and night
   mode redefines NEITHER. MIM Pro's own port only flips the backgrounds, which
   is why a straight copy left grey-on-near-black labels at 2.02:1 and a black
   counter on a black ground. Every one of them needs its night colour here. */
body.night-mode .bob__brick { background: #17171b; color: rgba(255,255,255,0.72); }
body.night-mode .bob__brick:hover,
body.night-mode .bob__brick.is-packed,
body.night-mode .bob__brick.is-active { color: rgba(255,255,255,0.94); }
body.night-mode .bob__brick.is-packed { background: #232329; }
.bob__meta { margin-top: var(--sp-5); display: flex; align-items: center; gap: 14px; }
.bob__count {
  font-family: var(--font-primary); font-weight: var(--weight-semibold);
  font-size: calc(15px * var(--fs-scale)); color: var(--fg);
}
.bob__count b { color: var(--mim-purple-aa-dark); }
body.night-mode .bob__count { color: rgba(255,255,255,0.92); }
body.night-mode .bob__count b { color: #cdbcff; }
@media (max-width: 760px) {
  .bob__right { display: block; }
  .bob__bricks { display: flex; flex: none; }
  .bob__brick { flex: none; min-height: 0; }
}

/* [screens 20, 25] The reflection questions as a list.

   ⚠ THREE TYPE SIZES IN ONE BLOCK IS TOO MANY. Anu, 1 October 2026, on deck
   22: *"fix the spacing. Too many different font sizes and styles."* The block
   had the 22px prompt face on the opener, 18px on the listed questions and
   16px body copy on the framing lines - three sizes and two weights inside one
   bordered panel, which reads as three unrelated things rather than one
   reflection.

   Two sizes now, and one of them is the questions: the opener stays at the
   prompt face because it is the prompt, and the questions and the framing
   lines share the body size. The questions are told apart by the purple
   markers and the indent, not by a third size. */
.reflect__questions { margin: var(--sp-4) 0; }
.reflect__questions li {
  font-family: var(--font-primary); font-weight: var(--weight-regular);
  font-size: var(--fs-body); line-height: 1.55; color: var(--fg);
}
.reflect__questions li + li { margin-top: 10px; }
body.night-mode .reflect__questions li { color: rgba(255,255,255,0.92); }
/* One rhythm through the block, rather than each part's own margin. */
.reflect .reflect__prompt { margin-bottom: var(--sp-4); }
.reflect .body { margin-top: var(--sp-4); }
.reflect .body + .body { margin-top: 10px; }

/* [screen 19] The plain accordion on a content screen. The engine centres
   `.accordion__title`, which is right on the FAQ screen where every row is a
   question and wrong here, where each row is the name of a moment in a
   consultation and the five read as a sequence. */
.screen[data-type="content"] .accordion__title { text-align: left; }
.screen[data-type="content"] .accordion__summary::before { display: none; }

/* [deck 71] The multiple-select knowledge check.

   Deliberately styled to the engine's own `.quiz-option` down to the padding,
   the radius and the grey, because it is the module's FOURTH knowledge check
   and the other three are the engine's. A learner should not be able to tell
   that this one is built differently; the only visible difference is the
   tickbox, which is the thing that tells them more than one answer counts.

   ⚠ THE NIGHT-MODE RULES ARE NOT OPTIONAL. Night mode does not redefine
   `--fg` or `--fg-muted`, so any colour taken from them stays dark-on-dark -
   this module has already shipped that fault three times (`.mo-phrase__text`,
   `.bob__label`, `.bob__count`). Every rule here that sets a colour has its
   own `body.night-mode` partner, mirroring what the engine does for
   `.quiz-option` two blocks up. */
.multi-select { max-width: var(--measure); margin: var(--sp-5) 0; }
.multi-options { display: flex; flex-direction: column; gap: var(--sp-3);
  margin: var(--sp-4) 0; }
.multi-option {
  display: flex; align-items: flex-start; gap: 14px;
  text-align: left; padding: 16px 20px; cursor: pointer;
  border: 1px solid var(--border); border-radius: var(--radius-md);
  background: var(--mim-grey-light); font-family: var(--font-primary);
  font-size: calc(15px * var(--fs-scale)); line-height: 1.45; color: var(--fg);
}
.multi-option:hover { border-color: var(--mim-purple-aa); }
.multi-option__box {
  flex: none; width: 20px; height: 20px; margin: 0; cursor: pointer;
  accent-color: var(--mim-purple-aa);
}
.multi-option__text { flex: 1 1 auto; }
/* After checking: the state is said in words as well as coloured, because
   colour must not be the only signal (WCAG 1.4.1) - the same reason the
   engine's revealed answer carries a tick. */
.multi-option__mark:not(:empty) {
  flex: none; align-self: center;
  font-family: var(--font-primary); font-weight: var(--weight-semibold);
  font-size: calc(12px * var(--fs-scale)); letter-spacing: .04em;
  text-transform: uppercase; white-space: nowrap;
}
.multi-option.is-right {
  border-color: var(--mim-purple-aa); background: rgba(132,99,248,.14);
  color: var(--mim-purple-aa-dark); font-weight: var(--weight-semibold);
}
.multi-option.is-wrong { border-color: var(--border-strong); opacity: .75; }
.multi-option.is-right .multi-option__mark { color: var(--mim-purple-aa-dark); }
.multi-option.is-wrong .multi-option__mark { color: var(--fg-muted); }
.multi-option input:disabled, .multi-option:has(input:disabled) { cursor: default; }
.multi-fb { margin-top: var(--sp-4); padding: var(--sp-4) var(--sp-5);
  border-radius: var(--radius-md); background: var(--mim-grey-light); }
.multi-fb .body + .body { margin-top: var(--sp-3); }
.multi-hint { margin-top: var(--sp-3); }

body.night-mode .multi-option {
  background: #17171b; border-color: rgba(255,255,255,.18);
  color: rgba(255,255,255,.9);
}
body.night-mode .multi-option.is-right {
  color: var(--mim-purple); border-color: var(--mim-purple);
  background: rgba(183,163,252,.16);
}
body.night-mode .multi-option.is-right .multi-option__mark { color: var(--mim-purple); }
body.night-mode .multi-option.is-wrong .multi-option__mark { color: rgba(255,255,255,.65); }
body.night-mode .multi-fb { background: #17171b; }

/* ⚠ THE ENGINE PUTS A FLOATING SHADOW ON EVERY BUTTON IN A SCREEN.
   `mim-gp-shared.css:1641` sets `.screen button { box-shadow: var(--depth-1) }`,
   which lands on the tickboxes' own control and on the check button. Anu had to
   report this twice on the accordion icons before it was fixed, because the
   first override was written as `.multi-select button` - specificity (0,1,1)
   against the engine's (0,1,1) with the engine later in the cascade, so the
   engine won. Scoped through `.screen` to reach (0,2,1) and actually win. */
.screen .multi-select button { box-shadow: none; }
.screen .multi-option { box-shadow: none; }

/* [screen 40] The few seconds to read the feedback before the film carries on.

   Anu, 1 October 2026: *"Once they choose the correct answer, give it a few
   seconds for them to read the options before resuming the film again."*

   A line and a control, not a modal: the feedback underneath it is what the
   learner is reading, so nothing may cover it. The button is there because
   anyone who has finished reading should not have to wait out a clock, and
   because a countdown with no way past it is the kind of small rudeness that
   accumulates over seventy-nine screens. */
.film-resume {
  display: flex; align-items: center; gap: 14px; flex-wrap: wrap;
  margin: var(--sp-4) 0 0; padding: 10px 14px;
  border-left: 3px solid var(--mim-purple-aa);
  background: var(--mim-grey-light);
  font-family: var(--font-primary);
  font-size: calc(14px * var(--fs-scale)); line-height: 1.4;
  color: var(--fg);
}
.film-resume__text { flex: 1 1 auto; }
.screen .film-resume__now {
  flex: none; box-shadow: none;
  padding: 7px 14px; cursor: pointer;
  border: 1px solid var(--mim-purple-aa); border-radius: var(--radius-sm);
  background: transparent;
  font-family: var(--font-primary); font-weight: var(--weight-semibold);
  font-size: calc(13px * var(--fs-scale));
  color: var(--mim-purple-aa-dark);
}
.screen .film-resume__now:hover { background: rgba(132,99,248,.12); }
body.night-mode .film-resume {
  background: #17171b; border-left-color: var(--mim-purple);
  color: rgba(255,255,255,.9);
}
body.night-mode .screen .film-resume__now {
  border-color: var(--mim-purple); color: var(--mim-purple);
}

/* [screen 40] The pause-and-choose rehearsal.

   The question is hidden with the `hidden` attribute rather than a class, so it
   is out of the accessibility tree as well as off the screen while the film is
   still running - a learner on a screen reader should not be read four options
   to a question they have not been asked yet. Nothing here may set `display` on
   `.film-question` without honouring `[hidden]`, or the attribute stops working
   and the answer appears above the film.

   When it arrives it arrives all at once, after the film has stopped, so it
   gets a little space above it and a short fade. The fade is suppressed under
   reduced motion by the engine's own global rule. */
.film-question { margin-top: var(--sp-5); }
.film-question:not([hidden]) { animation: filmQuestionIn .28s ease-out both; }
@keyframes filmQuestionIn {
  from { opacity: 0; transform: translateY(6px); }
  to   { opacity: 1; transform: none; }
}

/* ⚠ THIS LINE IS THE ONLY THING A LEARNER SEES WHEN AN EMBED IS REFUSED, so it
   has to be legible in both themes. Night mode does not redefine `--fg`, which
   has already shipped as dark-on-dark three times in this module. */
.film-failed {
  margin: var(--sp-4) 0 0; padding: 12px 16px;
  border-left: 3px solid var(--mim-purple-aa);
  background: var(--mim-grey-light);
  font-family: var(--font-primary);
  font-size: calc(15px * var(--fs-scale)); line-height: 1.45;
  color: var(--fg);
}
body.night-mode .film-failed {
  background: #17171b; border-left-color: var(--mim-purple);
  color: rgba(255,255,255,.9);
}

/* ⚠ THE PLAYER NEEDS THE FRAME'S 16:9 RATIO PUT BACK. The engine gives
   `.video-frame` `aspect-ratio: 16/9`; the block at the top of this file
   overrides it to `auto` for `[data-happy-frame]`, because the poster there is
   a picture PLUS a caption strip and the frame has to size to its content. The
   consequence only appears once a film actually plays: all of the frame's
   height comes from the poster, the iframe is `position: absolute; inset: 0`
   and contributes none, so hiding the poster leaves the frame at 700x0 and the
   whole container disappears. Anu, 1 October 2026: *"When I press the play
   button, the whole video container disappears and nothing happens."*
   
   `PLACEHOLDER_VIDEO: true` concealed this for as long as nothing in this
   module ever built an iframe. The engine's own player would have hit it too.
   
   `.is-playing` is set by rtl-film-player.js when it mounts the player.
   Specificity: two classes (0,2,0) against a class and an attribute (0,1,1),
   so this wins without !important. */
.video-frame[data-happy-frame].is-playing {
  aspect-ratio: 16 / 9;
  height: auto;
  overflow: hidden;
}
/* The engine already absolutely positions `.video-frame iframe` to inset 0, so
   with the ratio restored the player fills the frame. This only removes the
   border and makes it a block. */
.video-frame iframe { display: block; border: 0; }

/* [screen 6] The transcript toggle has to be centred with the film it belongs
   to, on the one screen whose content is centred.

   Anu, 1 October 2026: *"Align the whole content including the title, video etc
   to the centre."* `.screen--film .video-frame` gets an explicit
   `margin-inline: auto` for that, but the engine gives `.video-controls` and
   the transcript panel `margin-left: var(--video-ml)`, and `--video-ml` is 0 on
   this screen because `.video-screen:has(.body)` is more specific (0,2,0) than
   `.screen--film` (0,1,0) and sets it so.

   The result once a transcript existed: the film centred in the column and the
   toggle sat 176px to its left. Caught by `qa-video-widths.js`, which measures
   the toggle against the film rather than trusting the width to imply it. */
.screen--film .video-controls,
.screen--film .hifh__transcript { margin-inline: auto; }

/* [screen 34] A picture on the face of each card.

   Anu, 13 October 2026: *"make this an image accordion with the image visible
   when the learner sees the screen"*, and *"I want the images in each
   accordion. We had such an accordion in Module 1."*

   ⚠ A FIXED THUMBNAIL, NOT A FULL-BLEED BANNER. Screen 34's cards are
   `.mo-cards--stack`, so each one is the full column. An unsized 1400px
   photograph fills that width and comes out about 460px tall, three times
   over, which buries the three titles the learner is meant to be choosing
   between under a metre of scroll. Floated at 180x120 the picture sits beside
   its own title, where it does the one job it has: telling the three cards
   apart before any of them is opened.

   The same decision as `.reveal-stack .reveal-item__img`, which is MIM Pro's
   image accordion and is likewise given an explicit height and `cover` rather
   than being allowed to size itself.

   ⚠ `object-fit: cover` AND A FIXED BOX, because the three come off two
   different shoots and are not the same aspect ratio. Letting each one keep
   its own leaves three cards that do not line up. */
.mo-card__img {
  float: left; width: 180px; height: 120px; object-fit: cover;
  border-radius: var(--radius-md); display: block;
  margin: 0 clamp(16px, 1.6vw, 20px) 0 0;
}
/* The cue already floats right and the face is a block, so without this the
   card collapses to the height of its text and the photograph hangs out of
   the bottom of its own card. */
.screen .mo-card__btn::after { content: ""; display: block; clear: both; }
/* Below the stack's breakpoint there is no room beside 180px of photograph,
   so it becomes a short band across the top of the card instead. */
@media (max-width: 600px) {
  .mo-card__img {
    float: none; width: 100%; height: 140px; margin: 0 0 14px;
  }
}

/* A photograph on a screen that has none - see tools/sharet/make_m2_photos.py
   for the five and where each one lands. `.mo-figure img` already gives it the
   column width and the module's corner radius; the only thing it needs on top
   is a ceiling. At 700px wide a 3:2 frame is 467px tall, which on a screen
   that opens with one pushes the first line of teaching below the fold. */
.mo-figure--photo img { max-height: 340px; object-fit: cover; }

/* ⚠ LEFT-ALIGNED HEADINGS FILL THEIR FIRST LINE. THE SAME FINDING AS
   30 SEPTEMBER, ON EVERYTHING THAT IS NOT `.title`.

   Anu, 13 October 2026, on screen 31: *"adjust the spacing in the statement
   if you are stuck for an opening"*, and on screen 70: *"I want you to
   distribute the word spacing properly. In some instances, there is too much
   space in the first row. The first row/line needs to be filled fully before
   the text overflows into the second line. This occurs in a few other
   instances as well."*

   "A few other instances as well" is the part that needed measuring rather
   than guessing. `tools/sharet/qa-first-line-fill.js` walks all 79 screens,
   groups each wrapped block's client rects into rendered lines and compares
   the first line against the column it sits in. Four blocks fill under 82%
   of their own column, and all four compute `text-wrap: balance`:

       screen 31   0.57   h2.heading          the one Anu named
       screen 78   0.74   p.mo-tile__title
       screen 57   0.75   h2.heading
       screen 61   0.80   h2.heading

   The design system sets `text-wrap: balance` on every h1 to h6
   (mim-design-system.css:181). Balance EVENS the lines, which is right for a
   centred display line and wrong for a left-aligned sentence, which should
   fill its first line and carry the remainder. `.title` was given `pretty`
   for exactly this on 30 September; every other heading kept balance, so the
   fix only ever covered the h1.

   `pretty` fills the line and still avoids stranding one word on the last.

   ⚠ NOT A BLANKET OVERRIDE. The display pieces that are MEANT to be balanced
   keep it, because they are centred and short: `.screen--statement .title`,
   `.mim-mark`, `.hx-title` and `.lo-title` are all set further up this file
   and none of them is matched here. */
.screen-inner .heading,
.screen-inner h2, .screen-inner h3, .screen-inner h4,
.screen-inner h5, .screen-inner h6,
.mo-tile__title { text-wrap: pretty; }

/* ⚠ [screen 33] A SPACE AFTER EVERY SENTENCE INSIDE A REVEAL. THE FIX WAS IN
   THE WRONG PLACE TWICE.

   Anu asked for this on 13 October and then again the same day: *"Screen 33 -
   last accordion. Give the right spacing after each sentence"*, and *"Screen
   33 last accordion text spacing not fixed."*

   It was not fixed because it was being looked for in the markup, and the
   markup was already right: that panel holds three separate `<p class="body">`
   elements. The cause is one line of this stylesheet.

       .mo-reveal__body .body { margin-top: 0; }

   That is correct for the FIRST paragraph, which should sit flush under the
   tile it opened, and it silently closes the gap between every paragraph
   after it. Three paragraphs render as one block of text with no breaks.

   One rule, and only between paragraphs, so the first stays flush. It applies
   to every reveal in the module, not only screen 33 - nine panels across six
   screens hold more than one paragraph and all of them had this. */
.mo-reveal__body .body + .body { margin-top: var(--sp-4); }

/* [screen 12] Room between the tiles and the paragraph after them.

   Anu, 13 October 2026: *"Screen 12 - I need more space between the two tiles
   and the next paragraph."* `.reveal-list` closes on var(--sp-5) and what
   follows it on this screen is a full paragraph of teaching, not a caption,
   so the tiles and the text read as one run. The tiles are a thing the
   learner does; the paragraph is the next thing to read. */
.reveal-list + .body { margin-top: var(--sp-6); }

/* [screen 25] The finding reads first, the reference reads second.

   Anu, 13 October 2026: *"Screen 25 what the research says box just lists the
   citations without any information."* The sentences are added by
   tools/sharet/m2_s25_findings.py; this is what makes the citation beneath
   each one look like a citation rather than a second paragraph of equal
   weight. */
.evidence__finding + .body {
  font-size: calc(13px * var(--fs-scale)); line-height: 1.5;
  color: var(--fg-muted); margin-top: var(--sp-3);
}
body.night-mode .evidence__finding + .body { color: rgba(255,255,255,0.66); }

/* [screen 62] The way back to the model on screen 8. Quiet, because it is an
   offer and not the next thing to do - the learner's path is still Continue,
   and a solid button here would compete with it. */
.m2-backto { margin: var(--sp-5) 0; }
.m2-backto__note {
  margin: 8px 0 0; font-family: var(--font-primary);
  font-size: calc(13px * var(--fs-scale)); line-height: 1.5;
  color: var(--fg-muted);
}
body.night-mode .m2-backto__note { color: rgba(255,255,255,0.66); }

/* [screen 35] Three blocks that are simply open.

   The one screen in lesson 2.3 whose content is about what not to assume, so
   a learner who opens one panel and moves on has missed the two that prevent
   harm. See accordion_to_blocks() in build_m2_batch.py. The purple rule is
   the module's own statement treatment, which is what these three are. */
/* ⚠ NO RULE DOWN THE LEFT. The first version of this gave each block the
   module's purple statement rule, on the reasoning that these three are
   statements. Anu, 13 October 2026: *"Screen 35 - remove the purple lines as
   it looks odd."* She is right, and the reason is that a statement rule marks
   ONE sentence standing out of a page; three of them stacked, each a heading
   with a body and a list of phrasings underneath, is not three statements but
   a page with stripes down it. The separation comes from the space and the
   heading instead, which is what the rest of the module does. */
.mo-open { margin: var(--sp-6) 0; max-width: var(--measure); }
.mo-open__title { margin: 0 0 var(--sp-3); }
.mo-open__body > * + * { margin-top: var(--sp-4); }

/* [screens 36 and 78] Air between the lead and the photograph under it.

   Anu, 13 October 2026: *"Screen 36 - Need more space between the text and
   the image after the title"*, and then on 78 *"more space needed between
   the second line after the title and the image."* `screen_photo()` inserts
   the figure directly after `.section-lead`, and `.mo-figure`'s own margin
   is all at the BOTTOM, so the picture sat hard under the last line of the
   lead on all five screens that have one.

   ⚠ 48px, NOT 32px. The first version used --sp-6 and she asked again on the
   next screen, which is the useful signal: at 32px the picture still reads
   as belonging to the paragraph above it rather than following it. --sp-7 is
   the next step on the scale and is what the module already uses between a
   title and the thing under it. */
.section-lead + .mo-figure--photo { margin-top: var(--sp-7); }

/* [screens 6, 40 and 42] The picture window takes a frame from the film.

   Anu, 13 October 2026: *"Upload Doug and Jean screenshots to their videos"*,
   and when the first attempt showed nothing: *"I can't see the screenshot on
   screen 6."*

   ⚠ THE FRAME GOES ON `.vp-stage`, NOT ON `.video-poster`, AND MODULE 1
   ALREADY LEARNED THIS. `apply_anu_12oct_m1_j.py` has the long version. The
   engine declares a styled poster to have no thumbnail:

       .video-poster--styled {
         background: var(--mim-black) !important;
         background-image: none !important;     mim-gp-shared.css:727
       }

   An `!important` outranks an inline style, so the first version of
   m2_film_posters.py wrote `style="background-image:url(...)"` onto the
   poster and the computed value came back `none`. And even without that,
   `.vp-stage` is an opaque `background: #000` child filling the whole 16:9
   area, so the poster's own background sits behind it and could never be
   seen. Both measured on the built page rather than reasoned about.

   `.vp-stage` is this module's own element and carries no `!important`, so
   the inline background lands. This only gives it the sizing. The black stays
   underneath, as the ground behind a picture that has not loaded yet and as
   the deliberate look of a film screen whose frame was never captured.

   ⚠ MODULE 1'S OTHER TRAP DOES NOT APPLY HERE, and it is worth knowing why.
   On its screen 51 the stage measured zero: `.video-poster--styled` is a
   centred column flexbox, so an empty `aspect-ratio: 16/9` div with
   `width: auto` resolves to zero wide and therefore zero tall. It escaped on
   screen 10 because `.video-frame[data-happy-frame] > .video-poster` is
   `display: block`. All three of Module 2's frames carry `data-happy-frame`,
   and the stages measure 700x394. */
.vp-stage {
  background-size: cover;
  background-position: center;
  background-repeat: no-repeat;
}

/* ⚠ THE TRANSCRIPT FAILED AA AND NO AUDIT SAW IT. `qa-contrast.js` measures
   the film screens specifically and found two:

       .hifh__transcript p      12px   3.88:1   needs 4.5
       .transcript-speaker      11px   3.88:1   needs 4.5

   Both are `var(--fg-subtle)`, #6A6A6A, on the transcript's black ground.

   ⚠ WHY `audit.js` RETURNED 0 LOW-CONTRAST ON THE SAME PAGE, TWICE A DAY,
   FOR A WEEK. The transcript lives in a collapsed panel. audit.js walks the
   screens and measures what is rendered; a panel nobody opened has nothing
   to measure. A clean audit is evidence about what the audit looked at, not
   about the page - which is the same lesson as the stylesheet comment that
   swallowed a rule in silence.

   #767676 is 4.62:1 on black and 4.54:1 on white, so it passes on the dark
   film screens AND on screen 42, which Anu had made a plain white screen.
   One value, both grounds, no new conditional.

   ⚠ IT IS THE ENGINE'S TOKEN AND THE ENGINE'S COMPONENT. `--fg-subtle` is
   set in mim-gp-shared.css and every module with a transcript has this, so
   Module 1 has it too and is frozen - logged in
   POST-FREEZE-m1-backlog-2026-10-13.md. Raising the token itself is Tom's
   call; this overrides it only where it is measured failing. */
.hifh__transcript p,
.transcript-speaker { color: #767676; }

/* [screen 63] The sorting tiles take screen 19's purple.

   Anu, 13 October 2026: *"Let's add a bit of colour on screen 63. Make the
   small tiles a monochromatic purple gradient."*

   ⚠ SCREEN 63 ONLY, AND SHE SAID SO IN THE NEXT BREATH. The first version of
   this coloured all three sorting screens on a consistency argument. She then
   asked for the opposite on the other two: *"Let's make the tiles after the
   statements light grey colour on screen 64"* and *"same on screen 65"*. The
   two are not the same activity doing the same job - 63 is a sort where the
   tiles ARE the subject, 64 and 65 fill a framework where the tiles are
   answers to statements and should sit behind them.

   The split needs no new class: `fill_the_grid()` already marks 64 and 65
   `.rtl-cat--grid`, so the purple is scoped away from it.

   MONOCHROME MEANS ONE HUE: every value is #B7A3FC at a different alpha. The
   values are screen 19's, where black type on the 34%-to-14% tint was
   measured at 18.7:1.

   ⚠ THE CORRECT AND INCORRECT BORDERS STAY GREEN AND RED - the library's own
   `--_ok` and `--_no`. They are how a learner reads their answer back, not
   decoration, and both carry a tick or a cross glyph as well, so the state is
   never colour alone. */
.rtl-cat:not(.rtl-cat--grid) .rtl-cat__item {
  background: linear-gradient(160deg,
              rgba(var(--mim-purple-rgb), 0.34),
              rgba(var(--mim-purple-rgb), 0.14));
  color: var(--mim-black);
  border-color: rgba(var(--mim-purple-rgb), 0.55);
}
.rtl-cat:not(.rtl-cat--grid) .rtl-cat__item:hover:not(:disabled) {
  border-color: var(--mim-purple-aa-dark);
}
.rtl-cat:not(.rtl-cat--grid) .rtl-cat__item.is-held {
  background: linear-gradient(160deg,
              rgba(var(--mim-purple-rgb), 0.56),
              rgba(var(--mim-purple-rgb), 0.30));
}
/* Night mode needs its own, for the reason screen 19's note gives: the
   moment a tile stops being dark, "it is already dark in both" stops being
   true. The type flips to white and the tint deepens to carry it. */
body.night-mode .rtl-cat:not(.rtl-cat--grid) .rtl-cat__item {
  background: linear-gradient(160deg,
              rgba(var(--mim-purple-rgb), 0.30),
              rgba(var(--mim-purple-rgb), 0.12));
  color: rgba(255, 255, 255, 0.94);
  border-color: rgba(var(--mim-purple-rgb), 0.46);
}
body.night-mode .rtl-cat:not(.rtl-cat--grid) .rtl-cat__item.is-held {
  background: linear-gradient(160deg,
              rgba(var(--mim-purple-rgb), 0.48),
              rgba(var(--mim-purple-rgb), 0.24));
}

/* [screens 64 and 65] The answer tiles sit back, in light grey.

   Anu, 13 October 2026: *"Let's make the tiles after the statements light
   grey colour on screen 64"*, and *"same on screen 65"*.

   On these two the tiles are the ANSWERS to the five statements above them,
   and the statements are what the learner is reading. The library's default
   is `--_surface`, which is white on white and gives the pool no edge at
   all; light grey separates the answers from the page without competing with
   the framework they belong to. */
.rtl-cat--grid .rtl-cat__item {
  background: var(--mim-grey-light);
  color: var(--mim-black);
  border-color: var(--border);
}
.rtl-cat--grid .rtl-cat__item:hover:not(:disabled) {
  border-color: var(--mim-purple-aa-dark);
}
.rtl-cat--grid .rtl-cat__item.is-held {
  background: #E8E8EA;
}
body.night-mode .rtl-cat--grid .rtl-cat__item {
  background: rgba(255, 255, 255, 0.07);
  color: rgba(255, 255, 255, 0.94);
  border-color: rgba(255, 255, 255, 0.28);
}
body.night-mode .rtl-cat--grid .rtl-cat__item.is-held {
  background: rgba(255, 255, 255, 0.14);
}

/* [screen 78] The confidence rating that closes the module.

   The slider, its read-out and the result block are the activity library's
   own - `.rtl-conf__sure`, `__slider`, `__value`, `__result` - so there is no
   new visual design here and it matches every other activity in the course.
   What this adds is the frame around it, because unlike every other
   `.rtl-act` in the module this one is not a task with a right answer and
   should not look like one: no scored tint, no correct/incorrect rule, just
   a quiet panel on the summary screen.

   See confidence_close() for why it is not `data-activity="confidence"`. */
.m2-conf {
  max-width: var(--measure);
  background: var(--mim-grey-light);
  border-radius: var(--radius-lg);
  padding: clamp(20px, 2.4vw, 28px);
  margin: var(--sp-6) 0;
}
.m2-conf .rtl-act__stem { margin-bottom: var(--sp-5); }
/* The two ends of the scale are labels, not instructions, so they sit back. */
.m2-conf .rtl-conf__sure-label { font-weight: var(--weight-regular);
  color: var(--fg-muted); }
.m2-conf .rtl-conf__result {
  margin-top: var(--sp-5); padding-top: var(--sp-4);
  border-top: 1px solid var(--border);
}
body.night-mode .m2-conf { background: rgba(255, 255, 255, .05); }
body.night-mode .m2-conf .rtl-conf__sure-label { color: rgba(255,255,255,.72); }
body.night-mode .m2-conf .rtl-conf__result {
  border-top-color: rgba(255,255,255,.28);
}
/* ⚠ THE SAME TOKENS AS THE OTHER ORPHANS. `.m2-conf` IS a `.rtl-act`, so it
   inherits them already - this is here only so the night re-pitch applies at
   the same specificity as the day one and the two cannot diverge. */

/* ⚠ SEVEN LIBRARY BUTTONS WERE RENDERING WITH NO FILL, NO BORDER AND NO
   RADIUS, BECAUSE THE TOKENS THAT PAINT THEM ARE DECLARED ON A WRAPPER THEY
   DO NOT HAVE.

   Anu, 13 October 2026: *"Screen 70 - put a tile around check my answer."*
   She is describing the symptom exactly. The button had no tile.

   rtl-activities.css declares `--_accent`, `--_on-accent`, `--_radius` and
   the rest on `.rtl-act` (line 27) and then paints the button from them:

       .rtl-act__btn { background: var(--_accent); border: 2px solid
                       var(--_accent); border-radius: var(--_radius); }

   Those custom properties have NO fallback at the point of use, so on a
   button that is not inside a `.rtl-act` they resolve to nothing, the
   declarations are invalid at computed-value time, and the button falls back
   to the UA's transparent-background, zero-border default. Measured on the
   built page: background rgba(0,0,0,0), border 0px none, radius 0px.

   This module writes four components that borrow the library's button class
   without its wrapper, so SEVEN buttons across FOUR screens were affected and
   only one of them has been looked at:

       screen 62   Look at the model again on screen 8      (ghost)
       screen 67   Start again                              (ghost)
       screen 70   Check my answer                          SOLID
       screen 79   Select all                               (ghost)
       screen 79   Email my ticked resources                SOLID
       screen 79   Email all of them                        (ghost)
       screen 79   View my reflections                      (ghost)

   ⚠ THE TWO SOLID ONES ARE THE WORST OF IT. "Check my answer" is how the
   knowledge check is submitted and "Email my ticked resources" is what the
   whole resources screen exists to do, and both were rendering as plain bold
   text with no affordance at all. A ghost button at least keeps its label
   and its colour; a solid one loses everything that said it was a button.

   ⚠ WHY THIS IS SCOPED TO THE FOUR WRAPPERS AND NOT PUT ON `.rtl-act__btn`
   ITSELF. Declaring the tokens on the button would win over inheritance for
   the ELEVEN buttons that are correctly inside a `.rtl-act`, and those
   inherit a re-pitched `--_accent` and `--_on-accent` at night
   (rtl-activities.css:60). A custom property set on the element beats one
   inherited from an ancestor, so the blanket fix would have broken night
   mode on every activity button in the module to fix seven. The four
   wrappers get the tokens, and the night block mirrors the library's so the
   orphans flip with everything else.

   Four of the seven sit in a module-authored wrapper - `.multi-select`,
   `.mo-order`, `.m2-backto`, `.resources-email-section` - and the other two
   sit straight in `.screen-inner` with no wrapper of their own, so those two
   are named by their own classes. None of the six selectors can ever match
   something inside a `.rtl-act`, which is what keeps the night inheritance
   intact.

   ⚠ IT BELONGS IN rtl-activities.css, as `var(--accent, #5B4FD0)` at the
   point of use rather than on the wrapper, which would make the component
   work anywhere. `_components/` is Tom's and an edit there changes every
   module on the platform, so it is raised with him rather than patched here.
   Module 1 has the same four components and the same seven-ish buttons; it is
   frozen, and it is logged in POST-FREEZE-m1-backlog-2026-10-13.md. */
.multi-select, .mo-order, .m2-backto, .resources-email-section,
.resources-select-all, .view-reflections-btn {
  --_accent:    var(--accent, #5B4FD0);
  --_on-accent: var(--on-accent, #FFFFFF);
  --_text:      var(--fg);
  --_muted:     var(--fg-muted);
  --_border:    var(--border);
  /* ⚠ `var(--radius, 6px)`, NOT --radius-md. The library's own default, so
     these six buttons have the same corner as the eleven that were already
     right. --radius-md is 8px here and the first version of this fix used it,
     which left two corner radii on the same page - measured r8px on screen
     62's button against r6px on screen 74's, two buttons with the same
     treatment. */
  --_radius:    var(--radius, 6px);
  --_dur:       220ms;
  --_ease:      cubic-bezier(.2,.7,.3,1);
}
body.night-mode .multi-select,
body.night-mode .mo-order,
body.night-mode .m2-backto,
body.night-mode .resources-email-section,
body.night-mode .resources-select-all,
body.night-mode .view-reflections-btn {
  --_accent:    var(--accent-on-dark, #CDBCFF);
  --_on-accent: var(--on-accent-dark, #1A1A1A);
  --_text:      #FFFFFF;
  --_muted:     rgba(255,255,255,.72);
  --_border:    rgba(255,255,255,.28);
}

/* ── Replay clip ───────────────────────────────────────────────────────────
   Anu, 5 October 2026. The pause mechanic takes Vimeo's scrubber away, so a
   learner gets one pass at a twenty-second clip and then has to answer a
   question that turns on what was said in it. This gives the pass back.

   Set on `.transcript-toggle`'s pill rather than a look of its own: the two
   sit side by side in `.video-controls`, which already centres them on the
   film, and two different pills on one row would read as two different kinds
   of control. Built by `_components/rtl-film-player.js`, which only adds it to
   pause-and-choose screens - a film played whole still has Vimeo's scrubber. */
.film-replay {
  background: none; border: 1px solid rgba(0,0,0,0.25); border-radius: var(--radius-pill);
  padding: 7px 16px; font-family: var(--font-primary); font-size: 12px;
  font-weight: var(--weight-semibold); letter-spacing: 0.08em; text-transform: uppercase;
  color: var(--fg-muted); cursor: pointer;
  transition: border-color var(--dur-base) var(--ease-standard),
              color var(--dur-base) var(--ease-standard);
}
.film-replay:hover { border-color: var(--mim-purple); color: var(--mim-purple-aa-dark); }
body.night-mode .film-replay { border-color: rgba(255,255,255,0.28); color: rgba(255,255,255,0.7); }
body.night-mode .film-replay:hover { border-color: var(--mim-purple); color: var(--mim-purple); }

/* ── the evidence accordions carry a book, not a Mo ────────────────────────
   Anu, 5 October 2026: *"The Mo icon appears in the evidence and references.
   I don't want this to appear on each accordion. Also can we change the icon
   itself from a Mo to a book?"*

   Nine accordions across seven screens carried the moustache mark, twice over
   on two of them. A brand mark repeated nine times inside one module stops
   reading as branding and starts reading as a bullet.

   Done as a MASK rather than the background image the engine uses, so the
   glyph takes its colour from this rule. That covers both themes in one
   declaration and lets the engine's night-mode `filter: invert(1)
   brightness(2)` be switched off - it was there to make a black PNG legible
   on dark, and inverting a colour chosen for dark would undo it.

   ⚠ THE SVG IS PERCENT-ENCODED. The first attempt inlined it raw; the
   unescaped < and > made the whole declaration invalid, the browser dropped
   it, and the Mo was still there with `mask: none` computed.

   The 44px grid column and the 28px box are untouched, so the row geometry
   and the centred label do not move. The glyph draws at 22px inside that box,
   the same optical weight as the mark it replaces.

   ⚠ The class is still `.evidence__mo`. Renaming it means touching the
   engine, both modules' markup and three builders for a cosmetic gain, so the
   name stays and this comment is the explanation. */
.evidence__mo {
  background-image: none;
  background-color: var(--fg-muted);
  -webkit-mask: url("data:image/svg+xml,%3Csvg%20xmlns='http://www.w3.org/2000/svg'%20viewBox='0%200%2024%2024'%20fill='none'%20stroke='%23000'%20stroke-width='1.7'%20stroke-linecap='round'%20stroke-linejoin='round'%3E%3Cpath%20d='M12%207C10.5%205.7%208.6%205%206.5%205H3v12h3.5c2.1%200%204%20.7%205.5%202'/%3E%3Cpath%20d='M12%207c1.5-1.3%203.4-2%205.5-2H21v12h-3.5c-2.1%200-4%20.7-5.5%202'/%3E%3Cpath%20d='M12%207v12'/%3E%3C/svg%3E") left center / 22px 22px no-repeat;
          mask: url("data:image/svg+xml,%3Csvg%20xmlns='http://www.w3.org/2000/svg'%20viewBox='0%200%2024%2024'%20fill='none'%20stroke='%23000'%20stroke-width='1.7'%20stroke-linecap='round'%20stroke-linejoin='round'%3E%3Cpath%20d='M12%207C10.5%205.7%208.6%205%206.5%205H3v12h3.5c2.1%200%204%20.7%205.5%202'/%3E%3Cpath%20d='M12%207c1.5-1.3%203.4-2%205.5-2H21v12h-3.5c-2.1%200-4%20.7-5.5%202'/%3E%3Cpath%20d='M12%207v12'/%3E%3C/svg%3E") left center / 22px 22px no-repeat;
}
body.night-mode .evidence__mo {
  filter: none;                       /* the engine inverts a PNG that is gone */
  background-color: rgba(255, 255, 255, 0.72);
}

/* ═══════════════════════════════════════════════════════════════════════════
   Brought over from Module 1, 5 October 2026, on Anu's instruction that the
   design decisions made during her Module 1 review should carry into Module 2.

   What came over, what did not, and why - so the next person does not have to
   re-derive it:

     ported   transcripts start closed        3 panels here, all were open
     ported   the resource list lies flat     the resources screen
     ported   .mo-purple                      so the deck's ++purple++ renders
     ported   the :has(iframe) frame guard    defensive; see below
     already  the book on the evidence rows
     already  the Replay clip button

     SKIPPED  the white ground behind a transcript. Module 1's film screen is
              `.screen--nav` with the photocopy texture, and nine paragraphs of
              transcript landed on the grain. Module 2's film screens are
              `.screen--multimedia` on flat black with a scrim, so there is no
              grain to read through and white would be wrong.
     SKIPPED  the nav-tutorial highlight fix. `NAV_TUTORIAL_SCREEN: -1` here -
              this module has no tutorial screen, so there is nothing to leak.

   ⚠ NOT A DESIGN CHANGE: "one purple element on every screen". Measured
   before porting anything, screen by screen through the engine's own goTo:
   **Module 2 already carries purple on 79 of 79 screens.** Module 1 carries it
   on 59 of 62, missing only the landing, screen 2 and the summary. So there is
   nothing to bring over, and the module that needs the pass is Module 1.
   Counting it needs care: on a part-divider the purple is the highlighter
   block behind black text, which is a background IMAGE, so a colour-only
   check reads those screens as having none.
   ═══════════════════════════════════════════════════════════════════════════ */

/* ── transcripts start closed ──────────────────────────────────────────────
   `toggleTranscript()` flips `.open` and swaps the button label, and nothing
   in the engine's CSS responds to `.open` - so every panel shipped expanded
   under a button inviting the learner to read it. Measured here before the
   fix: screen 6 at 1728px, screen 40 at 691px and screen 42 at **2649px** of
   transcript under the film.

   `display: none` rather than height or visibility, so a closed panel leaves
   the accessibility tree - which is what the toggle's own `aria-expanded`
   already claims. */
.hifh__transcript:not(.open),
.transcript-panel:not(.open) { display: none; }

/* ── purple emphasis in body copy ──────────────────────────────────────────
   The pair for the deck's `++text++` marker, which `build_module.py` renders
   to this class. `--mim-purple-aa-dark`, NOT `--mim-purple-aa`: this sits in
   body copy and the AA-large purple is only good from 24px up. */
.mo-purple {
  color: var(--mim-purple-aa-dark);
  font-weight: var(--weight-semibold, 600);
}
body.night-mode .mo-purple { color: var(--accent-on-dark); }

/* ── the resource list lies flat ───────────────────────────────────────────
   ⚠ THE SHADOW IS THE ONLY THING GIVING THESE ROWS A SHAPE - no background,
   no border, no radius. Removing it alone turns the list into runs of text
   with nothing between them, so a hairline takes its place. It costs no
   layout: the border sits inside the padding the rows already have. */
.screen label.resource-item__label,
.screen label.resource-item__label:hover { box-shadow: none; }
.resources-list > li > .resource-item__label {
  border-bottom: 1px solid rgba(0, 0, 0, 0.10);
}
.resources-list > li:last-child > .resource-item__label { border-bottom: none; }
body.night-mode .resources-list > li > .resource-item__label {
  border-bottom-color: rgba(255, 255, 255, 0.14);
}
body.night-mode .resources-list > li:last-child > .resource-item__label {
  border-bottom: none;
}

/* ── the film frame keeps its ratio when the ENGINE mounts a player ────────
   Defensive here: all three of this module's films are on
   `data-action="filmPlay"`, so `rtl-film-player.js` mounts them and sets
   `.is-playing` itself. If a screen is ever put on the engine's own
   `playVideo()` it would hit what Module 1's screen 10 hit - the engine
   appends the iframe, hides the poster and sets nothing, and
   `[data-happy-frame]` has no height of its own, so the frame measures
   700x0 and the film vanishes. `:has(iframe)` restores the ratio the moment a
   player exists, however it got there. */
.video-frame[data-happy-frame]:has(iframe) {
  aspect-ratio: 16 / 9;
  height: auto;
  overflow: hidden;
}


/* ── References, one list at the foot of each lesson ───────────────────────
   Anu, 7 October 2026, on screen 17: *"Move the footnotes to the references
   section at the end of the lesson. Make this change throughout the module."*

   Built by `refs_to_lesson_end()`, which also renumbers every
   <sup class="ref"> so a lesson runs 1..n instead of each screen restarting
   at 1. The rule moves off the list and onto this wrapper, because the
   heading is now what separates the references from the screen above. */
.lesson-refs {
  margin-top: var(--sp-6); padding-top: var(--sp-4);
  border-top: 1px solid var(--border, #D8D6E0); max-width: var(--measure);
}
body.night-mode .lesson-refs { border-top-color: rgba(255,255,255,0.22); }
.lesson-refs__title {
  margin: 0 0 var(--sp-3);
  font-family: var(--font-primary); font-weight: var(--weight-semibold);
  font-size: calc(11px * var(--fs-scale)); letter-spacing: 0.14em;
  text-transform: uppercase; color: var(--mim-purple-aa-dark);
}
body.night-mode .lesson-refs__title { color: #cdbcff; }
.lesson-refs .refs { margin-top: 0; padding-top: 0; border-top: none; }

/* ── the wrong-answer cross is a glyph, not a disc ─────────────────────
   Anu, 7 October 2026, on screen 19: *"the cross button doesn't look right.
   Can we just have the x without the circle?"*

   ⚠ THE TICK KEEPS ITS DISC. She asked about the cross and only the cross,
   and a filled green badge on the right answer is doing different work from a
   mark on a wrong one. Raised rather than changed. */
.quiz-option[data-fb].incorrect::after {
  background: none; color: #B3261E;
  width: auto; height: auto; border-radius: 0;
  font-size: calc(19px * var(--fs-scale)); font-weight: var(--weight-regular);
}
body.night-mode .quiz-option[data-fb].incorrect::after { color: #FF8A80; }

/* ── a tile the learner has opened is ticked ─────────────────────────
   Anu, 7 October 2026, on screen 23: *"can we have a tick mark after each tile
   is clicked so learners know what they have already completed."*

   The engine already sets `data-viewed="1"` on a marker the first time its
   panel opens (`mim-gp-shared.js:2208`) and reads it for the completion gate,
   so this is a look for state that was being tracked and never shown. It is
   on the ROW variant, which is the tile shape - the markers over a photograph
   or a diagram are a different thing and keep their own numbering.

   ⚠ THE BASE RULE SETS `position: static`, so the tick needs the tile put
   back into the positioning context or it lands on the screen, not the tile. */
.mim-hotspots--row .mim-hotspots__marker { position: relative; }
.mim-hotspots--row .mim-hotspots__marker[data-viewed]::after {
  content: "✓"; position: absolute; top: 7px; right: 10px;
  font-family: 'Segoe UI Symbol', 'Apple Symbols', 'Noto Sans Symbols 2',
               var(--font-primary);
  font-size: calc(14px * var(--fs-scale)); line-height: 1;
  color: var(--mim-green, #1F7A52);
}
.mim-hotspots--row .mim-hotspots__marker.is-active[data-viewed]::after,
body.night-mode .mim-hotspots--row .mim-hotspots__marker[data-viewed]::after {
  color: #6FD3A4;
}

/* ── TRY beside COMPARE ───────────────────────────────────────
   Anu, 7 October 2026, on screen 33: *"can the try and compare boxes be in
   different colours so they can be easily distinguished? Also the try and
   compare boxes must be next to each other so they can be compared easily."*

   They were stacked, TRY above COMPARE, in the same purple rule - so the
   thing to say and the thing it is being contrasted with looked identical and
   were a scroll apart. Paired into two columns by `pair_try_compare()`.

   ⚠ THIS IS NOT A THIRD PHRASE CARD. The 7 October call was one card
   everywhere (see PHRASE_VARIANT), and this keeps it: same card, same
   geometry, same face. Only the rule colour changes, and only on COMPARE,
   because COMPARE is the one place in the module where two phrasings are
   deliberately set against each other. Everything else stays purple. */
.mo-phrases-pair {
  display: grid; grid-template-columns: 1fr 1fr;
  gap: var(--sp-4) var(--sp-5); align-items: start;
  max-width: var(--measure); margin: var(--sp-4) 0 var(--sp-5);
}
@media (max-width: 760px) {
  .mo-phrases-pair { grid-template-columns: 1fr; gap: var(--sp-3); }
}
.mo-phrases-pair > * { min-width: 0; }
.mo-phrases-pair .mo-phrases__label { margin-top: 0; }
.mo-phrases-pair .mo-phrases { margin-bottom: 0; }
/* COMPARE: the same card with a muted rule and a muted ground, so it reads as
   the one being held up against TRY rather than a second thing to say. */
.mo-phrases--compare .mo-phrase {
  border-left: 3px solid var(--fg-muted); background: var(--mim-grey-light);
}
body.night-mode .mo-phrases--compare .mo-phrase {
  border-left-color: rgba(255,255,255,0.45); background: #232329;
}
.mo-phrases-pair .mo-phrases__label--compare { color: var(--fg-muted); }
body.night-mode .mo-phrases-pair .mo-phrases__label--compare {
  color: rgba(255,255,255,0.68);
}


/* ── numbered steps ─────────────────────────────────────────────
   Anu, 7 October 2026, on screen 37: *"I don't like the way the content is
   laid out in the screen. Is there a different way we can show this
   information?"* Built by `numbered_steps()`.

   The number is a CSS counter, not markup, so a step cannot end up numbered
   by hand and then be wrong when one moves. The rule down the left is what
   carries the eye between the two steps - without it the step body, which
   holds a phrase card and a quote, runs back into the page. */
.mo-steps {
  list-style: none; counter-reset: mo-step;
  margin: var(--sp-5) 0 0; padding: 0; max-width: var(--measure);
}
.mo-step {
  counter-increment: mo-step; position: relative;
  padding: 0 0 var(--sp-5) var(--sp-6);
  border-left: 2px solid var(--border);
}
.mo-step:last-child { padding-bottom: 0; border-left-color: transparent; }
.mo-step::before {
  content: counter(mo-step);
  position: absolute; left: 0; top: 0; transform: translateX(-50%);
  width: 32px; height: 32px; border-radius: 999px;
  display: grid; place-items: center;
  background: var(--mim-purple); color: var(--mim-black);
  font-family: var(--font-primary); font-weight: var(--weight-semibold);
  font-size: calc(15px * var(--fs-scale)); line-height: 1;
}
.mo-step__title {
  margin: 4px 0 var(--sp-3);
  font-family: var(--font-primary); font-weight: var(--weight-semibold);
  font-size: calc(20px * var(--fs-scale)); line-height: 1.3; color: var(--fg);
}
.mo-step__body > *:first-child { margin-top: 0; }
.mo-step__body > *:last-child { margin-bottom: 0; }
body.night-mode .mo-step { border-left-color: rgba(255,255,255,0.22); }
body.night-mode .mo-step:last-child { border-left-color: transparent; }
body.night-mode .mo-step__title { color: rgba(255,255,255,0.92); }
@media (max-width: 760px) {
  .mo-step { padding-left: var(--sp-5); }
  .mo-step::before { width: 28px; height: 28px; }
}

/* Two cases, side by side, because they are alternatives and the deck already
   wrote them as a pair - one paragraph with a <br> between two sentences that
   both open "If they name ...". */
.mo-ifthen {
  display: grid; grid-auto-flow: column; grid-auto-columns: 1fr;
  gap: var(--sp-4); margin: var(--sp-4) 0 0;
}
@media (max-width: 760px) {
  .mo-ifthen { grid-auto-flow: row; grid-auto-columns: auto; gap: var(--sp-3); }
}
.mo-ifthen__case {
  padding: 14px 16px; border: 1px solid var(--border);
  border-radius: 10px; background: var(--mim-grey-light);
}
.mo-ifthen__case .body { margin: 0; font-size: calc(15px * var(--fs-scale)); }
body.night-mode .mo-ifthen__case {
  background: #232329; border-color: rgba(255,255,255,0.18);
}


/* ── the framework grid's columns are grey, not black ──────────────────
   Anu, 9 October 2026, on screen 62: *"change the black boxes to light grey."*

   ⚠ THEY WERE NEVER MEANT TO BE BLACK. `.dia-field` was written for the
   biopsychosocial drawing and fills CIRCLES only, so the three <rect> columns
   on this screen fell through to SVG's default fill, which is black. A filled
   black column behind an empty grid reads as content the learner is missing,
   which is the opposite of what the screen says. */
.dia-field rect { fill: var(--mim-grey-light); }
body.night-mode .dia-field rect { fill: #232329; }


/* ── the Tips block breathes, and its tips are the arrow list ────────
   Anu, 9 October 2026, on screen 43: *"Tips section needs more space before
   and after. Also the tips need to appear as a list - maybe use the purple
   arrows."* The heading had the body's own paragraph gap above it, so it sat
   in the run of text rather than opening a block of its own. */
.tips-heading { margin: var(--sp-6) 0 var(--sp-3); }
.mo-arrows--tips { margin-bottom: var(--sp-6); }


/* ── the framework, filled in and open ────────────────────────
   Anu, 9 October 2026: the two worked examples were seven-row accordions, and
   seven clicks to read 173 words is a bad trade. They are a list now, and the
   label column echoes the grid the screen before them teaches. */
.mo-rows {
  display: grid; gap: 0; max-width: var(--measure);
  margin: var(--sp-4) 0 var(--sp-5);
  border-top: 1px solid var(--border);
}
.mo-rows__row {
  display: grid; grid-template-columns: minmax(0, 11em) minmax(0, 1fr);
  gap: 4px var(--sp-4); padding: var(--sp-4) 0;
  border-bottom: 1px solid var(--border);
}
@media (max-width: 680px) { .mo-rows__row { grid-template-columns: minmax(0, 1fr); } }
body.night-mode .mo-rows { border-top-color: rgba(255,255,255,0.18); }
body.night-mode .mo-rows__row { border-bottom-color: rgba(255,255,255,0.18); }
.mo-rows__label {
  margin: 0; font-family: var(--font-primary); font-weight: var(--weight-semibold);
  font-size: calc(13px * var(--fs-scale)); line-height: 1.35;
  letter-spacing: 0.04em; text-transform: uppercase;
  color: var(--mim-purple-aa-dark);
}
body.night-mode .mo-rows__label { color: #cdbcff; }
.mo-rows__body > *:first-child { margin-top: 0; }
.mo-rows__body > *:last-child { margin-bottom: 0; }
/* The three columns, restored 9 October 2026. The rows were flattened when the
   accordion became an open list, so a column (Needs) and a row inside another
   column (Spiritual) were set at the same level. A column head now sits above
   its own rows, and the rule above each head is what separates the three. */
.mo-rows--cols { border-top: 0; }
.mo-rows__col { display: grid; gap: 0; border-top: 2px solid var(--mim-purple-aa-dark); }
.mo-rows__col + .mo-rows__col { margin-top: var(--sp-5); }
.mo-rows__col-head {
  margin: 0; padding: var(--sp-3) 0 var(--sp-2);
  font-family: var(--font-primary); font-weight: var(--weight-bold);
  font-size: calc(15px * var(--fs-scale)); line-height: 1.3;
  color: var(--mim-purple-aa-dark);
}
.mo-rows__row--solo { grid-template-columns: minmax(0, 1fr); }
body.night-mode .mo-rows__col { border-top-color: #cdbcff; }
body.night-mode .mo-rows__col-head { color: #cdbcff; }


/* ── a film screen: setup beside the film ─────────────────────
   Anu, 9 October 2026, on screen 42. The setup ran 101 words down a narrow
   left column with the right half of the screen empty and the film below the
   fold. Both of Module 2's film screens use this, so the course has one film
   layout rather than one each.

   ⚠ THE MEDIA COLUMN COMES SECOND IN THE MARKUP, which is the order a
   screen reader and a phone both want: read the setup, then reach the film.
   The grid places it on the right on a wide screen without reordering it. */
.mo-film {
  display: grid; grid-template-columns: minmax(0, 1fr) minmax(0, 1.05fr);
  gap: var(--sp-5) clamp(24px, 3.5vw, 48px);
  align-items: start; margin: var(--sp-4) 0 var(--sp-5);
}
@media (max-width: 900px) { .mo-film { grid-template-columns: minmax(0, 1fr); } }
.mo-film__text > *:first-child { margin-top: 0; }
.mo-film__text > *:last-child { margin-bottom: 0; }
.mo-film__media { min-width: 0; }
/* The last thing read before the play button, so it carries the purple rule
   the module uses for a line that tells the learner to do something. */
/* ⚠ NO LONGER A STATEMENT, AND THE CLASS IS KEPT ANYWAY. Anu, 13 October
   2026, on screen 42: *"what she does with that score. That doesn't need to
   be a statement."* The purple rule and the semibold are what made it read as
   one; the class is what `film_two_column()` uses to place the line at the
   foot of the text column, immediately above the play button, which is the
   part that has to stay. So the treatment comes off and the hook remains. */
.mo-film__watch { margin-top: var(--sp-4); }
/* ⚠ THE TEXTURE COMES FROM TOM'S SHARED STYLESHEET, NOT FROM THIS MODULE.
   mim-gp-shared.css:243 puts the white photocopy texture on
   `.screen[data-type="video"]:not(.screen--multimedia)` - which is exactly
   what this screen became when it stopped being a cinematic film screen. So
   making it plain was not a matter of removing something here; it had to be
   overridden. _components/ is Tom's and he is on leave, so the override lives
   in this module's own stylesheet, which @imports the shared one above it and
   therefore wins at equal specificity on order alone.

   Anu, 9 October 2026, on screen 42: *"make it a plain white background
   instead of a textured one."* One screen in this module matches, and one in
   Module 1 - the pause-and-play screen this one was built to match. */
.screen[data-type="video"]:not(.screen--multimedia) {
  background-image: none; background-color: var(--mo-white, #fff);
}
body.night-mode .screen[data-type="video"]:not(.screen--multimedia) {
  background-color: transparent;
}


/* ── a linked citation ──────────────────────────────────────────────────────
   Underlined only on hover and focus. A column of underlined citations turns
   the block into a wall of rules, and the list already reads as references
   from its position and its heading. Colour is not the only signal: these sit
   inside an <ol> that announces what they are, and the underline arrives the
   moment a pointer or the keyboard reaches one.

   Ported from Module 1, which has carried this rule since its references were
   first linked. */
.lesson-refs__link {
  color: var(--mim-purple-aa-dark); text-decoration: none;
}
.lesson-refs__link:hover,
.lesson-refs__link:focus-visible { text-decoration: underline; }
body.night-mode .lesson-refs__link { color: #cdbcff; }


/* ── the lesson closer, as a commitment ─────────────────────────────
   See tools/sharet/m3_commit_closers.py.

   ⚠ THE UNCHOSEN LINES DO NOT FADE. A closer is reference material and the
   learner is meant to read all of it; dimming the seven they did not pick
   would make the screen worse than the flat list it replaced. The chosen two
   gain a mark and a ground, and nothing else moves. */
.mo-commit__stem {
  font-family: var(--font-feature); font-size: calc(19px * var(--fs-scale));
  line-height: 1.35; margin: 0 0 var(--sp-2);
}
.mo-commit__note { font-size: calc(14px * var(--fs-scale)); opacity: .78; margin: 0 0 var(--sp-3); }
.mo-commit__item {
  display: block; width: 100%; text-align: left;
  font: inherit; color: inherit;
  background: transparent; border: 0;
  border-radius: 8px;
  padding: 6px 10px 6px 34px; margin: 0 0 0 -10px;
  position: relative; cursor: pointer;
}
/* The tick box sits where the list marker was, so the line does not shift
   when it is chosen. */
.mo-commit__item::before {
  content: ""; position: absolute; left: 8px; top: 0.42em;
  width: 15px; height: 15px; border-radius: 4px;
  border: 1.5px solid var(--mim-purple-aa-dark, #5B3DF5);
  background: transparent;
}
.mo-commit__item:hover { background: rgba(107, 78, 255, .07); }
.mo-commit__item:focus-visible { outline: 2px solid var(--mim-purple-aa-dark, #5B3DF5); outline-offset: 2px; }
.mo-commit__item[aria-pressed="true"] { background: #EDE6FF; }
.mo-commit__item[aria-pressed="true"]::before {
  background: var(--mim-purple-aa-dark, #5B3DF5);
  border-color: var(--mim-purple-aa-dark, #5B3DF5);
}
.mo-commit__item[aria-pressed="true"]::after {
  content: ""; position: absolute; left: 12.5px; top: 0.62em;
  width: 4px; height: 8px; border: solid #fff;
  border-width: 0 2px 2px 0; transform: rotate(45deg);
}
/* A full list disables what is left, so the rule is visible rather than
   discovered by clicking. */
.mo-commit__item[disabled] { cursor: default; opacity: 1; }
.mo-commit__item[disabled]:hover { background: transparent; }
.mo-commit__done {
  font-size: calc(14px * var(--fs-scale));
  color: var(--mim-purple-aa-dark, #5B3DF5);
  margin: var(--sp-2) 0 0;
}
.mo-commit__done[hidden] { display: none; }
.mo-commit__reflect { margin-top: var(--sp-3); }
.mo-commit__reflect[hidden] { display: none; }

body.night-mode .mo-commit__item::before { border-color: #cdbcff; }
body.night-mode .mo-commit__item:hover { background: rgba(205, 188, 255, .12); }
body.night-mode .mo-commit__item[aria-pressed="true"] { background: rgba(205, 188, 255, .20); }
body.night-mode .mo-commit__item[aria-pressed="true"]::before {
  background: #cdbcff; border-color: #cdbcff;
}
body.night-mode .mo-commit__item[aria-pressed="true"]::after { border-color: #1A1A1A; }
body.night-mode .mo-commit__done { color: #cdbcff; }

/* The list loses its bullets where it has become a set of controls: the tick
   box is the marker now, and two markers on one line is a mess.

   ⚠ `list-style: none` IS NOT ENOUGH, and the first version stopped
   there. `.list li` draws its marker as an 8px purple ::before block, not as
   a list marker, so it survived and every line rendered with a square AND a
   tick box. Caught by reading the computed ::before rather than by looking
   at a screenshot, which had the stagger mid-flight and hid it.

   The same ::before carries the stagger's sparkle, and losing that here is
   correct: Anu, 15 October, *"All What you can do now screens - no animated
   bullets."* */
.mo-commit-list { list-style: none; padding-left: 0; }
.mo-commit-list > li { margin: 0 0 2px; }
.mo-commit-list > li::before { display: none !important; content: none !important; }


/* ── replay-on-film-contrast ────────────────────────────────
   See tools/sharet/replay_on_film_contrast.py.

   A film screen is black in DAY mode as well as night, and `.film-replay` is
   styled for a light ground: --fg-muted text inside a 25% BLACK border. On
   screen 6 that measured 2.37:1 for the text and 1.00:1 for the border - the
   control is three-quarters invisible and reads as disabled.

   The values are chosen to clear AA rather than to look lively: white at 78%
   composites to about 9:1 on black, and the border at 45% to about 4.4:1,
   which is past the 3.0 a control edge needs. Hover keeps the module's
   purple, in the lighter tone the film screens already use for .mo-hl.

   ⚠ THE SELECTOR MATCHES BOTH SCREEN CLASSES, as the .mo-hl override does:
   a film screen carries .video-screen and .screen--multimedia and different
   builds have leaned on each. */
.video-screen .film-replay,
.screen--multimedia .film-replay {
  color: rgba(255, 255, 255, 0.78);
  border-color: rgba(255, 255, 255, 0.45);
}
.video-screen .film-replay:hover,
.video-screen .film-replay:focus-visible,
.screen--multimedia .film-replay:hover,
.screen--multimedia .film-replay:focus-visible {
  color: #B7A3FC;
  border-color: #B7A3FC;
}


/* ── anu-16oct-conversation-guide ─────────────────────────
   The guide on screen 116 is 57 lines across nine lessons. Grouped under the
   lesson that taught each one, it is a document; ungrouped it was a 3,854px
   wall, which is what the first build of it produced. */
/* ⚠ THE RESOURCE LINKS HAD NO COLOUR AT ALL AND FELL THROUGH TO THE
   BROWSER'S DEFAULT. `#0000EE` on the night ground is 2.09:1, and the night
   audit named all twelve of them the moment this screen existed. Modules 1
   and 2 both carry this rule; Module 3 never had a resources screen to need
   it, so it was never ported. These are the same declarations, verbatim. */
.resource-item__link {
  display: inline-block; margin: 0 0 14px 32px;
  font-family: var(--font-primary); font-weight: var(--weight-semibold);
  font-size: calc(11px * var(--fs-scale)); letter-spacing: 0.12em;
  text-transform: uppercase; color: var(--mim-purple-aa-dark);
  text-decoration: none;
}
.resource-item__link:hover { text-decoration: underline; }
body.night-mode .resource-item__link { color: #cdbcff; }
.resources-select-all { margin-top: var(--sp-4); }
.resource-item__link--dl { margin-left: 12px; }

/* ⚠ AND A TICKED RESOURCE GOES BLACK ON THE NIGHT PANEL WITHOUT THIS.
   `.summary-resource-item.tagged .resource-item__text` is three classes
   and beats the engine's two-class night rule, so the row reverts to
   `--fg`. It only bites on a resource that is ticked, which until the
   conversation guide started arriving pre-ticked was none of them - the
   night audit caught it on the guide's own row at 1.0:1.

   Module 1 hit this on its screen 62 and fixed it the same way; Module 3
   never had a resources screen to inherit the fix. Second night-only
   fault from that same gap today, after the link colour. */
body.night-mode .summary-resource-item.tagged .resource-item__text {
  color: rgba(255,255,255,0.92);
}

.m3-guide__lesson {
  font-family: var(--font-primary); font-weight: var(--weight-semibold);
  font-size: var(--t-micro); letter-spacing: 0.08em; text-transform: uppercase;
  color: var(--mim-purple-aa-dark);
  margin: var(--sp-5) 0 10px;
}
body.night-mode .m3-guide__lesson { color: #cdbcff; }

/* A line the learner marked on its closer. The tick is the mark; the card
   keeps the same shape as every other phrase card in the course. */
.mo-phrase.is-marked { border-left-color: var(--mim-green, #C2E189); }
.m3-guide__tick {
  float: left; margin-right: 10px; font-weight: var(--weight-semibold);
  color: #1F7A52;
}
body.night-mode .m3-guide__tick { color: #4FBF8B; }

/* m4-review-27oct */

/* ── Anu's review, 27 October ──────────────────────────────────────────────

   THE TRANSCRIPT READS BLACK, LIKE THE BODY COPY BESIDE IT.
   Anu: *"the transcript is hard to read as it's in a light grey colour.
   Please make the text colour black like the rest of the modules."*

   /!\ THIS OVERRIDES THE #767676 RULE ABOVE, WHICH STAYS. That rule came
   from Module 2's design layer, where the transcript sits on a BLACK film
   screen and #767676 was the one value that passed on both grounds. Module
   4's film screens went white on 21 October at Anu's request, so the
   compromise buys nothing here and costs legibility: measured on screen 8,
   #767676 at 12px against black 16px body copy.

   /!\ AND THE SPEAKER LABEL WAS FAILING AA AT NIGHT - 4.33:1 on the night
   ground, where 4.5 is needed. The 26 October fix targeted
   `.hifh__transcript p` and left `.transcript-speaker` behind. Neither is
   visible to audit.js: the panel is collapsed, so there is nothing rendered
   to measure. Both are set together here so they cannot drift again. */
.hifh__transcript p,
.hifh__transcript .transcript-line,
.transcript-speaker { color: var(--fg); }
body.night-mode .hifh__transcript p,
body.night-mode .hifh__transcript .transcript-line,
body.night-mode .transcript-speaker { color: rgba(255, 255, 255, 0.92); }

/* A diagram that needs to be read, not glanced at.
   Anu, on screens 25 and 30: *"make it bigger. It's too small at the
   moment."* The figure is capped at `--measure`, about 700px, which renders
   a 1160-wide artboard at 652px. This breaks it out of the column.

   /!\ NEGATIVE INLINE MARGINS, NOT A TRANSFORM. `.screen-inner` caps the
   column and a transform would take the figure out of flow, so the body copy
   below would not move down for it. */
.mo-figure--diagram.is-wide {
  width: min(1040px, 92vw);
  max-width: none;
  margin-inline: calc((100% - min(1040px, 92vw)) / 2);
  padding: var(--sp-4);
}
.mo-figure--diagram.is-wide img { width: 100%; height: auto; display: block; }

/* The heading Anu asked to "stand out" on screen 30, and the block it opens.
   A heading, not a title: the screen already has an h1 and a second one
   would be wrong for a screen reader walking the page. */
.m4-standout {
  font-family: var(--font-feature);
  font-size: calc(26px * var(--fs-scale));
  line-height: 1.25; margin: var(--sp-6) 0 var(--sp-4);
  padding-left: 16px; border-left: 4px solid var(--mim-purple-aa);
}
.m4-after-commit[hidden] { display: none; }

/* Screen 19's Open links sit under their bullet, not trailing off the end of
   it. Anu, 27 October: *"can we put the open link right below each bullet
   point and align it properly."*

   /!\ THE 32px INDENT IS NOT A SCREEN 19 STYLE. `.resource-item__link` is
   the resources screen's control and carries `margin-left: 32px` to clear
   the checkbox in front of it. Screen 19 has no checkbox since the ticking
   came off this morning, so the link was hanging 32px to the right of
   nothing. Block, and flush with the text it belongs to.

   /!\ SCOPED TO `.list`. Screen 84's list is `.resources-list`, where the
   inline-block and the 32px are correct and must stay. */
.list .resource-item__link {
  display: block;
  margin: 8px 0 0;
}

/* m4-review-27oct-b */

/* ── Anu's second review, 27 October ───────────────────────────────────────

   THE PREDICT OPTIONS TAKE A PALE PURPLE GROUND.
   Anu: *"With the predict screens, the options are all white and I feel we
   need a little bit more colour. Can we make this a very very pale purple
   box instead?"*

   /!\ THE ANSWERED STATES ARE NOT TOUCHED. `is-correct` and `is-incorrect`
   paint the option once the learner has chosen, and overriding the resting
   background without scoping would wash the feedback out. This is the
   resting state only. */
.rtl-predict__option:not(.is-correct):not(.is-incorrect) {
  background: #F6F3FE;
  border-color: #E0D8FB;
}
body.night-mode .rtl-predict__option:not(.is-correct):not(.is-incorrect) {
  background: rgba(183, 163, 252, 0.09);
  border-color: rgba(183, 163, 252, 0.28);
}

/* Screen 15's two panels, in a pale purple gradient.
   Anu: *"Can we also have the two tiles in light purple colour gradient?"* */
.rtl-pair__panel {
  background: linear-gradient(160deg, #F7F4FE 0%, #EFE9FD 100%);
  border-color: #E0D8FB;
}
body.night-mode .rtl-pair__panel {
  background: linear-gradient(160deg, rgba(183,163,252,0.12) 0%,
                                      rgba(183,163,252,0.05) 100%);
  border-color: rgba(183, 163, 252, 0.26);
}

/* The copy a predict holds back until the learner chooses. */
.m4-after-choose[hidden] { display: none; }

/* Screen 30's heading is a title, not a statement.
   Anu: *"Reading a PSA result should be a title not statement."* The purple
   left rule is the statement device and had to come off it. */
.m4-psa-title {
  font-family: var(--font-feature);
  font-size: calc(26px * var(--fs-scale));
  line-height: 1.25; margin: var(--sp-6) 0 var(--sp-4);
}

/* Screen 56's quotation needs air.
   Anu: *"the quote needs more spacing before and after."* */
.mo-quote { margin-block: var(--sp-7, 48px); }

/* /!\ SCREEN 46 SHOWED ITS ANSWER BEFORE ITS VERDICT. The sequence's
   feedback paragraph sits after the Check button in the library's markup,
   and the per-step detail it reveals sits inside the list ABOVE it - so a
   correct order opened five phrasing boxes and put "That is the right order"
   underneath them. Anu: *"the feedback should appear first before the
   sayings boxes. Right now the boxes appear and I am left confused whether
   the answer was right or wrong."*

   Ordered rather than moved in script: the markup stays the library's. */
.rtl-act.rtl-sequence { display: flex; flex-direction: column; }
.rtl-act.rtl-sequence > .rtl-act__stem { order: 1; }
.rtl-act.rtl-sequence > .rtl-sequence__feedback { order: 2; margin-block: var(--sp-4); }
.rtl-act.rtl-sequence > .rtl-sequence__list { order: 3; }
.rtl-act.rtl-sequence > [data-action="sequence:check"] { order: 4; }

/* m4-review-27oct-c */

/* ── Anu's third review, 27 October ────────────────────────────────────────

   The phrasing headings inside screen 72's model answer were competing with
   the screen's own title. Anu: *"Reduce the title font size."*

   /!\ SCOPED TO THE COMPARE BLOCK. `.heading` is the module's h2 everywhere
   else - the closers' group headings are the same class - so an unscoped
   size change would shrink those too. */
/* ── screen 28's two paths are click-to-reveal cards ─────────────────────
   Anu, 27 October: *"I want the buttons to act like click to reveal cards
   with the feedback displayed below that particular button only."*

   (!) THIS REPLACES THE GRID-PLACEMENT VERSION written an hour earlier,
   which pulled each body out of its own column and spanned it across both.
   That answered the first request - buttons next to each other, answer
   below - and not this one: a learner opening the left card got its answer
   under BOTH buttons. Each reveal keeps its own markup now, so the body
   lands under the button it belongs to.

   /!\ NO `display: contents`. The previous version needed it to place
   button and body independently; removing it puts each reveal back to being
   one element, which is what makes it a card. */
[data-src="m4-s28-reveals"] {
  display: grid; grid-template-columns: 1fr 1fr;
  gap: 14px; align-items: start;
  margin-top: var(--sp-3);
}
/* (!) NO PURPLE ON THESE CARDS. Anu, 27 October: *"Remove the purple
   highlight/line in the boxes."* They had the ground and the left rule the
   accordions took earlier the same day; on two side-by-side cards that read
   as emphasis rather than structure. A plain hairline is enough to make
   them cards. */
[data-src="m4-s28-reveals"] .reveal-item {
  border: 1px solid var(--border, #D8D6E0);
  border-radius: var(--radius-md);
  padding: 4px;
}
[data-src="m4-s28-reveals"] .reveal-btn {
  width: 100%; text-align: left;
}
[data-src="m4-s28-reveals"] .reveal-item__body { padding: 0 12px 10px; }
[data-src="m4-s28-reveals"] .reveal-item__body > *:first-child { margin-top: 0; }
body.night-mode [data-src="m4-s28-reveals"] .reveal-item {
  border-color: rgba(255, 255, 255, 0.18);
}
@media (max-width: 680px) {
  [data-src="m4-s28-reveals"] { grid-template-columns: minmax(0, 1fr); }
}

/* A tile that carries a numeral, for a set the lesson works through in
   order. Screen 40's four situations are screens 41 to 48 in sequence. */
.mo-tile--numbered { position: relative; }
.mo-tile--numbered .mo-tile__num {
  font-family: var(--font-feature);
  font-size: calc(26px * var(--fs-scale)); line-height: 1;
  color: var(--mim-purple-aa-dark); margin-bottom: 4px; display: block;
  letter-spacing: 0; text-transform: none;
}
body.night-mode .mo-tile--numbered .mo-tile__num { color: #cdbcff; }

/* ── screen 31: four factors, numbered ────────────────────────────────────
   Screens 31 and 35 both became tile grids on 27 October and read as the
   same device. This one is a count - the screen says "Four factors" - so it
   takes numerals in a single column. */
.mo-numbered { list-style: none; margin: var(--sp-5) 0; padding: 0; max-width: var(--measure); }
.mo-numbered__item {
  display: grid; grid-template-columns: auto 1fr; column-gap: 16px;
  padding: 14px 0; border-top: 1px solid var(--border, #D8D6E0);
}
.mo-numbered__item:last-child { border-bottom: 1px solid var(--border, #D8D6E0); }
.mo-numbered__num {
  font-family: var(--font-feature);
  font-size: calc(30px * var(--fs-scale)); line-height: 1;
  color: var(--mim-purple-aa-dark); min-width: 1.4em;
}
.mo-numbered__title {
  font-family: var(--font-primary); font-weight: var(--weight-semibold);
  font-size: calc(17px * var(--fs-scale)); margin: 0 0 4px; line-height: 1.4;
}
.mo-numbered__body {
  font-family: var(--font-primary); font-weight: var(--weight-light);
  font-size: calc(16px * var(--fs-scale)); line-height: 1.5;
  color: var(--fg-muted); margin: 0;
}
body.night-mode .mo-numbered__item,
body.night-mode .mo-numbered__item:last-child { border-color: rgba(255,255,255,0.18); }
body.night-mode .mo-numbered__num { color: #cdbcff; }
body.night-mode .mo-numbered__title { color: rgba(255,255,255,0.94); }
body.night-mode .mo-numbered__body { color: rgba(255,255,255,0.78); }

/* /!\ TOO MUCH AIR UNDER A PREDICT'S FEEDBACK. Anu, 27 October: *"For all
   predict screens, there is more space after the feedback and before any
   remaining text. Reduce this space."* Two gaps were stacking - the
   activity's own bottom margin and the top margin of the block held back
   until a choice is made - so the copy sat a long way below the verdict it
   belongs to. */
.rtl-act.rtl-predict { margin-bottom: var(--sp-4); }
.m4-after-choose > *:first-child { margin-top: var(--sp-3); }
.rtl-predict__after { margin-top: var(--sp-3); }

.rtl-rewrite__model .heading {
  font-size: calc(18px * var(--fs-scale));
  margin-top: var(--sp-5);
}

/* ── screen 4's accordions take the module's purple ───────────────────────
   Anu: *"Can we have some colour for the accordions? The whole page looks
   very bland."* Seven white boxes with a grey hairline and a grey plus, and
   the only colour on the page was the eyebrow and one statement rule.

   The two devices are the ones already in this module, not new ones: the
   pale purple ground the predict options took on 27 October, and the purple
   edge `.mo-tile` carries. A left rule rather than a top one, because these
   stack vertically and seven purple top edges would read as seven headings.

   /!\ THE TITLE STAYS BLACK. It is 16px and the AA-dark purple is 7.11:1 on
   white but only 6.3:1 on this ground - fine, but a seven-item list of
   purple headings is the 16 October over-marking again. The colour is the
   ground, the rule and the toggle; the words stay body colour. */
.accordion__item {
  background: #F6F3FE;
  border-color: #E0D8FB;
  border-left: 3px solid var(--mim-purple-aa);
}
.accordion__item + .accordion__item { margin-top: 10px; }
.accordion__toggle {
  color: var(--mim-purple-aa-dark);
  border-color: var(--mim-purple-aa);
  font-weight: var(--weight-semibold);
}
.accordion__item.open,
.accordion__item[open] { background: #EFE9FD; }

body.night-mode .accordion__item {
  background: rgba(183, 163, 252, 0.09);
  border-color: rgba(183, 163, 252, 0.28);
  border-left-color: var(--mim-purple);
}
body.night-mode .accordion__toggle {
  color: #cdbcff; border-color: rgba(183, 163, 252, 0.5);
}
body.night-mode .accordion__item.open,
body.night-mode .accordion__item[open] {
  background: rgba(183, 163, 252, 0.14);
}

/* m4-s4-sources */

/* ── a second reference block, for sources with no marker ─────────────────
   Lesson 4.1 carries three papers whose only citation was screen 4's
   multi-source marker, removed on 27 October. They are listed under the
   numbered references rather than inside them, so a number always has a
   marker pointing at it. */
.lesson-refs__also-title {
  font-family: var(--font-primary); font-weight: var(--weight-semibold);
  font-size: calc(13px * var(--fs-scale));
  letter-spacing: 0.08em; text-transform: uppercase;
  color: var(--fg-muted); margin: var(--sp-5) 0 10px;
}
.refs--also { list-style: none; margin: 0; padding: 0; }
.refs--also li { margin-bottom: 10px; }
body.night-mode .lesson-refs__also-title { color: rgba(255,255,255,0.72); }

/* m4-review-27oct-d */

/* ── a one-line statement still gets a full-length rule ───────────────────
   Measured 27 October: two-line statements draw a 53px purple rule, one-line
   statements 27px. Screens 58, 69 and 76 are the only one-liners in the
   module and read as half-drawn beside the other thirteen. */
.keypoint-wrap {
  min-height: calc(52.8px * var(--fs-scale));
  /* (!) AND THE TEXT SITS ON THE MIDDLE OF THE RULE. Anu, seeing the first
     version: "the statement should sit in the middle of the line. At the
     moment it is at the top." The floor above lengthened the rule but left a
     one-line statement hanging at its top with the gap underneath. Column
     flex rather than `align-items`, because `.keypoint-wrap` can also hold a
     `.caption` and those two have to stay stacked. */
  display: flex; flex-direction: column; justify-content: center;
}

/* ── room between the cumulative heading and the first thing marked ───────
   Anu, 27 October: "There needs to be more space between everything you have
   marked and the tick mark point." `.mo-chosen__list` had `margin: 0`, so the
   list sat directly under the h2's own margin and the tick crowded it. */
.mo-chosen__list { margin-top: var(--sp-4); }

/* ── the estimate's verdict gets the same rule as a statement ─────────────
   Anu, 27 October: "Screen 56 - just make the purple line longer for the
   feedback." `.rtl-estimate__verdict` carries the same 3px purple rule as a
   keypoint, but its text is two or three words ("Not quite"), so the rule
   was about a quarter the length of every statement in the module. Same
   floor, same centring, for the same reason. Reaches all three estimate
   screens, not only 56 - they had the stub rule too. */
.rtl-estimate__verdict {
  min-height: calc(52.8px * var(--fs-scale));
  display: flex; flex-direction: column; justify-content: center;
}

/* ── the activity question is not a lead-in ───────────────────────────────
   module.css line 76 groups .rtl-act__stem and .rtl-act__prompt with
   .section-lead under --t-lead, so on screens 12, 15, 30, 41, 43, 56, 58 and
   69 the question is exactly the same size as the paragraph above it. This
   steps them down - still above body copy, no longer level with the lead. */
:root { --t-ask: calc(18px * var(--fs-scale)); }
.rtl-act__prompt, .rtl-act__stem { font-size: var(--t-ask); }
