/* Marketing showcase — device frames, screen replicas and their clips.
 *
 * See web/app/showcase.js for why the "screenshots" are live DOM. Two mechanics
 * carry this whole sheet:
 *
 * 1. SCALING. Each replica sits in a container-query container (`.dev-screen`,
 *    `.mk-frame`) and every size inside is `calc(var(--u) * n)` where `--u` is
 *    `1cqw` — one percent of the frame's own width. So a replica is laid out once
 *    and then *is* the same screenshot at 240px or at 720px, with vector-crisp
 *    text, instead of being a raster that blurs or a transform that has to be
 *    recomputed in JS. `--u` is declared on `.mk`, never on the container itself:
 *    a container cannot query itself, so `1cqw` there would resolve against
 *    whatever container sits further up the page.
 * 2. CLIPS. Every animation is scoped to `.sc-stage.in` — the class the motion
 *    layer's reveal sweep adds when the section is first scrolled to — and is
 *    written as `@keyframes { from … }` so the *unanimated* state is the finished
 *    one. Nothing here can leave a mock half-drawn if the motion module never
 *    loads, if the sweep never reaches it, or under reduced motion.
 */

/* ============================================================ device frames */
.dev { position: relative; }

/* Hardware is dark in both themes — a phone does not have a light mode. */
.dev-phone-shell {
  position: relative; width: 100%; aspect-ratio: 9 / 19.5;
  background: linear-gradient(150deg, #2c313b 0%, #12151b 42%, #232830 100%);
  border-radius: calc(var(--pr, 34px));
  padding: 5px; box-shadow: 0 26px 60px rgba(8, 12, 22, .38), 0 2px 6px rgba(8, 12, 22, .3),
    inset 0 0 0 1px rgba(255, 255, 255, .14);
}
.dev-island {
  position: absolute; top: 9px; left: 50%; transform: translateX(-50%);
  width: 30%; height: 11px; background: #0a0c10; border-radius: 999px; z-index: 3;
}
/* No `height: 100%` on the shared rule: the laptop screen takes its height from
 * `aspect-ratio`, and a percentage height against an auto-height lid resolves to
 * zero — which collapsed the whole hero cluster to the phone's camera dot. The
 * phone screen fills its shell, which does have a definite height. */
.dev-screen { position: relative; overflow: hidden; background: var(--bg); container-type: inline-size; }
.dev-screen--phone { height: 100%; border-radius: calc(var(--pr, 34px) - 6px); padding-top: 6.5%; }

/* Laptop: 16/10 lid over a tapered base. */
.dev-laptop { display: grid; }
.dev-lid {
  background: linear-gradient(160deg, #333944 0%, #171b22 55%, #262c36 100%);
  border-radius: 12px 12px 4px 4px; padding: 9px 9px 12px;
  box-shadow: 0 26px 60px rgba(8, 12, 22, .34), inset 0 0 0 1px rgba(255, 255, 255, .12);
}
.dev-screen--laptop { aspect-ratio: 16 / 10; border-radius: 4px; display: flex; flex-direction: column; }
.dev-base {
  height: 11px; margin: 0 -3.5%; border-radius: 0 0 12px 12px;
  background: linear-gradient(180deg, #2a303a 0%, #171b22 70%, #0f1218 100%);
  box-shadow: 0 10px 18px rgba(8, 12, 22, .3);
  display: grid; place-items: center;
}
.dev-foot { width: 14%; height: 4px; border-radius: 0 0 6px 6px; background: rgba(255, 255, 255, .13); }

/* Browser chrome, so a laptop replica reads as "this website" and not as art. */
.mk-chrome {
  flex: none; display: flex; align-items: center; gap: 5px; padding: 0 8px; height: 6.5%; min-height: 14px;
  background: var(--surface-2); border-bottom: 1px solid var(--border);
}
.mk-dot { width: 5px; height: 5px; border-radius: 50%; background: var(--border); flex: none; }
.mk-url {
  margin-left: 6px; font-size: 9px; color: var(--muted); background: var(--surface);
  border-radius: 999px; padding: 1px 10px; letter-spacing: .01em; white-space: nowrap;
  overflow: hidden; text-overflow: ellipsis; max-width: 60%;
}
.mk-page { flex: 1; min-height: 0; overflow: hidden; background: var(--bg); container-type: inline-size; }

/* Clusters: how the frames sit together. */
.dev-cluster { position: relative; width: 100%; }
.dev-cluster--solo { max-width: 264px; margin-inline: auto; --pr: 36px; }
/* The read-aloud handset runs 10% larger than the other solo replicas: it is the one
   that wears headphones, so its own width has to cover the cups hanging off both sides
   (`--can-w` is a NEGATIVE inset, i.e. they sit outside the shell) before the section
   reads as a phone rather than as a phone squeezed between two blobs. Scoped with
   `:has(.dev-cans)` rather than a new modifier class because the cans are the condition
   — a phone that stops wearing them stops needing the extra width. `--pr` scales with
   it, or the corner radius reads tighter at the larger size. Everything inside is
   `--u: 1cqw` and every part of the headphones except the band thickness is a
   percentage of the shell, so both simply follow. */
.dev-cluster--solo:has(.dev-cans) { max-width: 290px; --pr: 40px; }
/* The one replica that is the whole visual half of its section, so it can afford
   to be read rather than recognised. */
.dev-cluster--solo.is-wide { max-width: 320px; --pr: 42px; }
.dev-cluster--wide .dev-laptop { width: 82%; }
.dev-cluster--wide .dev-phone { position: absolute; right: 0; bottom: -6%; width: 21%; z-index: 2; --pr: 20px; }
/* Bigger in "Et innblikk" than in the sections that merely nod at a phone: here
   it is the section's mobile view, and the reader is meant to read the card in it. */
.preview-cluster .dev-phone { width: 26%; --pr: 25px; }

/* Over-ear headphones for the read-aloud replica — two cups either side of the
   handset and a band arcing over it, so the section reads as *listening* before any
   of the copy beside it does. Drawn from the shell's own geometry (no asset), and
   behind the phone so the cups tuck under its edges. */
/* `--can-w` is a share of the phone's width; the band thickness has to be a length
   (percentages are invalid for border-width), and the phones that wear these run
   230–320px wide, so one value covers the range. The whole rig sits at z-index 0 and
   the shell paints over it, which is what makes the band read as passing *behind*
   the handset instead of across it. */
.dev-phone { --can-w: 15%; --can-b: 6.6px; }
.dev-cans {
  position: absolute; left: calc(var(--can-w) * -1); right: calc(var(--can-w) * -1);
  top: -4%; height: 44%; z-index: 0; pointer-events: none;
}
.dev-cans::before {
  content: ""; position: absolute; left: 7%; right: 7%; top: 0; height: 52%;
  border: var(--can-b) solid #2b313c; border-bottom: 0; border-radius: 999px 999px 0 0;
  box-shadow: inset 0 2px 0 rgba(255, 255, 255, .14);
}
.dev-can {
  position: absolute; top: 40%; width: var(--can-w); aspect-ratio: 3 / 4.2;
  background: linear-gradient(150deg, #3d4450, #262c36 62%, #1a1e26); border-radius: 42% / 34%;
  box-shadow: 0 8px 20px rgba(8, 12, 22, .34), inset 0 0 0 2px rgba(255, 255, 255, .1);
}
.dev-can::after {
  content: ""; position: absolute; inset: 24% 20%; border-radius: 42% / 34%;
  background: #12151b; opacity: .5;
}
.dev-can.l { left: 0; }
.dev-can.r { right: 0; }

/* The "your progress follows you" tag. It used to float in the gap between the two
 * devices, where it captioned nothing in particular and read as a sticker. It now sits
 * against the drill's PROGRESS BAR, which is the one thing on either screen that
 * actually shows a progress level — so the claim has something under it, and the bar
 * loading to that level (see `sc-load` with the other clips) is what the tag is for.
 *
 * Measured: `.mk-pbar` spans 2.1 → 79.9 across the cluster and sits at 21.4 → 22.3 down
 * it, and those numbers are IDENTICAL at 1500px and at 800px because the laptop frame
 * has a fixed aspect ratio. So one position serves every width above the 560px cut-off,
 * and the old ≤900px reposition is gone with the old anchor.
 *
 * It hangs a little off the laptop's left edge on purpose: half on the frame and half
 * on the page reads as an annotation *over* a screenshot, where the same pill sitting
 * tidily inside the toolbar would read as a chip the product itself has — which would
 * break this sheet's rule that a replica may only show things the product really has. */
.sc-sync {
  position: absolute; left: -4%; top: 23.6%; z-index: 3; display: inline-flex; align-items: center; gap: .4rem;
  padding: .35rem .8rem; border-radius: var(--r-pill); font-size: .78rem; font-weight: 700;
  background: var(--surface); border: 1px solid var(--border); box-shadow: var(--shadow-lg);
}
.sc-sync svg { width: 1rem; height: 1rem; color: var(--ok); }

/* A replica is dropped into containers this sheet does not own, and several of them
 * (`.lp-visual`, `.preview-visual`) are `place-items: center` grids. Chrome honours
 * `justify-items` in block layout too, so an inherited `center` shrink-wraps the
 * wrapper to its own min-content — which is the laptop lid's 18px of padding, i.e.
 * the replica vanishes and leaves the phone's camera dot behind. Defend here rather
 * than per host, so the next place one is dropped is not the next bug. */
.mk-wrap { display: block; width: 100%; justify-self: stretch; }
/* A replica shown without a device around it still needs to be a scaling container. */
.mk-frame {
  container-type: inline-size; background: var(--bg); border: 1px solid var(--border);
  border-radius: var(--r-lg); overflow: hidden; box-shadow: var(--shadow-lg);
}

/* ========================================================= replica: shared */
/* `--u` is "one unit of this screen's width", and the two device classes are not
 * the same product: a phone screen is ~390 CSS px wide, a browser window ~900, so
 * the same nominal type size occupies 2.3× more of a phone's width. One `--u` for
 * both rendered the laptop replica at phone type scale — a 22px paragraph in a
 * 620px frame, which reads as a zoomed-in mistake rather than as a screenshot.
 * `.mk` sets the phone value; the laptop rule below rescales the whole replica in
 * one number, since every size in this sheet is a multiple of `--u`. */
.mk {
  --u: 1cqw;
  height: 100%; display: flex; flex-direction: column; gap: calc(var(--u) * 2);
  padding: calc(var(--u) * 3); color: var(--text); background: var(--bg);
  font-size: calc(var(--u) * 3.6); line-height: 1.35; overflow: hidden;
}
.dev-screen--laptop .mk { --u: .46cqw; }
/* The frameless panel is a *zoom into* one card of the dashboard, not a whole
   window, so it carries fewer pixels of chrome and can run a larger unit. */
.mk-frame .mk { --u: .78cqw; }
/* …and in a half-width column there is no room for a two-up panel: stack it, on
   the frame's own width rather than the viewport's, so the same replica adapts
   wherever it is dropped. */
@container (max-width: 640px) {
  .mk-diag { flex-direction: column; }
  .mk-diag-list { flex: none; }
}
.mk-card {
  background: var(--surface); border: 1px solid var(--border); border-radius: calc(var(--u) * 3);
  padding: calc(var(--u) * 3); display: grid; gap: calc(var(--u) * 2);
}
.mk-p { color: var(--muted); margin: 0; font-size: calc(var(--u) * 3.3); }
.mk-p.sm { font-size: calc(var(--u) * 3); }

/* ========================================================= replica: the exam */
.mk-top {
  display: flex; justify-content: space-between; align-items: center;
  font-size: calc(var(--u) * 3.2); font-weight: 700; color: var(--muted);
}
.mk-timer { display: inline-flex; align-items: center; gap: calc(var(--u) * 1); }
.mk-top svg { width: calc(var(--u) * 3.6); height: calc(var(--u) * 3.6); }
.mk-pbar { height: calc(var(--u) * 1.4); border-radius: 999px; background: var(--surface-2); overflow: hidden; }
.mk-pbar i { display: block; height: 100%; border-radius: 999px; background: var(--grad-ui); }
/* The question illustrations are 400×180 and take their height from their width, so
 * in a wide laptop frame one drawing filled the whole screen and the question it
 * illustrates fell off the bottom. Cap it as a share of the screen's own height and
 * let the SVG letterbox inside (`meet` is the preserveAspectRatio default). */
.mk-exam .scene, .mk-scene { margin: 0; border-radius: calc(var(--u) * 2.5); flex: 0 1 auto; max-height: 34%; overflow: hidden; }
.mk-exam .scene .sc-svg, .mk-exam .scene .scene-art { height: 100%; }
.mk-exam .scene .scene-art .sc-svg { width: 100%; }
/* …but a question PHOTOGRAPH is not a drawing. There `.scene-art` IS the picture
   box and carries its own `aspect-ratio` (20/9), so the `height: 100%` above —
   written to letterbox an SVG — resolved its WIDTH through that ratio instead:
   163px of art inside a 97px scene, with the overflow clipped, i.e. the right
   third of every photo cut off. Take the scene's width and hand the cropping
   back to object-fit, which crops from the centre rather than from the edge. */
.mk-exam .scene--photo .scene-art { width: 100%; height: auto; }
/* Every drawn scene in these replicas is rendered with `caption: false`; a
   question's photograph brings its own figcaption, which at .72rem inside a
   140px handset is bigger than the question it captions. Same decision, applied
   to the one scene type that could not opt out at the call site. */
.mk-exam .scene figcaption { display: none; }
.mk-q { font-weight: 700; font-size: calc(var(--u) * 3.7); margin: 0; }
.mk-opt {
  display: flex; align-items: center; gap: calc(var(--u) * 2); padding: calc(var(--u) * 2.2) calc(var(--u) * 2.4);
  border: 1px solid var(--border); border-radius: calc(var(--u) * 2.2); background: var(--surface);
  font-size: calc(var(--u) * 3.2);
}
.mk-key {
  flex: none; width: calc(var(--u) * 5); height: calc(var(--u) * 5); border-radius: 50%;
  display: grid; place-items: center; background: var(--surface-2); color: var(--muted);
  font-weight: 800; font-size: calc(var(--u) * 2.8);
}
.mk-opt.is-correct {
  border-color: var(--ok); background: var(--ok-bg); color: var(--ok-ink); font-weight: 700;
}
/* `color: var(--bg)`, never `#fff`. `--ok` is a token whose ROLE inverts between
   themes (see docs/frontend.md): light's #1a7f37 is a dark green, dark's #4ade80 is
   a light one meant as ink. White on it measures 1.74:1 in dark — the exact trap
   `--bad-solid` exists to avoid on the red side. The page background is the one
   colour guaranteed to contrast with a token that tracks the theme. */
.mk-opt.is-correct .mk-key { background: var(--ok); color: var(--bg); }
.mk-tick { margin-left: auto; color: var(--ok-ink); display: grid; }
.mk-tick svg { width: calc(var(--u) * 4); height: calc(var(--u) * 4); stroke-width: 3; }
.mk-explain {
  border-left: calc(var(--u) * .8) solid var(--blue); padding-left: calc(var(--u) * 2.2);
  color: var(--muted); font-size: calc(var(--u) * 3);
}
.mk-explain b { color: var(--text); }

/* ================================================ replica: read-aloud player */
/* The player is pinned to the bottom because that is where the real one sits, so
   this replica has more vertical room to fill than the exam one — give the drawing
   the difference rather than leaving a band of empty screen above the controls. */
.mk-audio .mk-scene { max-height: 46%; }
.mk-audio .mk-line { border-radius: calc(var(--u) * 1.4); }
.mk-tts {
  margin-top: auto; background: var(--surface); border: 1px solid var(--border);
  border-radius: calc(var(--u) * 3); padding: calc(var(--u) * 2.6); display: grid; gap: calc(var(--u) * 2);
  box-shadow: 0 calc(var(--u) * 1) calc(var(--u) * 4) rgba(0, 0, 0, .08);
}
.mk-tts-head { display: flex; align-items: center; gap: calc(var(--u) * 1.6); font-size: calc(var(--u) * 3); }
.mk-tts-head svg { width: calc(var(--u) * 3.8); height: calc(var(--u) * 3.8); color: var(--brand); }
.mk-wave { margin-left: auto; display: flex; align-items: flex-end; gap: calc(var(--u) * .7); height: calc(var(--u) * 4); }
.mk-wave i {
  width: calc(var(--u) * .9); border-radius: 999px; background: var(--brand); opacity: .55;
  height: calc(var(--u) * 1.6 + var(--u) * 2 * var(--h, .5));
}
.mk-wave i:nth-child(odd) { --h: .9; }
.mk-wave i:nth-child(3n) { --h: 1; }
.mk-tts-track { height: calc(var(--u) * 1.4); border-radius: 999px; background: var(--surface-2); overflow: hidden; }
.mk-tts-track i { display: block; height: 100%; width: 58%; border-radius: 999px; background: var(--brand); }
.mk-tts-row { display: flex; align-items: center; gap: calc(var(--u) * 1.6); }
.mk-tts-btn {
  display: inline-grid; place-items: center; position: relative;
  width: calc(var(--u) * 7); height: calc(var(--u) * 7); border-radius: 50%;
  background: var(--surface-2); color: var(--text); flex: none;
}
.mk-tts-btn svg { width: calc(var(--u) * 3.4); height: calc(var(--u) * 3.4); }
.mk-tts-btn b {
  position: absolute; inset: 0; display: grid; place-items: center;
  font-size: calc(var(--u) * 1.9); font-weight: 800;
}
.mk-tts-btn.sm { width: calc(var(--u) * 5); height: calc(var(--u) * 5); background: none; }
.mk-tts-main { width: calc(var(--u) * 9); height: calc(var(--u) * 9); background: var(--grad-ui); color: #fff; }
.mk-tts-main svg { width: calc(var(--u) * 4.2); height: calc(var(--u) * 4.2); }
.mk-tts-rates { margin-left: auto; display: flex; gap: calc(var(--u) * .8); }
.mk-tts-rates i {
  font-style: normal; font-size: calc(var(--u) * 2.5); font-weight: 700; color: var(--muted);
  padding: calc(var(--u) * .6) calc(var(--u) * 1.4); border-radius: 999px; background: var(--surface-2);
}
.mk-tts-rates i.on { background: var(--grad-ui); color: #fff; }
.mk-tts-vol { flex: 1; height: calc(var(--u) * 1.2); border-radius: 999px; background: var(--surface-2); overflow: hidden; }
.mk-tts-vol i { display: block; height: 100%; width: 60%; background: var(--muted); border-radius: 999px; }
.mk-tts-time { font-size: calc(var(--u) * 2.5); color: var(--muted); font-variant-numeric: tabular-nums; }

/* ==================================================== replica: sign drill */
/* The runner's own two header rows: the category/count toolbar, then progress +
   score + "open the catalogue". They are most of what makes the replica read as a
   working screen rather than a question on a card. */
.mk-toolbar { display: flex; align-items: center; gap: calc(var(--u) * 1.6); }
.mk-chip {
  display: inline-flex; align-items: center; padding: calc(var(--u) * 1) calc(var(--u) * 2);
  border-radius: var(--r-pill); font-size: calc(var(--u) * 2.7); font-weight: 700;
  background: var(--surface); border: 1px solid var(--border); color: var(--muted);
}
.mk-chip.is-on { color: var(--text); }
.mk-chip.is-score {
  text-transform: uppercase; letter-spacing: .04em; font-size: calc(var(--u) * 2.4);
  color: var(--ok-ink); background: var(--ok-bg); border-color: transparent;
}
.mk-count { margin-left: auto; display: inline-flex; align-items: center; gap: calc(var(--u) * 1.2); color: var(--muted); font-size: calc(var(--u) * 2.7); font-weight: 700; }
.mk-select { padding: calc(var(--u) * .9) calc(var(--u) * 2.4) calc(var(--u) * .9) calc(var(--u) * 1.6); border: 1px solid var(--border); border-radius: calc(var(--u) * 1.6); background: var(--surface); color: var(--text); }
.mk-progress { font-weight: 700; color: var(--text); font-size: calc(var(--u) * 3.4); }
.mk-spacer { flex: 1; }
.mk-drill .mk-top { gap: calc(var(--u) * 1.6); }
.mk-drill .mk-top .mk-btn { padding: calc(var(--u) * 1.4) calc(var(--u) * 2.4); font-size: calc(var(--u) * 2.8); background: var(--surface); }
/* The sign gets a modest, fixed band rather than the leftover height of the screen.
   The card's job here is to show the *whole* answered state — sign, question, the
   wrong pick, the right answer and the explanation — and a sign scaled to fill a
   16/10 laptop pushed the explanation off the bottom, which is the one part a buyer
   most needs to see. */
/* The two header rows cost ~25px, which pushed the Previous/Next row past the
   bottom bezel and left a red sliver of the Next button clipped at the edge. The
   sign gives the height back — it is the one element here with room to spare. */
.mk-drill .mk-card { gap: calc(var(--u) * 1.7); flex: 1; min-height: 0; }
.mk-drill { gap: calc(var(--u) * 1.6); }
/* No background band and a modest sign: `.signstage` in the real app is a plain
   centred well with a 168px sign in a ~700px card — about a quarter of the width.
   The replica keeps that ratio, so the sign is recognisable without taking the room
   the answered state needs. */
.mk-signstage { display: grid; place-items: center; padding: 0 0 calc(var(--u) * 1); }
/* Sized by HEIGHT, not width: 536.1 is a portrait plate (283×379) and 202 is a wide
   triangle, so a fixed width makes one of them tower over the card. */
.mk-signstage .signimg { height: calc(var(--u) * 16); width: auto; max-width: 30%; display: block; }
.mk-q.center { text-align: center; }
/* One column, like the real runner: the options are the answer sheet, and a two-up
   grid made the replica a different screen from the one it claims to be. */
.mk-drill-opts { display: grid; gap: calc(var(--u) * 1.4); }
/* The runner's own footer row, so the screen ends where the product's does instead
   of trailing off into empty page. */
.mk-navrow { display: flex; gap: calc(var(--u) * 1.6); margin-top: calc(var(--u) * 1); }
.mk-btn {
  padding: calc(var(--u) * 1.8) calc(var(--u) * 3); border-radius: calc(var(--u) * 2);
  border: 1px solid var(--border); font-size: calc(var(--u) * 3); font-weight: 700; color: var(--text);
}
.mk-btn.is-primary { background: var(--grad-ui); color: #fff; border-color: transparent; margin-left: auto; }
/* The wrong pick, in the product's own answered-state colours (`.opt.wrong`). */
.mk-opt.is-wrong { border-color: var(--bad); background: var(--bad-bg); color: var(--bad); font-weight: 700; }
.mk-opt.is-wrong .mk-key { background: var(--bad); color: var(--bg); }
.mk-tick.is-bad { color: var(--bad); }
/* The drill's explanation: verdict word, sign name, meaning, then code + group —
   the same four parts, in the same order, as `signExplain()` in main.js. */
.mk-explain--drill {
  border-left: 0; background: var(--surface-2); border: 1px solid var(--border);
  border-radius: calc(var(--u) * 2.2); padding: calc(var(--u) * 2.2);
}
.mk-verdict-bad { color: var(--bad); margin-right: calc(var(--u) * .8); }
.mk-src { display: block; margin-top: calc(var(--u) * 1); font-size: calc(var(--u) * 2.5); color: var(--muted); }
.mk-code {
  display: inline-block; font-weight: 700; color: var(--brand);
  background: var(--surface); border: 1px solid var(--border);
  border-radius: calc(var(--u) * 1); padding: 0 calc(var(--u) * .8); margin-right: calc(var(--u) * .6);
}

/* ============================ replica: a course chapter, as a phone renders it */
/* Matched against the real `#/course/introduksjon` on a handset: the back link, the
   round chapter medallion beside the title block (not above it), the measured
   reading-time pill, then lesson cards with a filled green tick on the left and the
   estimate on the right. The medallion is the product's own asset
   (photos/chapters_logo/ch1.png) — a stroke glyph in its place was the one part of
   this replica that did not look like the screen it claims to be. */
/* A larger unit than the other phone replicas. This one is a *reading* screen, and
   on a real 390px handset its title runs ~8.7% of the screen width against the 6%
   the shared unit gave it — so at the shared scale it read as a shrunken page rather
   than as the page. Matching the ratio also fills the height the three lessons left. */
.mk-cintro { --u: 1.32cqw; gap: calc(var(--u) * 2.6); }
.mk-back { display: inline-flex; align-items: center; gap: calc(var(--u) * 1); font-weight: 700; color: var(--muted); font-size: calc(var(--u) * 3.2); }
.mk-back svg { width: calc(var(--u) * 3.6); height: calc(var(--u) * 3.6); transform: rotate(180deg); }
.mk-cintro-head { display: grid; grid-template-columns: auto 1fr; gap: calc(var(--u) * 3); align-items: start; }
.mk-cintro-ico { width: calc(var(--u) * 15); height: calc(var(--u) * 15); border-radius: 50%; object-fit: cover; flex: none; }
.mk-cintro-body { display: grid; gap: calc(var(--u) * 1.2); justify-items: start; min-width: 0; }
.mk-cintro-num {
  font-size: calc(var(--u) * 2.6); font-weight: 800; letter-spacing: .06em;
  /* The product's own eyebrow formula (`.course-ch-num`), not bare `--blue`: the
     pure token measures 4.20:1 on the surface and this is small text. Mixing it
     toward the text colour is what makes the real one pass, and copying the colour
     without copying the mix reproduced the look and lost the contrast. */
  text-transform: uppercase; color: color-mix(in srgb, var(--blue) 62%, var(--text));
}
.mk-cintro .mk-title { font-size: calc(var(--u) * 6); line-height: 1.1; }
.mk-cintro .mk-p { font-size: calc(var(--u) * 3.2); line-height: 1.45; }
.mk-cintro .mk-meta { gap: calc(var(--u) * 2.6); font-weight: 700; }
.mk-cintro .mk-meta span { display: inline-flex; align-items: center; gap: calc(var(--u) * .8); }
.mk-readtime {
  font-size: calc(var(--u) * 2.9); font-weight: 700; padding: calc(var(--u) * .8) calc(var(--u) * 1.8);
  border-radius: var(--r-pill); color: color-mix(in srgb, var(--blue) 60%, var(--text));
  background: color-mix(in srgb, var(--blue) 10%, transparent);
}
.mk-lrows { list-style: none; margin: 0; padding: 0; display: grid; gap: calc(var(--u) * 2); }
.mk-lrow {
  display: grid; grid-template-columns: auto 1fr auto; align-items: center; gap: calc(var(--u) * 2.4);
  background: var(--surface); border: 1px solid var(--border);
  border-radius: calc(var(--u) * 2.6); padding: calc(var(--u) * 2.6);
}
.mk-lrow-num {
  flex: none; display: grid; place-items: center;
  width: calc(var(--u) * 7.5); height: calc(var(--u) * 7.5); border-radius: 50%;
  background: var(--surface-2); color: var(--muted); font-weight: 800; font-size: calc(var(--u) * 3.2);
}
.mk-lrow-num svg { width: calc(var(--u) * 4); height: calc(var(--u) * 4); stroke-width: 3; }
.mk-lrow.is-done .mk-lrow-num { background: var(--ok); color: var(--bg); }
.mk-lrow-body { display: grid; gap: calc(var(--u) * .6); min-width: 0; }
.mk-lrow-body b { font-size: calc(var(--u) * 3.6); line-height: 1.25; }
.mk-lrow-body span { color: var(--muted); font-size: calc(var(--u) * 3); line-height: 1.4; }
.mk-lrow-meta {
  flex: none; display: inline-flex; align-items: center; gap: calc(var(--u) * .7);
  color: var(--muted); font-size: calc(var(--u) * 2.8); font-weight: 700; white-space: nowrap;
}
.mk-lrow-meta svg { width: calc(var(--u) * 3); height: calc(var(--u) * 3); }

/* =================================================== replica: course reader */
.mk-course { flex-direction: row; gap: calc(var(--u) * 3); padding: calc(var(--u) * 3); }
.mk-side {
  flex: 0 0 30%; display: grid; gap: calc(var(--u) * 2); align-content: start;
  background: var(--surface); border: 1px solid var(--border); border-radius: calc(var(--u) * 2.4);
  padding: calc(var(--u) * 2.4);
}
.mk-side-head { display: grid; justify-items: center; gap: calc(var(--u) * .6); }
.mk-side-head span { font-size: calc(var(--u) * 2.4); color: var(--muted); font-weight: 700; }
.mk-side-ch { font-weight: 800; font-size: calc(var(--u) * 2.9); }
.mk-lessons { list-style: none; margin: 0; padding: 0; display: grid; gap: calc(var(--u) * 1.4); }
.mk-lesson {
  display: flex; align-items: center; gap: calc(var(--u) * 1.4); font-size: calc(var(--u) * 2.5);
  color: var(--muted); padding: calc(var(--u) * 1) calc(var(--u) * 1.2); border-radius: calc(var(--u) * 1.4);
}
.mk-lesson-ic { display: grid; flex: none; color: var(--border); }
.mk-lesson-ic svg { width: calc(var(--u) * 3); height: calc(var(--u) * 3); }
.mk-lesson.is-done { color: var(--text); }
.mk-lesson.is-done .mk-lesson-ic { color: var(--ok); }
.mk-lesson.is-now { background: var(--surface-2); color: var(--text); font-weight: 700; }
.mk-lesson.is-now .mk-lesson-ic { color: var(--brand); }
.mk-main { flex: 1; min-width: 0; display: grid; gap: calc(var(--u) * 1.6); align-content: start; }
.mk-crumb { font-size: calc(var(--u) * 2.4); color: var(--muted); }
.mk-title { font-weight: 800; font-size: calc(var(--u) * 4.4); letter-spacing: -.01em; }
.mk-meta { display: flex; align-items: center; gap: calc(var(--u) * .8); font-size: calc(var(--u) * 2.4); color: var(--muted); }
.mk-meta svg { width: calc(var(--u) * 2.8); height: calc(var(--u) * 2.8); }
/* The real course diagrams are drawn tall enough to be read at article width; in a
 * replica that height eats the callout underneath, so cap it and let the SVG
 * letterbox (preserveAspectRatio defaults to `meet`, so nothing is cropped). */
.mk-course .cs { margin: 0; border-radius: calc(var(--u) * 2); overflow: hidden; }
.mk-course .cs-svg { max-height: calc(var(--u) * 40); width: 100%; }
.mk-callout {
  display: flex; gap: calc(var(--u) * 1.6); padding: calc(var(--u) * 2);
  border-radius: calc(var(--u) * 2); background: var(--ok-bg); color: var(--text);
  font-size: calc(var(--u) * 2.7); border: 1px solid color-mix(in srgb, var(--ok) 25%, transparent);
}
.mk-callout svg { width: calc(var(--u) * 3.4); height: calc(var(--u) * 3.4); color: var(--ok-ink); flex: none; }
/* On a phone the rail would be four unreadable characters wide — drop it. */
.mk-course.is-compact { flex-direction: column; }
.mk-course.is-compact .mk-side { flex: none; }
.mk-course.is-compact .mk-lessons { display: none; }
.dev-screen--phone .mk-course.is-compact .mk-side { grid-auto-flow: column; align-items: center; justify-content: start; gap: calc(var(--u) * 3); }

/* ================================================ replica: progress dashboard */
.mk-prog { gap: calc(var(--u) * 2.4); }
.mk-prog-hero {
  display: flex; gap: calc(var(--u) * 3); align-items: center;
  background: var(--surface); border: 1px solid var(--border);
  border-radius: calc(var(--u) * 2.4); padding: calc(var(--u) * 2.4);
}
.mk-prog-ring { display: grid; justify-items: center; gap: calc(var(--u) * .4); flex: none; }
.mk-prog-ring small { font-size: calc(var(--u) * 2.2); color: var(--muted); font-weight: 700; }
/* Every ring is sized here, not by an attribute — see mkRing() in showcase.js. */
.mk-ring { width: calc(var(--u) * 22); height: calc(var(--u) * 22); flex: none; }
.mk-side-head .mk-ring { width: calc(var(--u) * 13); height: calc(var(--u) * 13); }
.mk-verdict { font-weight: 800; font-size: calc(var(--u) * 3.6); }
.mk-tiles { display: grid; grid-template-columns: repeat(4, 1fr); gap: calc(var(--u) * 1.4); margin-top: calc(var(--u) * 1.6); }
.mk-tile { background: var(--surface-2); border-radius: calc(var(--u) * 1.6); padding: calc(var(--u) * 1.4); text-align: center; }
.mk-tile-num { font-weight: 800; font-size: calc(var(--u) * 3.6); line-height: 1.1; }
.mk-tile-lbl { font-size: calc(var(--u) * 2.1); color: var(--muted); margin-top: calc(var(--u) * .3); }
.mk-chart {
  background: var(--surface); border: 1px solid var(--border);
  border-radius: calc(var(--u) * 2.4); padding: calc(var(--u) * 2.4); flex: 1; min-height: 0;
  display: flex; flex-direction: column; gap: calc(var(--u) * 1.2);
}
.mk-chart-head { font-weight: 700; font-size: calc(var(--u) * 2.8); }
.mk-chart-svg { width: 100%; flex: 1; min-height: 0; }
.mk-legend { display: flex; gap: calc(var(--u) * 2.4); font-size: calc(var(--u) * 2.2); color: var(--muted); }
.mk-legend span { display: inline-flex; align-items: center; gap: calc(var(--u) * .8); }
.mk-legend .lg-key { width: calc(var(--u) * 1.6); height: calc(var(--u) * 1.6); }

/* ================================================ replica: topic diagnosis */
.mk-diag { flex-direction: row; align-items: stretch; gap: calc(var(--u) * 2.6); padding: calc(var(--u) * 3); }
.mk-diag-list {
  flex: 1.35; min-width: 0; background: var(--surface); border: 1px solid var(--border);
  border-radius: calc(var(--u) * 2.4); padding: calc(var(--u) * 2.4); display: grid;
  gap: calc(var(--u) * 1.6); align-content: start; position: relative; overflow: hidden;
}
.mk-diag-head { font-weight: 700; font-size: calc(var(--u) * 2.8); position: relative; }
.mk-row { display: flex; align-items: center; gap: calc(var(--u) * 1.4); font-size: calc(var(--u) * 2.4); }
.mk-row-name { flex: 0 0 34%; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; font-weight: 600; }
.mk-bar { flex: 1; height: calc(var(--u) * 1.4); border-radius: 999px; background: var(--surface-2); overflow: hidden; }
.mk-bar i { display: block; height: 100%; width: var(--w); border-radius: 999px; background: var(--blue); }
.mk-bar i.strong { background: var(--ok); }
.mk-bar i.weak { background: var(--bad); }
.mk-diag .mband { font-size: calc(var(--u) * 2); padding: calc(var(--u) * .4) calc(var(--u) * 1.2); flex: none; }
.mk-row-val { flex: none; width: calc(var(--u) * 6); text-align: right; font-variant-numeric: tabular-nums; color: var(--muted); }
/* The sweep that reads as "we are measuring this". */
.mk-scan {
  position: absolute; inset: calc(var(--u) * -2.4) calc(var(--u) * -2.4) calc(var(--u) * -60);
  background: linear-gradient(180deg, transparent, color-mix(in srgb, var(--brand) 12%, transparent) 60%, transparent);
  opacity: 0; pointer-events: none;
}
.mk-rec {
  flex: 1; display: grid; align-content: start; gap: calc(var(--u) * 1.2);
  background: var(--surface); border: 1px solid color-mix(in srgb, var(--brand) 45%, var(--border));
  border-radius: calc(var(--u) * 2.4); padding: calc(var(--u) * 2.4);
  box-shadow: 0 calc(var(--u) * 1.2) calc(var(--u) * 4) color-mix(in srgb, var(--brand) 14%, transparent);
}
.mk-rec-head { display: flex; align-items: center; gap: calc(var(--u) * 1.2); font-size: calc(var(--u) * 2.5); color: var(--brand); }
.mk-rec-head svg { width: calc(var(--u) * 3.2); height: calc(var(--u) * 3.2); }
.mk-rec-topic { font-weight: 800; font-size: calc(var(--u) * 4); }
.mk-rec-cta {
  display: inline-flex; align-items: center; gap: calc(var(--u) * 1); justify-self: start;
  background: var(--grad-ui); color: #fff; font-weight: 700; font-size: calc(var(--u) * 2.5);
  padding: calc(var(--u) * 1.4) calc(var(--u) * 2.4); border-radius: 999px; margin-top: calc(var(--u) * .8);
}
.mk-rec-cta svg { width: calc(var(--u) * 3); height: calc(var(--u) * 3); }

/* ========================================================= section layouts */
.sc-split {
  display: grid; grid-template-columns: 1fr 1fr; gap: clamp(1.75rem, 4.5vw, 3.5rem); align-items: center;
}
.sc-split.is-flip .sc-copy { order: 2; }
.sc-copy .eyebrow { color: var(--brand); font-weight: 700; font-size: .82rem; letter-spacing: .08em; text-transform: uppercase; }
.sc-copy h2 { font-size: var(--fs-h2); margin: .45rem 0 .6rem; }
.sc-copy > p.muted { color: var(--muted); font-size: 1.05rem; margin-bottom: 1.1rem; }
.sc-copy .price-list { margin-bottom: 1.4rem; }
.sc-media { min-width: 0; }

.sc-every-stage, .sc-wide-stage { max-width: 940px; margin-inline: auto; }
.sc-every-stage { padding-bottom: 4%; }
.sc-every-notes, .sc-wide-notes {
  display: flex; flex-wrap: wrap; justify-content: center; gap: .8rem 2rem; margin-top: 1.8rem;
}
.sc-every-notes span, .sc-wide-notes span {
  display: inline-flex; align-items: center; gap: .5rem; color: var(--muted); font-weight: 650; font-size: .92rem;
}
.sc-every-notes svg, .sc-wide-notes svg { width: 1.1rem; height: 1.1rem; color: var(--ok); flex: none; }

/* --------------------------------------------------------- Pass Protection */
.sc-pp {
  position: relative; overflow: hidden; border-radius: var(--r-2xl);
  background: linear-gradient(150deg, color-mix(in srgb, var(--ok) 12%, var(--surface)) 0%, var(--surface) 55%,
    color-mix(in srgb, var(--brand) 9%, var(--surface)) 100%);
  border: 1px solid color-mix(in srgb, var(--ok) 30%, var(--border));
  padding: clamp(1.6rem, 3.6vw, 2.4rem) clamp(1.2rem, 4vw, 2.4rem);
}
.sc-pp::after {
  content: ""; position: absolute; inset: auto -10% -60% 55%; height: 120%;
  background: radial-gradient(closest-side, color-mix(in srgb, var(--ok) 16%, transparent), transparent);
  pointer-events: none;
}
.sc-pp-inner { position: relative; max-width: 820px; margin-inline: auto; text-align: center; }
.sc-pp-badge {
  display: inline-flex; align-items: center; gap: .5rem; padding: .3rem .9rem .3rem .35rem;
  border-radius: var(--r-pill); background: var(--surface); border: 1px solid color-mix(in srgb, var(--ok) 40%, var(--border));
  font-size: .8rem; font-weight: 800; letter-spacing: .02em; text-transform: uppercase;
}
.sc-pp-shield { display: grid; place-items: center; width: 1.6rem; height: 1.6rem; border-radius: 50%; background: var(--ok); color: var(--bg); }
.sc-pp-shield svg { width: 1rem; height: 1rem; }
.sc-pp-title { font-size: clamp(1.5rem, 3.4vw, 2.1rem); margin: .8rem 0 .5rem; letter-spacing: -.02em; }
.sc-pp-sub { color: var(--muted); font-size: 1rem; max-width: 52ch; margin: 0 auto; }
/* Four pills on one line, not four cards in a grid: the requirements are one set and
   are read as one, and the long version of this band was mostly whitespace. */
.sc-pp-reqs {
  list-style: none; padding: 0; margin: 1.2rem 0 1.1rem; display: flex;
  flex-wrap: wrap; justify-content: center; gap: .5rem;
}
.sc-pp-reqs li {
  display: inline-flex; align-items: center; gap: .45rem; padding: .4rem .8rem .4rem .4rem;
  font-size: .84rem; font-weight: 650; white-space: nowrap;
  background: var(--surface); border: 1px solid var(--border); border-radius: var(--r-pill);
}
.sc-pp-ic { display: grid; place-items: center; flex: none; width: 1.5rem; height: 1.5rem; border-radius: 50%; background: var(--ok-bg); color: var(--ok-ink); }
.sc-pp-ic svg { width: .95rem; height: .95rem; }
.sc-pp-cta { display: inline-flex; align-items: center; gap: .45rem; }
.sc-pp.is-compact { padding: clamp(1.4rem, 4vw, 2.2rem) clamp(1.2rem, 4vw, 2rem); }
.sc-pp.is-compact .sc-pp-title { font-size: clamp(1.3rem, 3.2vw, 1.8rem); }

/* --- slim: the landing-page size ---------------------------------------------
   Same content, a fraction of the room. On the front page this band sits ABOVE the
   price, so it was answering an objection the reader has not had yet, at the scale
   of a hero. The badge and the title share a row, the display type drops to body
   size, the pills go small, and the CTA stops being a button — it is a footnote
   link here, not the action the page wants.
   The requirements themselves stay: shrinking them is fine, hiding them would make
   the guarantee read as unconditional. */
.sc-pp.is-slim { padding: .9rem 1.1rem; border-radius: var(--r-xl); }
/* The big decorative pool is sized for a tall band; at this height it just muddies
   the corner. */
.sc-pp.is-slim::after { display: none; }
.sc-pp.is-slim .sc-pp-inner {
  display: flex; flex-wrap: wrap; align-items: center; justify-content: center;
  gap: .4rem .7rem; max-width: none; text-align: left;
}
.sc-pp.is-slim .sc-pp-badge { padding: .2rem .6rem .2rem .25rem; font-size: .68rem; }
.sc-pp.is-slim .sc-pp-shield { width: 1.25rem; height: 1.25rem; }
.sc-pp.is-slim .sc-pp-shield svg { width: .8rem; height: .8rem; }
.sc-pp.is-slim .sc-pp-title { font-size: 1rem; margin: 0; letter-spacing: -.01em; }
.sc-pp.is-slim .sc-pp-sub { font-size: .84rem; max-width: none; margin: 0; }
/* Own row, full width, so the four conditions still read as one set rather than
   wrapping into the title line. */
.sc-pp.is-slim .sc-pp-reqs { width: 100%; margin: .15rem 0 0; gap: .35rem; justify-content: center; }
.sc-pp.is-slim .sc-pp-reqs li { font-size: .74rem; padding: .2rem .55rem .2rem .2rem; gap: .3rem; }
.sc-pp.is-slim .sc-pp-ic { width: 1.15rem; height: 1.15rem; }
.sc-pp.is-slim .sc-pp-ic svg { width: .72rem; height: .72rem; }
/* `padding: 0` alone left this at 22px — under the 24px WCAG 2.5.8 target floor,
   which the audit caught. It is a text link now, so the height has to come from
   padding rather than from the button box it no longer has. */
.sc-pp.is-slim .sc-pp-cta {
  padding: .25rem 0; border: none; background: none; box-shadow: none; min-height: 28px;
  font-size: .82rem; font-weight: 650; color: var(--muted); text-decoration: underline;
  text-underline-offset: 3px;
}
.sc-pp.is-slim .sc-pp-cta:hover { color: var(--text); box-shadow: none; }
.sc-pp.is-slim .sc-pp-cta svg { width: .9rem; height: .9rem; }
@media (max-width: 560px) {
  /* Left-aligned and stacked on a phone: centred wrapped pills at this size turn
     into a ragged block that is harder to scan than a plain list. */
  .sc-pp.is-slim .sc-pp-inner { justify-content: flex-start; }
  .sc-pp.is-slim .sc-pp-reqs { justify-content: flex-start; }
}

/* =============================================================== the clips */
/* Each is `from`-only: the resting state IS the finished state (see file header). */

/* Exam — the right answer lights up and its explanation slides in. */
@keyframes sc-pick { from { border-color: var(--border); background: var(--surface); color: inherit; } }
@keyframes sc-tick { from { transform: scale(.2); opacity: 0; } }
@keyframes sc-slide { from { opacity: 0; transform: translateY(6px); } }
/* Scoped to `.mk-exam`: unscoped, these also drove the sign drill's answered state,
   whose explanation then sat at opacity 0 for 1.55s — long enough that a reader
   scrolling at a normal pace never saw the one part of that replica worth seeing. */
.sc-stage.in .mk-exam .mk-opt.is-correct { animation: sc-pick .45s ease-out 1.1s backwards; }
.sc-stage.in .mk-exam .mk-tick { animation: sc-tick .4s cubic-bezier(.34, 1.56, .64, 1) 1.35s backwards; }
.sc-stage.in .mk-exam .mk-explain { animation: sc-slide .45s ease-out 1.55s backwards; }

/* The drill runs the same beats twice as fast, and in the order the learner lived
   them: the wrong pick reddens, the right answer greens, the explanation arrives.
   Its answered state IS the screenshot, so it cannot be a second and a half late. */
@keyframes sc-pick-bad { from { border-color: var(--border); background: var(--surface); color: inherit; } }
.sc-stage.in .mk-drill .mk-opt.is-wrong { animation: sc-pick-bad .4s ease-out .3s backwards; }
.sc-stage.in .mk-drill .mk-opt.is-correct { animation: sc-pick .4s ease-out .5s backwards; }
.sc-stage.in .mk-drill .mk-tick { animation: sc-tick .35s cubic-bezier(.34, 1.56, .64, 1) .65s backwards; }
.sc-stage.in .mk-drill .mk-explain { animation: sc-slide .4s ease-out .8s backwards; }

/* The looping variant, for the front page's "a peek inside": the same replica, but
 * it answers the question again every 7 s. It is the only replica that is the sole
 * visual in its section, so a single play would leave it a still photograph. */
@keyframes sc-loop-bar { 0% { width: 18%; } 45%, 100% { width: 62%; } }
@keyframes sc-loop-pick { 0%, 30% { border-color: var(--border); background: var(--surface); color: inherit; } 45%, 100% { border-color: var(--ok); background: var(--ok-bg); color: var(--ok-ink); } }
@keyframes sc-loop-key { 0%, 30% { background: var(--surface-2); color: var(--muted); } 45%, 100% { background: var(--ok); color: var(--bg); } }
@keyframes sc-loop-tick { 0%, 35% { opacity: 0; transform: scale(.4); } 48%, 100% { opacity: 1; transform: scale(1); } }
@keyframes sc-loop-timer { 50% { opacity: .55; } }
.sc-stage.in .mk-exam.is-loop .mk-pbar i { animation: sc-loop-bar 7s cubic-bezier(.4, 0, .2, 1) infinite; }
.sc-stage.in .mk-exam.is-loop .mk-opt.is-correct { animation: sc-loop-pick 7s ease-in-out infinite; }
.sc-stage.in .mk-exam.is-loop .mk-opt.is-correct .mk-key { animation: sc-loop-key 7s ease-in-out infinite; }
.sc-stage.in .mk-exam.is-loop .mk-tick { animation: sc-loop-tick 7s ease-in-out infinite; }
.sc-stage.in .mk-exam.is-loop .mk-timer { animation: sc-loop-timer 1s steps(1) infinite; }
/* …and the explanation lands with the answer (owner, 2026-09-22). It comes in just after the
   tick (35→48%) and clears before the loop re-asks, so the empty strip under the last option
   reads as "not answered yet" rather than as the hole the read-aloud rule below warns about.
   Opacity and transform only: `.mk` is a flex column whose illustration can SHRINK
   (`flex: 0 1 auto`), so an explanation that collapsed its own height would resize the drawing
   every seven seconds. The space is reserved for the whole loop on purpose.
   Beats the one-shot `.mk-exam .mk-explain` sc-slide rule above on specificity, which is what
   keeps the explanation from arriving at 1.55s — a second before the answer is even picked. */
@keyframes sc-loop-explain {
  0%, 42% { opacity: 0; transform: translateY(calc(var(--u) * 1.4)); }
  55%, 93% { opacity: 1; transform: none; }
  100% { opacity: 0; transform: translateY(calc(var(--u) * 1.4)); }
}
.sc-stage.in .mk-exam.is-loop .mk-explain { animation: sc-loop-explain 7s ease-in-out infinite; }
/* A slow idle bob, so the handset reads as an object on the page rather than a
   sticker. Composes with the hero parallax, which writes `translate`, not `transform`. */
@keyframes sc-bob { 50% { transform: translateY(-10px) rotate(.6deg); } }
.dev-cluster--solo .dev-phone { animation: sc-bob 7s ease-in-out infinite; }
/* Hero parallax (motion.js writes --px/--py on the [data-parallax] host). Only the
   cluster reacts — putting it on `.dev` as well would move each frame a second time
   inside a container that has already moved. */
.dev-cluster { translate: calc(var(--px, 0) * -8px) calc(var(--py, 0) * -8px); transition: translate .4s ease-out; }

/* Read-aloud — a 9 s loop: the scrubber runs while the spoken line highlights. */
@keyframes sc-scrub { from { width: 4%; } to { width: 96%; } }
@keyframes sc-read {
  0%, 4% { background: transparent; }
  8%, 28% { background: color-mix(in srgb, var(--brand) 16%, transparent); }
  33%, 100% { background: transparent; }
}
@keyframes sc-wave { from { transform: scaleY(.35); } to { transform: scaleY(1.15); } }
@keyframes sc-pulse { from { box-shadow: 0 0 0 0 color-mix(in srgb, var(--brand) 45%, transparent); } to { box-shadow: 0 0 0 calc(var(--u) * 3) transparent; } }
.sc-stage.in .mk-tts-track i { animation: sc-scrub 9s linear infinite; }
.sc-stage.in .mk-line { animation: sc-read 9s linear infinite; }
.sc-stage.in .mk-line[data-line="2"] { animation-delay: 3s; }
.sc-stage.in .mk-line[data-line="3"] { animation-delay: 6s; }
/* ⚠️ The read-aloud replica's explanation carries BOTH `mk-explain` and
   `mk-line[data-line="3"]`, and those two rules collide on the `animation` shorthand:
   they are equal specificity (4 classes each) and the data-line one is later, so it
   replaced sc-slide's 1.55s delay with 6s. With `backwards` fill that left the element
   at opacity 0 for a full six seconds — a card with a hole under its last option, which
   is the same fault the comment above says was fixed for the drill. Declaring both
   animations here, each with its own delay, is what the design already implied: the
   explanation fades in on time AND takes the reading highlight when it is spoken. */
.sc-stage.in .mk-exam .mk-explain.mk-line[data-line="3"] {
  animation: sc-slide .45s ease-out 1.55s backwards, sc-read 9s linear 6s infinite;
}
.sc-stage.in .mk-wave i { animation: sc-wave .55s ease-in-out infinite alternate; animation-delay: calc(var(--i) * -.13s); transform-origin: bottom; }
.sc-stage.in .mk-tts-main { animation: sc-pulse 2.4s ease-out infinite; }

/* Course — the read lessons tick off, the ring and the rail fill. */
@keyframes sc-tickin { from { opacity: 0; transform: translateX(-4px) scale(.6); } }
@keyframes sc-ringfill { from { stroke-dashoffset: var(--dash); } }
.sc-stage.in .mk-lesson.is-done .mk-lesson-ic { animation: sc-tickin .45s cubic-bezier(.34, 1.56, .64, 1) backwards; animation-delay: calc(.5s + var(--i) * .18s); }
.sc-stage.in .ring-fg { animation: sc-ringfill 1.3s cubic-bezier(.22, 1, .36, 1) .25s backwards; }

/* Progress — the line draws left to right, then the twelve dots land in order. */
@keyframes sc-draw { from { stroke-dashoffset: 1600; } }
@keyframes sc-fade { from { opacity: 0; } }
@keyframes sc-dot { from { opacity: 0; transform: scale(0); } }
.sc-stage.in .mk-trend { stroke-dasharray: 1600; animation: sc-draw 1.9s cubic-bezier(.4, 0, .2, 1) .3s backwards; }
.sc-stage.in .mk-area { animation: sc-fade 1s ease-out 1.5s backwards; }
.sc-stage.in .mk-dot { transform-box: fill-box; transform-origin: center; animation: sc-dot .35s cubic-bezier(.34, 1.56, .64, 1) backwards; animation-delay: calc(.3s + var(--i) * .09s); }

/* Diagnosis — a sweep down the topic list, the bars grow, the advice lands. */
@keyframes sc-scan { 0% { opacity: 0; transform: translateY(-60%); } 25% { opacity: 1; } 100% { opacity: 0; transform: translateY(60%); } }
@keyframes sc-grow { from { width: 0; } }
@keyframes sc-rec { from { opacity: 0; transform: translateY(10px) scale(.97); } }
.sc-stage.in .mk-scan { animation: sc-scan 1.5s ease-in-out .1s 1 both; }
.sc-stage.in .mk-bar i { animation: sc-grow .8s cubic-bezier(.22, 1, .36, 1) backwards; animation-delay: calc(.3s + var(--i) * .12s); }
.sc-stage.in .mk-rec { animation: sc-rec .5s cubic-bezier(.34, 1.56, .64, 1) 1.3s backwards; }

/* Works everywhere — the progress bar loads to the level the card is on, and the tag
 * beside it names what just happened. Deliberately one move and no loop: the section
 * is a still product shot with a claim pinned to it, and the claim is "your progress
 * is already here", not "something is busy". `from`-only like everything else here, so
 * the resting state is the finished one — the bar at its true 40% (sign 4 of 10, the
 * card the replica is showing), never a bar frozen at zero or overrun past its label.
 * The tag follows the fill rather than arriving with the section, so it reads as the
 * result of the bar and not as a second sticker that happens to be nearby. */
@keyframes sc-load { from { width: 0; } }
@keyframes sc-sync-in { from { opacity: 0; transform: translateY(5px); } }
.sc-stage.in .mk-drill .mk-pbar i { animation: sc-load .9s cubic-bezier(.22, 1, .36, 1) .15s backwards; }
.sc-stage.in .sc-sync { animation: sc-sync-in .45s ease-out .95s backwards; }
.sc-stage.in .sc-sync svg { animation: sc-tick .4s cubic-bezier(.34, 1.56, .64, 1) 1.15s backwards; }

/* Pass Protection — the requirement chips arrive as a set, not as a list. */
@keyframes sc-chip { from { opacity: 0; transform: translateY(8px); } }
.sc-pp.in .sc-pp-reqs li { animation: sc-chip .45s ease-out backwards; animation-delay: calc(.15s + var(--i) * .09s); }

/* ============================================================== responsive */
@media (max-width: 900px) {
  .sc-split { grid-template-columns: 1fr; gap: 1.6rem; }
  .sc-split.is-flip .sc-copy { order: 0; }
  .sc-copy h2 { font-size: clamp(1.5rem, 6vw, 2rem); }
  .dev-cluster--wide .dev-laptop { width: 100%; }
  .dev-cluster--wide .dev-phone { width: 24%; right: -3%; bottom: -10%; }
  .preview-cluster .dev-phone { width: 30%; }
  .sc-every-stage { padding-bottom: 8%; }
  .sc-sync { font-size: .7rem; padding: .3rem .6rem; }
}
@media (max-width: 700px) {
  /* A laptop replica at phone width is ~350px of frame. At the desktop unit its
     text lands near 6px, which is a grey texture, not a screenshot. Raising the
     unit trades horizontal fidelity for legibility: the replica keeps wrapping to
     its frame and simply shows less of the page down its bottom edge, which is
     what a screenshot of a scrolled page looks like anyway. */
  .dev-screen--laptop .mk { --u: .62cqw; }
}
@media (max-width: 560px) {
  /* A 16/10 laptop on a 360px screen is a 40px-tall strip of unreadable text —
     the phone replica is the honest thing to show, so the laptop steps back to
     a supporting role and the diagnosis panel goes single-column. */
  .dev-cluster--solo { max-width: 230px; }
  .dev-cluster--solo:has(.dev-cans) { max-width: 253px; }
  .mk-diag { flex-direction: column; }
  .mk-diag-list { flex: none; }
  .sc-sync { display: none; }
}

/* ========================================================== reduced motion */
@media (prefers-reduced-motion: reduce) {
  /* Not scoped to `.in`: the motion layer sets that class immediately under
     reduced motion, but a stylesheet must not depend on a script having run. */
  .sc-stage, .sc-stage *, .sc-pp, .sc-pp * { animation: none !important; }
  .dev-cluster { translate: none; }
}
