/* ==========================================================================
   Practice Exercises — the shared gamified UI.

   ONE design system, used by all four sections. The AI Speaking Tutor is
   restyled onto this too, so a student moving from Reading drills to Speaking
   drills does not land in a different product.

   NAMESPACED ON PURPOSE. Everything here is prefixed `px-` and nothing depends
   on a Tailwind utility. In this repo css/styles.css loads AFTER tailwind.css,
   so an equal-specificity rule in a stylesheet silently beats a utility class.
   That has already cost this site 900px of empty hero on two live pages. A
   self-contained component layer cannot be caught by it.

   Structure:
     1. tokens
     2. HUD
     3. level map (the serpentine path)
     4. nodes
     5. band zones
     6. drill screen
     7. options and feedback
     8. level complete
     9. review
    10. motion + reduced motion
   ========================================================================== */

/* ---------------------------------------------------------------- 1. tokens */
.px {
  /* Brand, matching tailwind.config.js. Duplicated deliberately: this layer
     must not depend on Tailwind being present. */
  --px-navy: #031f42;
  --px-navy-2: #072d5c;
  --px-blue: #005bff;
  --px-teal: #00e2c5;
  --px-gold: #ffd25d;
  --px-rose: #ff6b9d;
  --px-lime: #c3f499;

  --px-ink: #031f42;
  --px-muted: #5b6d84;
  --px-line: #e6efff;
  --px-surface: #ffffff;
  --px-canvas: #f4f9ff;

  --px-ok: #16a34a;
  --px-ok-bg: #ecfdf5;
  --px-no: #e5484d;
  --px-no-bg: #fef2f2;

  --px-r-sm: 10px;
  --px-r: 16px;
  --px-r-lg: 24px;

  --px-shadow: 0 1px 2px rgba(3, 31, 66, .06), 0 8px 24px rgba(3, 31, 66, .08);
  --px-shadow-lg: 0 2px 4px rgba(3, 31, 66, .08), 0 18px 48px rgba(3, 31, 66, .16);

  /* Set per band by JS on the map container. */
  --px-accent: var(--px-teal);

  font-family: Sora, system-ui, -apple-system, "Segoe UI", sans-serif;
  color: var(--px-ink);
}

/* The map runs on a dark canvas so the node glows actually read. */
.px-dark {
  --px-ink: #ffffff;
  --px-muted: rgba(255, 255, 255, .58);
  --px-line: rgba(255, 255, 255, .12);
  --px-surface: rgba(255, 255, 255, .06);
  color: #fff;
}

/* ------------------------------------------------------------------ 2. HUD */
.px-hud {
  position: sticky;
  top: 0;
  z-index: 30;
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 10px 16px;
  background: rgba(3, 31, 66, .82);
  backdrop-filter: blur(14px);
  -webkit-backdrop-filter: blur(14px);
  border-bottom: 1px solid rgba(255, 255, 255, .08);
}

.px-hud-back {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 36px;
  height: 36px;
  flex: 0 0 36px;
  border-radius: 50%;
  color: rgba(255, 255, 255, .75);
  background: rgba(255, 255, 255, .07);
  border: 1px solid rgba(255, 255, 255, .1);
  transition: background .15s, color .15s;
}

.px-hud-back:hover {
  background: rgba(255, 255, 255, .14);
  color: #fff;
}

.px-hud-title {
  min-width: 0;
  flex: 1 1 auto;
}

.px-hud-title b {
  display: block;
  font-size: 14px;
  font-weight: 700;
  color: #fff;
  line-height: 1.2;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

.px-hud-title span {
  display: block;
  font-size: 11px;
  color: rgba(255, 255, 255, .5);
  letter-spacing: .04em;
  text-transform: uppercase;
  margin-top: 2px;
}

/* XP / streak pills */
.px-stat {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  height: 34px;
  padding: 0 12px;
  border-radius: 999px;
  background: rgba(255, 255, 255, .07);
  border: 1px solid rgba(255, 255, 255, .1);
  font-size: 13px;
  font-weight: 700;
  color: #fff;
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
}

.px-stat svg {
  width: 15px;
  height: 15px;
  flex: 0 0 15px;
}

.px-stat-xp svg { color: var(--px-gold); }
.px-stat-streak svg { color: #ff8a3d; }
.px-stat-star svg { color: var(--px-teal); }

/* A stat that just changed, so the student sees what they earned. */
.px-stat.is-bumped { animation: px-bump .5s cubic-bezier(.2, 1.6, .4, 1); }

@keyframes px-bump {
  0%   { transform: scale(1); }
  35%  { transform: scale(1.18); }
  100% { transform: scale(1); }
}

/* ------------------------------------------------------------- 3. the map */
.px-map-wrap {
  position: relative;
  /* NO overflow:hidden. It clipped the "Start" flag off the top of the first
     node. The starfield below used to need the clip because it was oversized
     and translated; it now animates its background-position inside inset:0
     and stays in bounds on its own. */
  background:
    radial-gradient(1100px 500px at 50% -12%, rgba(0, 226, 197, .17), transparent 62%),
    radial-gradient(900px 480px at 12% 42%, rgba(0, 91, 255, .16), transparent 60%),
    radial-gradient(800px 520px at 88% 78%, rgba(255, 107, 157, .13), transparent 60%),
    linear-gradient(180deg, #04264f 0%, #031f42 45%, #02162f 100%);
  overflow-x: clip;
  overflow-y: visible;
}

/* Slow drifting starfield. Pure CSS, no image request. */
.px-map-wrap::before {
  content: "";
  position: absolute;
  inset: 0;
  background-image:
    radial-gradient(1.4px 1.4px at 12% 18%, rgba(255, 255, 255, .55), transparent),
    radial-gradient(1.2px 1.2px at 68% 12%, rgba(255, 255, 255, .40), transparent),
    radial-gradient(1.6px 1.6px at 34% 62%, rgba(255, 255, 255, .32), transparent),
    radial-gradient(1.2px 1.2px at 82% 48%, rgba(255, 255, 255, .45), transparent),
    radial-gradient(1.5px 1.5px at 22% 88%, rgba(255, 255, 255, .30), transparent),
    radial-gradient(1.3px 1.3px at 91% 82%, rgba(255, 255, 255, .38), transparent);
  background-size: 520px 520px;
  animation: px-drift 90s linear infinite;
  pointer-events: none;
}

@keyframes px-drift {
  from { background-position: 0 0; }
  to   { background-position: -520px 260px; }
}

.px-map {
  position: relative;
  max-width: 560px;
  margin: 0 auto;
  padding: 28px 20px 120px;
  z-index: 1;
  /* The band layout below keys off THIS element's width, not the viewport's.
     A viewport media query is wrong for a component that can be dropped into a
     narrow container: in the 340px phone frame of the design preview, on a
     1400px screen, the desktop rule matched and collapsed the node column to
     0px. A container query asks the only question that actually matters, which
     is how much room the map itself has. */
  container-type: inline-size;
}

/* The connector path is drawn by JS from measured node centers, so it stays
   correct at any width without a media query per breakpoint. */
.px-trail {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  pointer-events: none;
  overflow: visible;
  z-index: 0;
}

.px-trail-base {
  fill: none;
  stroke: rgba(255, 255, 255, .16);
  stroke-width: 4;
  stroke-linecap: round;
  /* Dotted, and thin. The old 14px band read as a solid pipe the nodes sat on
     top of, so anywhere it passed near text the text lost. A dotted hairline
     reads as a route without competing with anything. */
  stroke-dasharray: 1 12;
}

.px-trail-done {
  fill: none;
  stroke: url(#px-trail-grad);
  stroke-width: 5;
  stroke-linecap: round;
  opacity: .85;
  filter: drop-shadow(0 0 7px rgba(0, 226, 197, .45));
  transition: stroke-dashoffset 1.1s cubic-bezier(.4, 0, .2, 1);
}

/* --------------------------------------------------------------- 4. nodes */
/* One node per grid cell. layoutNodes() assigns grid-row and grid-column so
   the numbers snake left-to-right then right-to-left, which is what keeps the
   ladder a few rows deep instead of one very long column. */
.px-band-nodes {
  display: grid;
  gap: 38px 8px;
  justify-items: center;
  align-items: start;
  /* The top padding is reserved for the "Start" callout, which sits above the
     current node. Without it the flag pushed out of the map and was clipped at
     the top of the first band. */
  padding: 46px 0 6px;
}

.px-row {
  position: relative;
  display: flex;
  justify-content: center;
  z-index: 1;
}

.px-node {
  position: relative;
  display: flex;
  align-items: center;
  justify-content: center;
  width: 74px;
  height: 74px;
  border-radius: 50%;
  border: 0;
  cursor: pointer;
  font-family: inherit;
  font-size: 23px;
  font-weight: 800;
  font-variant-numeric: tabular-nums;
  color: #fff;
  background: linear-gradient(160deg, var(--px-accent), color-mix(in srgb, var(--px-accent) 62%, #000));
  box-shadow:
    0 6px 0 color-mix(in srgb, var(--px-accent) 55%, #000),
    0 12px 28px rgba(0, 0, 0, .38);
  transition: transform .13s cubic-bezier(.2, 1.4, .4, 1), box-shadow .13s;
}

/* Pressed, like a physical key. This is most of what makes a tap feel good. */
.px-node:active {
  transform: translateY(4px);
  box-shadow:
    0 2px 0 color-mix(in srgb, var(--px-accent) 55%, #000),
    0 6px 14px rgba(0, 0, 0, .34);
}

.px-node:focus-visible {
  outline: 3px solid #fff;
  outline-offset: 4px;
}

/* -- locked -- */
.px-node.is-locked {
  background: linear-gradient(160deg, #22354f, #16243a);
  box-shadow: 0 5px 0 #0e1929, 0 10px 22px rgba(0, 0, 0, .3);
  color: rgba(255, 255, 255, .3);
  /* Pointer, not not-allowed. The node IS tappable: tapping it explains which
     level has to be cleared first, which is the one thing the student wants to
     know. A not-allowed cursor promised nothing would happen. */
  cursor: pointer;
}

.px-node.is-locked:active { transform: none; }

/* -- current: the one thing on the screen that moves -- */
.px-node.is-current { animation: px-breathe 2.4s ease-in-out infinite; }

@keyframes px-breathe {
  0%, 100% { transform: translateY(0); }
  50%      { transform: translateY(-7px); }
}

.px-node.is-current::after {
  content: "";
  position: absolute;
  inset: -9px;
  border-radius: 50%;
  border: 3px solid var(--px-accent);
  opacity: .55;
  animation: px-ring 2.4s ease-out infinite;
}

@keyframes px-ring {
  0%   { transform: scale(1);    opacity: .55; }
  70%  { transform: scale(1.42); opacity: 0; }
  100% { transform: scale(1.42); opacity: 0; }
}

/* -- milestone: bigger, and reads as a reward -- */
.px-node.is-milestone {
  width: 90px;
  height: 90px;
  font-size: 27px;
  background: linear-gradient(160deg, var(--px-gold), #e8a417);
  box-shadow:
    0 7px 0 #b87d0c,
    0 16px 34px rgba(0, 0, 0, .42),
    0 0 0 4px rgba(255, 210, 93, .18);
}

/* The ring is what says "milestone" once the gold is gone. Without it a locked
   milestone is just a slightly bigger grey circle. */
.px-node.is-milestone::before {
  content: "";
  position: absolute;
  inset: -11px;
  border-radius: 50%;
  border: 2px dashed rgba(255, 210, 93, .5);
  pointer-events: none;
}

.px-node.is-milestone.is-locked {
  background: linear-gradient(160deg, #2b3a52, #1a2740);
  box-shadow: 0 6px 0 #101c2e, 0 12px 24px rgba(0, 0, 0, .3);
}

.px-node.is-milestone.is-locked::before { border-color: rgba(255, 210, 93, .22); }

/* -- cleared -- */
.px-node.is-done {
  background: linear-gradient(160deg, var(--px-accent), color-mix(in srgb, var(--px-accent) 55%, #000));
}

.px-node .px-node-check { width: 30px; height: 30px; }

/* Three stars under every node the student can open, filled to their best
   score. Below rather than above: the visible text label used to sit here and
   the trail ran straight through it, so this space is free, and a score reads
   more naturally under the thing it scores. */
.px-stars {
  position: absolute;
  top: calc(100% + 6px);
  left: 50%;
  transform: translateX(-50%);
  display: flex;
  gap: 2px;
  pointer-events: none;
}

.px-stars.is-dim { opacity: .45; }

.px-stars svg {
  width: 15px;
  height: 15px;
  color: rgba(255, 255, 255, .16);
  filter: drop-shadow(0 1px 1px rgba(0, 0, 0, .4));
}

.px-stars svg.is-on { color: var(--px-gold); }

/* "Start" callout on the current node. */
/* Sized to fit inside one grid column. At the old 11.5px with 15px padding it
   was 105px wide, and on the rightmost column of a 375px screen that pushed 31
   pixels past the viewport and gave the whole page a horizontal scrollbar. */
.px-start-flag {
  position: absolute;
  bottom: calc(100% + 13px);
  left: 50%;
  /* --px-flag-shift is set by clampFlag() when the pill would otherwise run
     past the edge of the map on an outer column. */
  transform: translateX(calc(-50% + var(--px-flag-shift, 0px)));
  padding: 6px 11px;
  border-radius: 999px;
  background: #fff;
  color: var(--px-navy);
  font-size: 10.5px;
  font-weight: 800;
  letter-spacing: .07em;
  text-transform: uppercase;
  white-space: nowrap;
  box-shadow: 0 5px 18px rgba(0, 0, 0, .3);
  /* Its OWN keyframes, not the shared px-breathe. That one animates
     `transform: translateY(...)`, which replaces the whole transform property
     and silently threw away the translateX(-50%) that centres this pill on its
     node, and the clamp offset with it. The pill was sitting a half-width to
     the right of its node the entire time. */
  animation: px-flag-breathe 2.4s ease-in-out infinite;
}

@keyframes px-flag-breathe {
  0%, 100% { transform: translateX(calc(-50% + var(--px-flag-shift, 0px))) translateY(0); }
  50%      { transform: translateX(calc(-50% + var(--px-flag-shift, 0px))) translateY(-7px); }
}

.px-start-flag::after {
  content: "";
  position: absolute;
  top: 100%;
  /* Counter-shifted so the arrow keeps pointing at the node even when the pill
     itself has been nudged inward. */
  left: calc(50% - var(--px-flag-shift, 0px));
  transform: translateX(-50%);
  border: 7px solid transparent;
  border-top-color: #fff;
}

/* ---------------------------------------------------------- 5. band zones */
.px-band-group {
  position: relative;
  z-index: 1;
}

.px-band {
  position: relative;
  z-index: 2;
  margin: 34px 0 26px;
  padding: 15px 18px;
  border-radius: var(--px-r);
  /* OPAQUE, deliberately. This used to mix the accent with `transparent`, so
     the trail behind it showed straight through the card and the path appeared
     to run over the text. Mixing with a solid navy instead keeps the same tint
     and occludes the path. On desktop the banner sits beside the path rather
     than over it, but this still has to hold on a phone, where the two columns
     stack and the path does pass behind the card. */
  background: linear-gradient(100deg,
    color-mix(in srgb, var(--px-accent) 20%, #0a2545),
    color-mix(in srgb, var(--px-accent) 5%, #06192f));
  border: 1px solid color-mix(in srgb, var(--px-accent) 32%, transparent);
  box-shadow: 0 8px 26px rgba(0, 0, 0, .30);
}

.px-band-eyebrow {
  display: flex;
  align-items: center;
  gap: 8px;
  font-size: 10.5px;
  font-weight: 800;
  letter-spacing: .12em;
  text-transform: uppercase;
  color: var(--px-accent);
}

.px-band-eyebrow::after {
  content: "";
  flex: 1;
  height: 1px;
  background: linear-gradient(90deg, color-mix(in srgb, var(--px-accent) 50%, transparent), transparent);
}

.px-band h3 {
  margin: 7px 0 4px;
  font-size: 19px;
  font-weight: 800;
  color: #fff;
}

.px-band p {
  margin: 0;
  font-size: 13px;
  line-height: 1.6;
  color: rgba(255, 255, 255, .62);
}

.px-band-cefr {
  display: inline-block;
  margin-top: 9px;
  padding: 3px 9px;
  border-radius: 6px;
  background: rgba(255, 255, 255, .1);
  font-size: 10.5px;
  font-weight: 700;
  letter-spacing: .06em;
  color: rgba(255, 255, 255, .8);
}

/* -------------------------------------------------------- 6. drill screen */
.px-drill {
  min-height: 100vh;
  min-height: 100dvh;
  display: flex;
  flex-direction: column;
  background: var(--px-canvas);
}

.px-drill-bar {
  display: flex;
  gap: 5px;
  padding: 14px 16px 0;
  max-width: 720px;
  width: 100%;
  margin: 0 auto;
}

.px-pip {
  flex: 1;
  height: 9px;
  border-radius: 999px;
  background: #dbe7f7;
  overflow: hidden;
}

.px-pip i {
  display: block;
  height: 100%;
  width: 0;
  border-radius: 999px;
  background: var(--px-accent);
  transition: width .35s cubic-bezier(.4, 0, .2, 1);
}

.px-pip.is-done i { width: 100%; }
.px-pip.is-wrong i { width: 100%; background: var(--px-no); }

.px-drill-body {
  flex: 1;
  width: 100%;
  max-width: 720px;
  margin: 0 auto;
  padding: 22px 16px 200px;
}

.px-qtype {
  display: inline-block;
  padding: 4px 11px;
  border-radius: 999px;
  background: color-mix(in srgb, var(--px-blue) 10%, #fff);
  color: var(--px-blue);
  font-size: 10.5px;
  font-weight: 800;
  letter-spacing: .09em;
  text-transform: uppercase;
}

.px-stem {
  margin: 14px 0 18px;
  font-size: 20px;
  line-height: 1.45;
  font-weight: 700;
  color: var(--px-ink);
}

/* Passage / transcript / email panel. */
.px-passage {
  padding: 18px 20px;
  border-radius: var(--px-r);
  background: #fff;
  border: 1px solid var(--px-line);
  box-shadow: var(--px-shadow);
  font-size: 15.5px;
  line-height: 1.75;
  color: #12305a;
  margin-bottom: 18px;
  white-space: pre-wrap;
}

/* Complete the Words: an input sitting over one dash per missing letter, the
   same affordance the live test pages give. The dashes are decoration for a
   sighted reader; the letter count is in the input's aria-label instead. */
.px-ctw-slot {
  position: relative;
  display: inline-block;
  vertical-align: baseline;
  margin: 0 2px;
}

.px-ctw-slot input {
  position: relative;
  z-index: 1;
  padding: 2px 6px 3px;
  border: 0;
  background: transparent;
  font: inherit;
  font-size: 16px;
  color: var(--px-blue);
  font-weight: 600;
  outline: none;
  text-align: left;
  /* Monospace, and the SAME letter-spacing as .px-ctw-dashes below.
     A proportional face gives every letter its own width, so typed text can
     never sit on fixed dashes: "ill" and "www" would occupy different spans.
     One advance per glyph is what makes letter i land on dash i. */
  font-family: ui-monospace, SFMono-Regular, "SF Mono", Menlo, Consolas, monospace;
  letter-spacing: .35em;
}

.px-ctw-slot input:focus + .px-ctw-dashes { color: var(--px-blue); }

.px-ctw-slot input:disabled { color: inherit; }

.px-ctw-dashes {
  position: absolute;
  left: 6px;
  right: 6px;
  bottom: 0;
  font-size: 16px;
  /* Must match .px-ctw-slot input exactly: same family, same spacing. */
  font-family: ui-monospace, SFMono-Regular, "SF Mono", Menlo, Consolas, monospace;
  font-weight: 600;
  letter-spacing: .35em;
  color: #9db4d4;
  pointer-events: none;
  white-space: nowrap;
  overflow: hidden;
}

.px-passage mark {
  background: #fff2c4;
  color: inherit;
  padding: 1px 3px;
  border-radius: 4px;
  font-weight: 600;
}

/* --------------------------------------------- 7. options and feedback */
.px-opts {
  display: flex;
  flex-direction: column;
  gap: 10px;
}

.px-opt {
  position: relative;
  display: flex;
  align-items: flex-start;
  gap: 13px;
  width: 100%;
  padding: 15px 17px;
  text-align: left;
  border-radius: var(--px-r);
  border: 2px solid var(--px-line);
  border-bottom-width: 4px;
  background: #fff;
  font-family: inherit;
  font-size: 15.5px;
  line-height: 1.5;
  color: var(--px-ink);
  cursor: pointer;
  transition: border-color .12s, background .12s, transform .1s;
}

.px-opt:hover:not(:disabled) { background: #f7fbff; border-color: #c9dcf5; }
.px-opt:active:not(:disabled) { transform: translateY(2px); border-bottom-width: 2px; }

.px-opt-key {
  flex: 0 0 27px;
  height: 27px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  border-radius: 8px;
  border: 2px solid var(--px-line);
  font-size: 12.5px;
  font-weight: 800;
  color: var(--px-muted);
  text-transform: uppercase;
}

.px-opt.is-picked {
  border-color: var(--px-blue);
  background: #f2f7ff;
}

.px-opt.is-picked .px-opt-key {
  border-color: var(--px-blue);
  background: var(--px-blue);
  color: #fff;
}

.px-opt.is-right {
  border-color: var(--px-ok);
  background: var(--px-ok-bg);
}

.px-opt.is-right .px-opt-key {
  border-color: var(--px-ok);
  background: var(--px-ok);
  color: #fff;
}

.px-opt.is-wrong {
  border-color: var(--px-no);
  background: var(--px-no-bg);
}

.px-opt.is-wrong .px-opt-key {
  border-color: var(--px-no);
  background: var(--px-no);
  color: #fff;
}

.px-opt:disabled { cursor: default; }

/* An option that is also creditable. Shown so a student who picked it knows
   it counted rather than assuming the grader is broken. */
.px-opt.is-alsook {
  border-color: var(--px-ok);
  background: var(--px-ok-bg);
  border-style: dashed;
}

/* Feedback bar, docked at the bottom of the drill. */
/* The dock is position:fixed and was uncapped, while .px-drill-body reserves
   only 200px for it. Measured against the real banks it renders 347 to 459px
   tall, so on any viewport under about 460px (a 320x568 phone, or any phone in
   landscape) the Continue button fell below the bottom of a fixed box that page
   scrolling cannot reach. Capped and scrollable. */
.px-fb {
  max-height: min(60vh, 60dvh);
  overflow-y: auto;
  -webkit-overflow-scrolling: touch;
}
.px-fb {
  position: fixed;
  left: 0;
  right: 0;
  bottom: 0;
  z-index: 20;
  padding: 16px 16px calc(16px + env(safe-area-inset-bottom));
  background: #fff;
  border-top: 2px solid var(--px-line);
  transform: translateY(100%);
  /* Slid off-screen is NOT hidden. Without these the dock stays in the tab
     order and in the accessibility tree while the student is still reading the
     question, so Tab then Enter reaches a Continue button that has no business
     existing yet and skips the question unanswered. visibility rather than
     display so the slide transition still runs. */
  visibility: hidden;
  pointer-events: none;
  transition: transform .28s cubic-bezier(.2, .9, .3, 1), visibility 0s linear .28s;
}

.px-fb.is-open {
  transform: translateY(0);
  visibility: visible;
  pointer-events: auto;
  transition: transform .28s cubic-bezier(.2, .9, .3, 1), visibility 0s;
}
.px-fb.is-ok  { background: var(--px-ok-bg); border-top-color: #b6ecc9; }
.px-fb.is-no  { background: var(--px-no-bg); border-top-color: #f6c9ca; }

.px-fb-inner {
  max-width: 720px;
  margin: 0 auto;
  display: flex;
  align-items: flex-start;
  gap: 13px;
}

.px-fb-icon {
  flex: 0 0 40px;
  width: 40px;
  height: 40px;
  border-radius: 50%;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  color: #fff;
}

.px-fb.is-ok .px-fb-icon { background: var(--px-ok); }
.px-fb.is-no .px-fb-icon { background: var(--px-no); }

.px-fb-text { flex: 1; min-width: 0; }

.px-fb-text b {
  display: block;
  font-size: 16px;
  font-weight: 800;
  margin-bottom: 3px;
}

.px-fb.is-ok .px-fb-text b { color: #0f7a3d; }
.px-fb.is-no .px-fb-text b { color: #b3282c; }

.px-fb-text p {
  margin: 0;
  font-size: 14px;
  line-height: 1.6;
  color: #2c4a6e;
}

.px-fb-skill {
  display: inline-block;
  margin-top: 8px;
  padding: 3px 9px;
  border-radius: 6px;
  background: rgba(3, 31, 66, .07);
  font-size: 11px;
  font-weight: 700;
  color: #2c4a6e;
}

/* Primary action. */
.px-btn {
  /* Block-level, not inline-flex. The `margin: ... auto` below only centres a
     block-level box; on an inline-level one both auto margins compute to 0 and
     the button pins to the left edge of its container. That was invisible while
     width:100% filled the line, and showed up in the fixed feedback dock, which
     spans the whole viewport: at 1280px the 720px button sat hard left with
     560px of dead space beside it. */
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 8px;
  width: 100%;
  max-width: 720px;
  margin: 14px auto 0;
  padding: 15px 22px;
  border: 0;
  border-radius: var(--px-r);
  box-shadow: 0 4px 0 color-mix(in srgb, var(--px-accent) 55%, #000);
  background: var(--px-accent);
  color: #04283f;
  font-family: inherit;
  font-size: 15px;
  font-weight: 800;
  letter-spacing: .04em;
  text-transform: uppercase;
  cursor: pointer;
  transition: transform .1s, box-shadow .1s, opacity .15s;
}

.px-btn:active { transform: translateY(3px); box-shadow: 0 1px 0 color-mix(in srgb, var(--px-accent) 55%, #000); }
.px-btn:disabled { opacity: .4; cursor: not-allowed; }
.px-btn-ok { background: var(--px-ok); color: #fff; box-shadow: 0 4px 0 #0d7a37; }
.px-btn-no { background: var(--px-no); color: #fff; box-shadow: 0 4px 0 #a81f23; }

/* Shake on a wrong answer. Short, once. */
.px-shake { animation: px-shake .38s cubic-bezier(.36, .07, .19, .97); }

@keyframes px-shake {
  10%, 90% { transform: translateX(-2px); }
  20%, 80% { transform: translateX(4px); }
  30%, 50%, 70% { transform: translateX(-7px); }
  40%, 60% { transform: translateX(7px); }
}

/* ------------------------------------------------- 8. level complete */
.px-done {
  min-height: 100vh;
  min-height: 100dvh;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  padding: 40px 20px 60px;
  text-align: center;
  background:
    radial-gradient(900px 420px at 50% 0%, rgba(0, 226, 197, .2), transparent 62%),
    linear-gradient(180deg, #04264f, #02162f);
}

.px-done h2 {
  font-size: 30px;
  font-weight: 800;
  color: #fff;
  margin: 22px 0 6px;
}

.px-done .px-done-sub {
  font-size: 15px;
  color: rgba(255, 255, 255, .6);
  margin: 0 0 26px;
}

.px-done-stars {
  display: flex;
  justify-content: center;
  gap: 12px;
}

.px-done-stars svg {
  width: 56px;
  height: 56px;
  color: #1e3153;
  transform: scale(.4);
  opacity: 0;
}

.px-done-stars svg.is-on {
  color: var(--px-gold);
  filter: drop-shadow(0 0 18px rgba(255, 210, 93, .6));
}

.px-done-stars svg.is-in {
  animation: px-star-pop .6s cubic-bezier(.2, 1.7, .4, 1) forwards;
}

@keyframes px-star-pop {
  0%   { transform: scale(.4) rotate(-24deg); opacity: 0; }
  70%  { transform: scale(1.22) rotate(6deg); opacity: 1; }
  100% { transform: scale(1) rotate(0);       opacity: 1; }
}

.px-done-grid {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: 10px;
  width: 100%;
  max-width: 420px;
  margin: 0 0 24px;
}

.px-done-cell {
  padding: 15px 10px;
  border-radius: var(--px-r);
  background: rgba(255, 255, 255, .07);
  border: 1px solid rgba(255, 255, 255, .1);
}

.px-done-cell b {
  display: block;
  font-size: 25px;
  font-weight: 800;
  color: #fff;
  font-variant-numeric: tabular-nums;
}

.px-done-cell span {
  display: block;
  margin-top: 3px;
  font-size: 10.5px;
  letter-spacing: .08em;
  text-transform: uppercase;
  color: rgba(255, 255, 255, .5);
}

.px-done-actions {
  display: flex;
  flex-direction: column;
  gap: 10px;
  width: 100%;
  max-width: 420px;
}

.px-btn-ghost {
  background: transparent;
  color: rgba(255, 255, 255, .8);
  border: 2px solid rgba(255, 255, 255, .2);
  box-shadow: none;
}

.px-btn-ghost:active { transform: translateY(2px); box-shadow: none; }

/* Confetti, drawn by JS into this layer. */
.px-confetti {
  position: fixed;
  inset: 0;
  pointer-events: none;
  z-index: 40;
}

/* --------------------------------------------------------- 9. review */
.px-review {
  width: 100%;
  max-width: 720px;
  margin: 0 auto;
  padding: 20px 16px 80px;
  text-align: left;
}

.px-review h3 {
  font-size: 13px;
  font-weight: 800;
  letter-spacing: .1em;
  text-transform: uppercase;
  color: rgba(255, 255, 255, .55);
  margin: 0 0 14px;
}

.px-rev-item {
  border-radius: var(--px-r);
  background: #fff;
  border: 1px solid var(--px-line);
  margin-bottom: 10px;
  overflow: hidden;
}

.px-rev-head {
  display: flex;
  align-items: center;
  gap: 11px;
  width: 100%;
  padding: 14px 16px;
  background: none;
  border: 0;
  font-family: inherit;
  font-size: 14.5px;
  font-weight: 700;
  text-align: left;
  color: var(--px-ink);
  cursor: pointer;
}

.px-rev-badge {
  flex: 0 0 25px;
  width: 25px;
  height: 25px;
  border-radius: 50%;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  color: #fff;
}

.px-rev-badge.is-ok { background: var(--px-ok); }
.px-rev-badge.is-no { background: var(--px-no); }
/* Targeted class, NOT `.px-rev-head span`. That selector also matched the
   badge sitting beside this label, and at 0,1,1 it beat `.px-rev-badge` at
   0,1,0, so the badge inherited `flex: 1` and a 25px circle rendered as a
   308px ellipse. Component classes must not be overridden by a descendant
   selector aimed at a sibling. */
.px-rev-title { flex: 1; min-width: 0; }

.px-rev-chev {
  flex: 0 0 16px;
  color: var(--px-muted);
  transition: transform .2s;
}

.px-rev-item.is-open .px-rev-chev { transform: rotate(180deg); }

.px-rev-body {
  display: none;
  padding: 0 16px 16px;
  border-top: 1px solid var(--px-line);
}

.px-rev-item.is-open .px-rev-body { display: block; }

.px-rev-row {
  display: flex;
  gap: 9px;
  padding: 9px 0;
  font-size: 14px;
  line-height: 1.55;
  border-bottom: 1px dashed var(--px-line);
}

.px-rev-row:last-child { border-bottom: 0; }

.px-rev-row em {
  flex: 0 0 74px;
  font-style: normal;
  font-size: 10.5px;
  font-weight: 800;
  letter-spacing: .06em;
  text-transform: uppercase;
  color: var(--px-muted);
  padding-top: 2px;
}

.px-rev-why {
  margin-top: 12px;
  padding: 13px 15px;
  border-radius: var(--px-r-sm);
  background: #f2f7ff;
  border-left: 3px solid var(--px-blue);
  font-size: 14px;
  line-height: 1.65;
  color: #2c4a6e;
}

/* ------------------------------------------------ 10. motion, a11y */
.px-sr {
  position: absolute;
  width: 1px;
  height: 1px;
  padding: 0;
  margin: -1px;
  overflow: hidden;
  clip: rect(0, 0, 0, 0);
  white-space: nowrap;
  border: 0;
}

@media (prefers-reduced-motion: reduce) {
  .px-map-wrap::before,
  .px-node.is-current,
  .px-node.is-current::after,
  .px-stat.is-bumped,
  .px-shake {
    animation: none !important;
  }

  .px-start-flag {
    animation: none !important;
    /* The animation was carrying the centring, so it has to be restated here. */
    transform: translateX(calc(-50% + var(--px-flag-shift, 0px)));
  }

  .px-done-stars svg.is-in {
    animation: none !important;
    transform: scale(1);
    opacity: 1;
  }

  .px-trail-done,
  .px-fb,
  .px-opt,
  .px-node {
    transition: none !important;
  }
}

/* Wider viewports get a roomier map and a bigger tap target. */
@media (min-width: 720px) {
  .px-map { max-width: 620px; padding-bottom: 150px; }
  .px-node { width: 84px; height: 84px; font-size: 26px; }
  .px-node.is-milestone { width: 100px; height: 100px; font-size: 30px; }
  .px-stem { font-size: 22px; }
}

/* The map gets the room to go two-column. This part IS a viewport question. */
@media (min-width: 1000px) {
  .px-map {
    max-width: 980px;
    padding: 34px 28px 160px;
  }
}

/* ---- wide map: band beside the path, not across it ---------------------
   A single narrow column on a 1400px screen wastes most of the viewport and
   forces the banner to interrupt the trail. Once the map itself is wide enough
   each band becomes two columns: description on the left, path on the right.
   The banner sticks while its own band scrolls past, so you always know which
   band you are in, and the path never has to cross it.

   Keyed on the CONTAINER, so a map inside a narrow panel still stacks. Browsers
   without container query support simply keep the stacked layout, which is a
   working design rather than a broken one. */
@container (min-width: 860px) {
  .px-band-group {
    display: grid;
    grid-template-columns: minmax(230px, 300px) minmax(0, 1fr);
    gap: 40px;
    align-items: start;
  }

  .px-band {
    position: sticky;
    /* Clears the sticky HUD above it. */
    top: 88px;
    margin: 0;
    padding: 20px 22px;
  }

  .px-band h3 { font-size: 22px; }

  /* The nodes column carries the vertical rhythm now, so the band no longer
     adds its own top margin and the first node lines up with the banner. */
  .px-band-nodes {
    min-width: 0;
    padding-bottom: 34px;
  }

  .px-band-group + .px-band-group .px-band-nodes { padding-top: 6px; }
}

/* ------------------------------------------------- 11. section task tabs
   Moving between the task types of one section without going back to the hub.

   These used to be pills on a dark full-bleed strip sitting directly under the
   dark site nav, and they read as a second navbar rather than as tabs. Worse,
   the whole page header sat between them and the map, so nothing connected
   them to the thing they switch.

   Now they are a conventional underlined tab row at the foot of the light page
   header, immediately above the map: shared baseline rule, the active tab
   carrying the section accent on a 3px underline that sits on that rule, and a
   real hover state. That is the shape people are trained to click.

   Still real links, one per indexable page. They have to stay crawlable, so
   this is never a JS tab widget. */
.px-tabs {
  border-top: 1px solid rgba(9, 30, 66, .07);
  background: #fff;
}

.px-tabs-inner {
  max-width: 980px;
  margin: 0 auto;
  padding: 0 20px;
  display: flex;
  align-items: stretch;
  justify-content: space-between;
  gap: 20px;
  /* The rule every tab shares a baseline with. The active tab's underline sits
     directly on it, which is what makes the row read as tabs rather than as a
     list of links. */
  box-shadow: inset 0 -1px 0 rgba(9, 30, 66, .1);
}

.px-tabs-row {
  display: flex;
  align-items: stretch;
  gap: 4px;
  min-width: 0;
  overflow-x: auto;
  scrollbar-width: none;
  -ms-overflow-style: none;
}

.px-tabs-row::-webkit-scrollbar { display: none; }

.px-tab {
  position: relative;
  display: inline-flex;
  align-items: center;
  gap: 8px;
  flex: 0 0 auto;
  padding: 15px 16px 14px;
  font-family: Sora, system-ui, -apple-system, "Segoe UI", sans-serif;
  font-size: 14.5px;
  font-weight: 600;
  color: #5b6d84;
  text-decoration: none;
  white-space: nowrap;
  background: none;
  border: 0;
  /* #6b7c93 measured 4.26:1 on the white tab bar, just under the AA floor. */
  transition: color .15s;
}

.px-tab svg { opacity: .75; flex: 0 0 auto; }

/* The underline is the whole affordance, so it exists on every tab and only
   changes colour and weight. A tab that grows an underline out of nothing on
   hover reads as a link; one whose underline darkens reads as a tab. */
.px-tab::after {
  content: "";
  position: absolute;
  left: 10px;
  right: 10px;
  bottom: 0;
  height: 3px;
  border-radius: 3px 3px 0 0;
  background: transparent;
  transition: background .15s;
}

.px-tab:hover { color: #091e42; }
.px-tab:hover::after { background: rgba(9, 30, 66, .18); }
.px-tab:hover svg { opacity: 1; }

/* Label in ink, underline in accent. Three of the four section accents are
   under 3:1 on white (Writing gold is 1.44:1), so the accent can be the bar
   but never the words. */
.px-tab.is-on {
  color: var(--px-ink, #005bff);
  font-weight: 700;
}

.px-tab.is-on::after { background: var(--px-ink, #005bff); }
.px-tab.is-on svg { opacity: 1; }

.px-tab:focus-visible {
  outline: 2px solid var(--px-accent, #005bff);
  outline-offset: -3px;
  border-radius: 6px;
}

.px-tabs-all {
  display: inline-flex;
  align-items: center;
  flex: 0 0 auto;
  font-family: Sora, system-ui, -apple-system, "Segoe UI", sans-serif;
  font-size: 13px;
  font-weight: 600;
  color: #8496aa;
  text-decoration: none;
  white-space: nowrap;
  transition: color .15s;
}

.px-tabs-all:hover { color: #005bff; }

@media (max-width: 720px) {
  .px-tabs-inner { padding: 0 14px; gap: 12px; }
  .px-tab { padding: 13px 12px 12px; font-size: 13.5px; }
  .px-tab svg { display: none; }
  .px-tabs-all { font-size: 12px; }
}

/* Under about 430px the "All task types" link and three tabs cannot share a
   row without the tabs losing their labels, and the labels are the point. */
@media (max-width: 430px) {
  .px-tabs-all { display: none; }
  .px-tabs-inner { justify-content: flex-start; }
}


/* ==================================================== 12. the hub page
   The first version of this page was four identical white cards in a grid with
   a coloured square on each, and it read as a directory rather than a product.
   What changed: a hero that states the actual job (find the task costing you
   the band), a three-step that answers "what do I do if I do not know my weak
   area", section blocks with an accent rail, and cards that carry a miniature
   of the band ladder they lead to. */

.px-hero {
  position: relative;
  overflow: hidden;
  background:
    radial-gradient(900px 420px at 18% 0%, rgba(0, 226, 197, .20), transparent 60%),
    radial-gradient(760px 420px at 82% 30%, rgba(0, 91, 255, .26), transparent 62%),
    linear-gradient(180deg, #04264f, #031f42);
  padding: 104px 20px 64px;
}

@media (min-width: 1024px) { .px-hero { padding: 132px 20px 76px; } }

.px-hero-inner { max-width: 1120px; margin: 0 auto; }

.px-hero-kicker {
  display: inline-block;
  margin: 0 0 18px;
  padding: 6px 13px;
  border-radius: 999px;
  background: rgba(0, 226, 197, .14);
  color: var(--px-teal, #00e2c5);
  font-size: 12px;
  font-weight: 700;
  letter-spacing: .04em;
}

.px-hero h1 {
  margin: 0;
  max-width: 17ch;
  font-size: clamp(31px, 5vw, 54px);
  line-height: 1.06;
  font-weight: 800;
  letter-spacing: -.025em;
  color: #fff;
  text-wrap: balance;
}

.px-hero-sub {
  margin: 20px 0 0;
  max-width: 62ch;
  font-size: clamp(16px, 1.5vw, 19px);
  line-height: 1.65;
  color: rgba(255, 255, 255, .72);
}

.px-hero-cta { display: flex; flex-wrap: wrap; gap: 12px; margin-top: 30px; }

.px-hero-btn {
  display: inline-flex;
  align-items: center;
  padding: 14px 26px;
  border-radius: 12px;
  background: var(--px-teal, #00e2c5);
  color: #04283f;
  font-size: 15px;
  font-weight: 700;
  transition: transform .12s, background .15s;
}

.px-hero-btn:hover { background: #2ff0da; }
.px-hero-btn:active { transform: translateY(2px); }

.px-hero-btn.is-ghost {
  background: transparent;
  color: #fff;
  border: 1px solid rgba(255, 255, 255, .25);
}

.px-hero-btn.is-ghost:hover { background: rgba(255, 255, 255, .07); }

.px-hero-stats {
  display: flex;
  flex-wrap: wrap;
  gap: 14px 40px;
  margin: 40px 0 0;
  padding: 0;
  list-style: none;
}

.px-hero-stats b {
  display: block;
  font-size: clamp(24px, 2.6vw, 32px);
  font-weight: 800;
  color: var(--px-teal, #00e2c5);
  font-variant-numeric: tabular-nums;
}

.px-hero-stats span {
  display: block;
  margin-top: 3px;
  font-size: 12.5px;
  color: rgba(255, 255, 255, .5);
}

/* ---- the three step band ---- */
.px-how { background: #f4f9ff; border-bottom: 1px solid #e6efff; }
.px-how-inner { max-width: 1120px; margin: 0 auto; padding: 34px 20px; }

.px-how ol {
  display: grid;
  gap: 18px;
  margin: 0;
  padding: 0;
  list-style: none;
}

@media (min-width: 860px) { .px-how ol { grid-template-columns: repeat(3, 1fr); gap: 28px; } }

.px-how li { display: flex; gap: 13px; align-items: flex-start; }

.px-how b {
  flex: 0 0 27px;
  height: 27px;
  border-radius: 50%;
  background: var(--px-navy, #031f42);
  color: #fff;
  font-size: 13px;
  font-weight: 800;
  display: inline-flex;
  align-items: center;
  justify-content: center;
}

.px-how span { font-size: 14.5px; line-height: 1.62; color: #46596f; }
.px-how strong { color: var(--px-navy, #031f42); font-weight: 700; }

/* ---- section blocks ---- */
.px-secs { background: #fff; }
.px-secs-inner { max-width: 1120px; margin: 0 auto; padding: 52px 20px 20px; }

.px-sec { margin-bottom: 46px; }

.px-sec-head {
  display: flex;
  align-items: center;
  gap: 11px;
  padding-left: 13px;
  border-left: 4px solid var(--sec);
  margin-bottom: 18px;
}

.px-sec-icon {
  width: 34px;
  height: 34px;
  border-radius: 10px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  background: color-mix(in srgb, var(--sec) 14%, #fff);
  color: var(--sec);
  flex: 0 0 34px;
}

.px-sec-head h2 {
  margin: 0;
  font-size: 21px;
  font-weight: 800;
  color: var(--px-navy, #031f42);
  letter-spacing: -.01em;
}

.px-sec-meta { font-size: 12.5px; color: #7a8ba1; margin-left: auto; text-align: right; }

.px-sec-grid { display: grid; gap: 14px; }
@media (min-width: 780px) { .px-sec-grid { grid-template-columns: repeat(2, 1fr); } }

/* ---- a task card ---- */
.px-card {
  display: flex;
  gap: 15px;
  padding: 19px;
  border-radius: 18px;
  border: 1px solid #e6efff;
  background: #fff;
  transition: border-color .15s, box-shadow .15s, transform .15s;
}

.px-card:hover {
  border-color: color-mix(in srgb, var(--sec) 45%, #fff);
  box-shadow: 0 10px 30px rgba(3, 31, 66, .09);
  transform: translateY(-2px);
}

.px-card-icon {
  flex: 0 0 44px;
  height: 44px;
  border-radius: 13px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
}

.px-card-body { display: block; min-width: 0; flex: 1; }

.px-card-head { display: flex; align-items: center; gap: 8px; }

.px-card-head b {
  font-size: 16px;
  font-weight: 700;
  color: var(--px-navy, #031f42);
  letter-spacing: -.01em;
}

.px-card-arrow { color: #b9c8dc; flex: 0 0 16px; transition: transform .15s, color .15s; }
.px-card:hover .px-card-arrow { color: var(--sec); transform: translateX(3px); }

.px-card-tag {
  display: block;
  margin-top: 5px;
  font-size: 13.5px;
  line-height: 1.6;
  color: #5b6d84;
}

/* Miniature of the band ladder the card leads to. Says "this climbs, and the
   last stretch goes past the exam" without another line of stats. */
.px-card-ladder { display: flex; gap: 3px; margin: 13px 0 9px; }

.px-card-ladder i {
  display: block;
  height: 4px;
  flex: 1;
  border-radius: 999px;
  opacity: .75;
}

.px-card-meta { display: block; font-size: 12px; color: #7a8ba1; }
.px-card-meta b { color: var(--px-navy, #031f42); font-weight: 700; }
.px-card-meta span { opacity: .5; }

.px-card-foot {
  display: flex;
  flex-direction: column;
  align-items: flex-end;
  justify-content: space-between;
  flex: 0 0 auto;
  text-align: right;
  font-size: 10.5px;
  color: #9db0c6;
  max-width: 118px;
  line-height: 1.45;
}

.px-card-foot em {
  font-style: normal;
  margin-top: 9px;
  padding: 3px 9px;
  border-radius: 999px;
  background: #ecfdf5;
  color: #0f7a3d;
  font-size: 10.5px;
  font-weight: 700;
  white-space: nowrap;
}

@media (max-width: 520px) { .px-card-foot { display: none; } }

/* ---- the band ladder explainer ---- */
.px-bands-sec { background: #f4f9ff; }
.px-bands-inner { max-width: 1120px; margin: 0 auto; padding: 54px 20px; }

.px-bands-inner h2 {
  margin: 0 0 8px;
  font-size: clamp(22px, 2.6vw, 30px);
  font-weight: 800;
  color: var(--px-navy, #031f42);
  letter-spacing: -.02em;
}

.px-bands-lede { margin: 0 0 26px; max-width: 60ch; color: #5b6d84; line-height: 1.6; }

.px-bands-grid { display: grid; gap: 12px; }
@media (min-width: 700px) { .px-bands-grid { grid-template-columns: repeat(2, 1fr); } }
@media (min-width: 1040px) { .px-bands-grid { grid-template-columns: repeat(4, 1fr); } }

.px-band-card {
  background: #fff;
  border: 1px solid #e6efff;
  border-top: 3px solid var(--sec);
  border-radius: 14px;
  padding: 17px 18px;
}

.px-band-card-no {
  font-size: 10.5px;
  font-weight: 800;
  letter-spacing: .1em;
  text-transform: uppercase;
  color: var(--sec);
}

.px-band-card h3 {
  margin: 7px 0 6px;
  font-size: 16px;
  font-weight: 700;
  color: var(--px-navy, #031f42);
}

.px-band-card p { margin: 0; font-size: 13.5px; line-height: 1.6; color: #5b6d84; }

.px-bands-note { margin: 24px 0 0; max-width: 78ch; font-size: 13.5px; line-height: 1.7; color: #7a8ba1; }

/* ---------------------------------------------- 13. authored body copy
   The long-form prose that makes each page rank and, just as importantly,
   makes the ten track pages distinct from each other. */
.px-prose { background: #fff; }
.px-prose-inner { max-width: 760px; margin: 0 auto; padding: 10px 20px 60px; }

.px-prose-h2,
.px-prose-inline h2 {
  margin: 34px 0 12px;
  font-size: clamp(19px, 2.2vw, 25px);
  font-weight: 800;
  line-height: 1.25;
  letter-spacing: -.015em;
  color: var(--px-navy, #031f42);
  text-wrap: balance;
}

.px-prose-inner p,
.px-prose-inline p {
  margin: 0 0 15px;
  font-size: 16px;
  line-height: 1.75;
  color: #46596f;
}

.px-prose-inner a,
.px-prose-inline a {
  color: var(--px-blue, #005bff);
  text-decoration: underline;
  text-underline-offset: 2px;
}

.px-prose-inner a:hover,
.px-prose-inline a:hover { color: #0047c7; }

.px-prose-inline { margin-top: 8px; }

/* ------------------------------------------------- 14. drill takes the page
   While a level is open the app sets .px-in-drill on <html>. The section tabs
   are navigation between sibling track PAGES, so during a drill they are a
   third stacked header bar and a way to lose an in-progress level to a
   mis-tap. The page header goes with them, for the same reason. */
.px-in-drill .px-track-head { display: none; }

/* ------------------------------------------------- 15. locked level
   The only screen in the product that names a plan. Everything indexable says
   "level 1 is free" and stops there, because the exercises are intended to
   become an add-on and a page that promises otherwise would need rewriting. */
.px-locked {
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 44px 18px 60px;
}

.px-locked-card {
  width: 100%;
  max-width: 520px;
  background: #fff;
  border: 1px solid rgba(9, 30, 66, .1);
  border-radius: 22px;
  padding: 32px 28px 26px;
  text-align: center;
  box-shadow: 0 18px 46px rgba(9, 30, 66, .1);
}

.px-locked-badge {
  width: 58px;
  height: 58px;
  margin: 0 auto 18px;
  border-radius: 17px;
  display: flex;
  align-items: center;
  justify-content: center;
  color: var(--px-gold, #ffd25d);
  background: color-mix(in srgb, var(--px-gold, #ffd25d) 16%, #fff);
}

.px-locked-title {
  font-family: Sora, system-ui, -apple-system, "Segoe UI", sans-serif;
  font-size: 23px;
  font-weight: 800;
  color: #091e42;
  margin: 0 0 10px;
  text-wrap: balance;
}

.px-locked-lede {
  font-size: 15px;
  line-height: 1.62;
  color: #5b6d84;
  margin: 0 0 22px;
}

.px-locked-list {
  list-style: none;
  margin: 0 0 24px;
  padding: 18px 0 0;
  border-top: 1px solid rgba(9, 30, 66, .08);
  text-align: left;
  display: grid;
  gap: 11px;
}

.px-locked-list li {
  position: relative;
  padding-left: 27px;
  font-size: 14.5px;
  line-height: 1.5;
  color: #33475b;
}

.px-locked-list li::before {
  content: "";
  position: absolute;
  left: 2px;
  top: 5px;
  width: 14px;
  height: 8px;
  border-left: 2.4px solid var(--px-teal, #00e2c5);
  border-bottom: 2.4px solid var(--px-teal, #00e2c5);
  transform: rotate(-45deg);
}

.px-locked-cta {
  display: flex;
  flex-wrap: wrap;
  gap: 10px;
  justify-content: center;
}

.px-locked-btn,
.px-locked-back {
  font-family: inherit;
  font-size: 15px;
  font-weight: 700;
  padding: 13px 26px;
  border-radius: 13px;
  cursor: pointer;
  text-decoration: none;
  transition: background .15s, border-color .15s;
}

.px-locked-btn {
  background: #091e42;
  color: #fff;
  border: 1px solid #091e42;
}

.px-locked-btn:hover { background: #16305e; }

.px-locked-back {
  background: #fff;
  color: #33475b;
  border: 1px solid rgba(9, 30, 66, .16);
}

.px-locked-back:hover { border-color: #091e42; }

.px-locked-foot {
  font-size: 13px;
  color: #7a8ba0;
  margin: 20px 0 0;
}

.px-locked-foot a { color: #005bff; font-weight: 600; }

/* ------------------------------------------------- 16. track page header
   Sits between the section tabs and the map. Light, because the map below it
   is dark and the contrast is what stops the two reading as one slab. */
.px-track-head {
  background: linear-gradient(180deg, #f6f9fd, #eef3fa);
  border-bottom: 1px solid rgba(9, 30, 66, .07);
}

.px-track-head-inner {
  max-width: 980px;
  margin: 0 auto;
  padding: 26px 20px 24px;
  font-family: Sora, system-ui, -apple-system, "Segoe UI", sans-serif;
}

.px-crumbs ol {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 8px;
  list-style: none;
  margin: 0;
  padding: 0;
  font-size: 13px;
  color: #7a8ba0;
}

.px-crumbs a { color: #7a8ba0; text-decoration: none; }
.px-crumbs a:hover { color: #005bff; }
.px-crumbs li[aria-current] { color: #33475b; font-weight: 600; }

.px-track-kicker {
  font-size: 11px;
  font-weight: 800;
  letter-spacing: .1em;
  text-transform: uppercase;
  margin: 16px 0 6px;
}

.px-track-h1 {
  font-size: clamp(25px, 4.4vw, 38px);
  font-weight: 800;
  line-height: 1.14;
  color: #091e42;
  margin: 0;
  text-wrap: balance;
}

.px-track-lede {
  font-size: 16px;
  line-height: 1.62;
  color: #4a5c73;
  margin: 12px 0 0;
  max-width: 68ch;
}

.px-track-facts {
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
  list-style: none;
  margin: 18px 0 0;
  padding: 0;
}

.px-track-facts li {
  font-size: 13px;
  color: #4a5c73;
  background: #fff;
  border: 1px solid rgba(9, 30, 66, .1);
  border-radius: 999px;
  padding: 6px 13px;
}

.px-track-facts li b { color: #091e42; font-weight: 800; }

.px-track-facts li.is-free {
  background: color-mix(in srgb, var(--px-teal, #00e2c5) 14%, #fff);
  border-color: color-mix(in srgb, var(--px-teal, #00e2c5) 42%, #fff);
  color: #0a6d5f;
  font-weight: 700;
}

/* Band key. The four colours on the ladder below mean nothing without it. */
.px-legend {
  display: flex;
  flex-wrap: wrap;
  gap: 7px 18px;
  margin: 18px 0 0;
  padding: 14px 0 0;
  border-top: 1px solid rgba(9, 30, 66, .08);
}

.px-legend-item {
  display: inline-flex;
  align-items: center;
  gap: 7px;
  font-size: 12.5px;
  color: #4a5c73;
  white-space: nowrap;
}

.px-legend-item i {
  width: 11px;
  height: 11px;
  border-radius: 3.5px;
  flex: 0 0 11px;
}

.px-legend-item em {
  font-style: normal;
  color: #8496aa;
  font-variant-numeric: tabular-nums;
}

@media (max-width: 640px) {
  .px-track-head-inner { padding: 20px 16px 18px; }
  .px-track-lede { font-size: 15px; }
  .px-legend { gap: 6px 14px; }
}

/* ===========================================================================
   17. TASK-TYPE TREATMENTS
   ---------------------------------------------------------------------------
   The owner chose the illustration direction ("Field"), so all three variants
   below share it: a duotone glyph of the artefact each task uses, on a tinted
   field, with the field's bottom edge available as a progress rail.

   They differ ONLY in how progress and the call to action are presented, which
   is the open question:

     i  Rail   progress is the field edge itself. Quietest. Count in the meta
               line, action as a text link that fills in on hover.
     j  Ring   a progress ring sits on the field. Most course-like. Solid button.
     k  Bar    an explicit labelled bar with a percentage, and a full-width
               button. Most dashboard-like, and the most legible at a glance.

   The MARKUP IS IDENTICAL across all three and identical to what shipped
   before, so the page stays the same to a crawler whichever is chosen.

   Progress is hydrated client-side from the student's own storage by the script
   in scripts/build-practice-exercise-pages.js. A card that has never been
   opened reads 0 and "Start now"; storage being blocked leaves it there.
   ======================================================================== */

/* ---------------------------------------------- shared: the Field look ----- */
[data-px-skin="i"] .px-sec,
[data-px-skin="j"] .px-sec,
[data-px-skin="k"] .px-sec { margin-bottom: 40px; }

[data-px-skin="i"] .px-sec-head,
[data-px-skin="j"] .px-sec-head,
[data-px-skin="k"] .px-sec-head {
  display: flex;
  align-items: center;
  gap: 11px;
  margin-bottom: 16px;
}
[data-px-skin="i"] .px-sec-icon,
[data-px-skin="j"] .px-sec-icon,
[data-px-skin="k"] .px-sec-icon {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 32px;
  height: 32px;
  border-radius: 9px;
  background: color-mix(in srgb, var(--sec) 15%, #fff);
  color: var(--ink, var(--sec));
}
[data-px-skin="i"] .px-sec-head h2,
[data-px-skin="j"] .px-sec-head h2,
[data-px-skin="k"] .px-sec-head h2 { font-size: 20px; margin: 0; }
[data-px-skin="i"] .px-sec-meta,
[data-px-skin="j"] .px-sec-meta,
[data-px-skin="k"] .px-sec-meta { margin-left: auto; color: #8496aa; font-size: 12.5px; }

[data-px-skin="i"] .px-sec-grid,
[data-px-skin="j"] .px-sec-grid,
[data-px-skin="k"] .px-sec-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(270px, 1fr));
  gap: 16px;
}

[data-px-skin="i"] .px-card,
[data-px-skin="j"] .px-card,
[data-px-skin="k"] .px-card {
  position: relative;
  display: flex;
  flex-direction: column;
  padding: 0 0 16px;
  border: 1px solid #e6efff;
  border-radius: 16px;
  overflow: hidden;
  background: #fff;
  transition: transform .14s, box-shadow .14s, border-color .14s;
}
[data-px-skin="i"] .px-card:hover,
[data-px-skin="j"] .px-card:hover,
[data-px-skin="k"] .px-card:hover {
  transform: translateY(-2px);
  border-color: color-mix(in srgb, var(--sec) 40%, transparent);
  box-shadow: 0 12px 26px rgba(9, 30, 66, .09);
}
[data-px-skin="i"] .px-card-icon,
[data-px-skin="j"] .px-card-icon,
[data-px-skin="k"] .px-card-icon { display: none; }

[data-px-skin="i"] .px-card-field,
[data-px-skin="j"] .px-card-field,
[data-px-skin="k"] .px-card-field {
  position: relative;
  display: block;
  height: 96px;
  background: color-mix(in srgb, var(--sec) 8%, #fff);
  color: var(--ink, var(--sec));
}
[data-px-skin="i"] .px-card-art,
[data-px-skin="j"] .px-card-art,
[data-px-skin="k"] .px-card-art {
  position: absolute;
  left: 18px;
  top: 50%;
  transform: translateY(-50%);
  width: 58px;
  height: 58px;
}

[data-px-skin="i"] .px-card-body,
[data-px-skin="j"] .px-card-body,
[data-px-skin="k"] .px-card-body {
  display: flex;
  flex-direction: column;
  flex: 1 1 auto;
  padding: 14px 18px 0;
}
[data-px-skin="i"] .px-card-head b,
[data-px-skin="j"] .px-card-head b,
[data-px-skin="k"] .px-card-head b { font-size: 16px; letter-spacing: -.01em; }
[data-px-skin="i"] .px-card-arrow,
[data-px-skin="j"] .px-card-arrow,
[data-px-skin="k"] .px-card-arrow { color: var(--ink, var(--sec)); }
[data-px-skin="i"] .px-card-tag,
[data-px-skin="j"] .px-card-tag,
[data-px-skin="k"] .px-card-tag { font-size: 13px; line-height: 1.5; color: #5b6d84; }
[data-px-skin="i"] .px-card-ladder,
[data-px-skin="j"] .px-card-ladder,
[data-px-skin="k"] .px-card-ladder { display: none; }
[data-px-skin="i"] .px-card-meta,
[data-px-skin="j"] .px-card-meta,
[data-px-skin="k"] .px-card-meta {
  display: block;
  margin-top: 10px;
  font-size: 12px;
  color: #8496aa;
  font-variant-numeric: tabular-nums;
}
[data-px-skin="i"] .px-card-meta b,
[data-px-skin="j"] .px-card-meta b,
[data-px-skin="k"] .px-card-meta b { color: #33475b; font-weight: 700; }

/* The free badge floats on the field so it never competes with the text. */
[data-px-skin="i"] .px-card-foot,
[data-px-skin="j"] .px-card-foot,
[data-px-skin="k"] .px-card-foot { font-size: 0; }
[data-px-skin="i"] .px-card-foot em,
[data-px-skin="j"] .px-card-foot em,
[data-px-skin="k"] .px-card-foot em {
  position: absolute;
  top: 14px;
  right: 14px;
  padding: 4px 9px;
  border-radius: 999px;
  background: #0f7a3d;
  color: #fff;
  font-size: 11px;
  font-style: normal;
  font-weight: 700;
  z-index: 1;
}

/* The progress block is hidden by default and each variant opts in, so a
   variant can never inherit a treatment it did not ask for. */
.px-card-prog { display: none; }
.px-card-cta { display: none; }
.px-card-prog-line {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 8px;
}
.px-card-prog-text { font-size: 12px; color: #5b6d84; font-variant-numeric: tabular-nums; }
.px-card-prog-text b { color: #031f42; font-weight: 800; }
.px-card-prog-pct { font-size: 11.5px; font-weight: 700; color: #8496aa; font-variant-numeric: tabular-nums; }
.px-card-prog-bar {
  display: block;
  height: 6px;
  margin-top: 7px;
  border-radius: 999px;
  background: rgba(9, 30, 66, .09);
  overflow: hidden;
}
.px-card-prog-bar i {
  display: block;
  height: 100%;
  border-radius: 999px;
  background: var(--sec);
  transition: width .4s ease;
}

/* ------------------------------------------------------- i. Rail ---------
   Progress stays in the field's own bottom edge, so the card gains a state
   without gaining a component. The count rides in the meta line and the action
   is a text link that fills in on hover. */
[data-px-skin="i"] .px-card-field::after {
  content: '';
  position: absolute;
  left: 0;
  right: 0;
  bottom: 0;
  height: 4px;
  background: color-mix(in srgb, var(--sec) 18%, #fff);
}
[data-px-skin="i"] .px-card-field::before {
  content: '';
  position: absolute;
  left: 0;
  bottom: 0;
  height: 4px;
  width: var(--px-done, 0%);
  background: var(--sec);
  z-index: 1;
  transition: width .4s ease;
}
[data-px-skin="i"] .px-card-prog { display: block; margin-top: 8px; }
[data-px-skin="i"] .px-card-prog-bar,
[data-px-skin="i"] .px-card-prog-pct { display: none; }
[data-px-skin="i"] .px-card-cta {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  align-self: flex-start;
  margin-top: 12px;
  padding: 7px 14px;
  border-radius: 999px;
  border: 1px solid color-mix(in srgb, var(--ink, var(--sec)) 45%, transparent);
  color: var(--ink, var(--sec));
  font-size: 12.5px;
  font-weight: 700;
  transition: background .14s, color .14s;
}
[data-px-skin="i"] .px-card:hover .px-card-cta { background: var(--ink, var(--sec)); color: #fff; }

/* ------------------------------------------------------- j. Ring ---------
   A ring on the field, which is the most course-like of the three and the only
   one that reads as progress from across the page. */
[data-px-skin="j"] .px-card-field::after {
  content: '';
  position: absolute;
  right: 16px;
  bottom: -22px;
  width: 46px;
  height: 46px;
  border-radius: 50%;
  background:
    conic-gradient(var(--sec) calc(var(--px-done, 0%) * 3.6), rgba(9, 30, 66, .1) 0);
  box-shadow: 0 0 0 4px #fff;
}
[data-px-skin="j"] .px-card-prog {
  display: block;
  position: absolute;
  right: 16px;
  bottom: -22px;
  width: 46px;
  height: 46px;
  z-index: 2;
}
[data-px-skin="j"] .px-card-prog-line {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 34px;
  height: 34px;
  margin: 6px;
  border-radius: 50%;
  background: #fff;
}
[data-px-skin="j"] .px-card-prog-text { display: none; }
[data-px-skin="j"] .px-card-prog-pct { font-size: 11px; font-weight: 800; color: var(--ink, #031f42); }
[data-px-skin="j"] .px-card-prog-bar { display: none; }
[data-px-skin="j"] .px-card-body { padding-top: 30px; }
[data-px-skin="j"] .px-card-cta {
  display: block;
  margin-top: 14px;
  padding: 10px 14px;
  border-radius: 10px;
  background: color-mix(in srgb, var(--sec) 14%, #fff);
  color: var(--ink, var(--sec));
  font-size: 13px;
  font-weight: 800;
  text-align: center;
  transition: background .14s, color .14s;
}
[data-px-skin="j"] .px-card:hover .px-card-cta { background: var(--ink, var(--sec)); color: #fff; }

/* -------------------------------------------------------- k. Bar ---------
   Says the number out loud. The least decorative and the easiest to read at a
   glance, which is what a returning student wants. */
[data-px-skin="k"] .px-card-field::after {
  content: '';
  position: absolute;
  left: 0;
  right: 0;
  bottom: 0;
  height: 3px;
  background: color-mix(in srgb, var(--sec) 18%, #fff);
}
[data-px-skin="k"] .px-card-prog { display: block; margin-top: 13px; }
[data-px-skin="k"] .px-card-cta {
  display: block;
  margin-top: 13px;
  padding: 11px 14px;
  border-radius: 11px;
  background: var(--ink, var(--sec));
  color: #fff;
  font-size: 13px;
  font-weight: 800;
  text-align: center;
  box-shadow: 0 2px 0 0 color-mix(in srgb, var(--ink, var(--sec)) 62%, #000);
  transition: transform .12s, box-shadow .12s;
}
[data-px-skin="k"] .px-card:hover .px-card-cta {
  transform: translateY(1px);
  box-shadow: 0 1px 0 0 color-mix(in srgb, var(--ink, var(--sec)) 62%, #000);
}

/* A finished track should read as finished, not as one more thing to start. */
[data-px-skin="i"] .px-card[data-complete] .px-card-cta,
[data-px-skin="j"] .px-card[data-complete] .px-card-cta,
[data-px-skin="k"] .px-card[data-complete] .px-card-cta {
  background: #0f7a3d;
  border-color: #0f7a3d;
  color: #fff;
  box-shadow: none;
}

@media (prefers-reduced-motion: reduce) {
  [data-px-skin="i"] .px-card,
  [data-px-skin="j"] .px-card,
  [data-px-skin="k"] .px-card { transition: none; transform: none; }
  .px-card-prog-bar i { transition: none; }
}



/* ---- Complete the Words review: the passage, not a pile of fragments -------
   This screen used to list word endings with no sentence around them, which
   told a student nothing. The passage IS the question on this track. */
.px-rev-para-label {
  margin: 2px 0 7px;
  font-size: 11.5px;
  font-weight: 800;
  letter-spacing: .06em;
  text-transform: uppercase;
  color: #5b6d84;
}
.px-rev-para {
  padding: 14px 16px;
  border-radius: 12px;
  background: #f7f9fc;
  border: 1px solid rgba(3, 31, 66, .08);
  font-size: 15px;
  line-height: 2;
  color: #2c4a6e;
  text-align: left;
}
.px-rev-fill {
  padding: 1px 3px;
  border-radius: 4px;
  font-weight: 800;
}
.px-rev-fill.is-ok { color: #0f7a3d; background: rgba(22, 163, 74, .12); }
.px-rev-fill.is-no {
  color: #b3282c;
  background: rgba(229, 72, 77, .12);
  margin-right: 4px;
  font-weight: 700;
}

/* ---- the launch-offer line, as a real tooltip ------------------------------
   A button rather than a hover target, so it works on a phone and reaches a
   keyboard, and so a student can leave it open while reading it. */
.px-flag-tip {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  border: 0;
  cursor: pointer;
  font: inherit;
}
.px-flag-tip svg { opacity: .7; flex: 0 0 auto; }
.px-flag-tip:hover svg { opacity: 1; }
.px-flag-tip-body {
  display: block;
  max-width: 420px;
  margin: -4px auto 14px;
  padding: 11px 14px;
  border-radius: 11px;
  background: #031f42;
  color: #dce9fb;
  font-size: 12.5px;
  line-height: 1.55;
  text-align: left;
}
.px-flag-tip-body[hidden] { display: none; }

/* ---- the design swatch ------------------------------------------------------
   Temporary. Lets the owner switch between the four treatments above and is
   deleted, with its markup in scripts/build-practice-exercise-pages.js, once
   one is chosen.

   This was lost when the previous four skins were replaced, because it lived
   inside the section that was swapped out. The markup and all four buttons kept
   rendering with no position and no styling, so the control was invisible on
   the page. Restored verbatim. */





@media (max-width: 560px) {
}

/* A modal card is focused on open so screen readers announce the dialog, and
   the browser then rings the whole card. Focus stays, ring goes. Same cause as
   the heading ring on the speaking tutor. */
/* RESTORED. 83e32c55 was a copy fix and deleted every one of these rules by
   accident, so .px-modal lost position:fixed while js/practice-exercises.js
   kept locking body scroll when it opens. On a phone that froze the page with
   the dialog rendered thousands of pixels below the fold and no way to reach
   it: tapping any locked level bricked the tab. Byte-for-byte what 83e32c55^
   had, plus a max-height guard for short screens. */
.px-modal {
  position: fixed;
  inset: 0;
  z-index: 80;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 20px;
  opacity: 0;
  transition: opacity .18s ease;
}
.px-modal.is-in { opacity: 1; }
.px-modal-backdrop {
  position: absolute;
  inset: 0;
  background: rgba(3, 31, 66, .62);
  backdrop-filter: blur(2px);
}
.px-modal-card {
  position: relative;
  z-index: 1;
  max-width: 520px;
  width: 100%;
  max-height: calc(100vh - 40px);
  overflow-y: auto;
  transform: translateY(10px) scale(.985);
  transition: transform .18s ease;
  outline: none;
}
.px-modal.is-in .px-modal-card { transform: translateY(0) scale(1); }
.px-modal-x {
  position: absolute;
  top: 10px;
  right: 12px;
  width: 34px;
  height: 34px;
  border: 0;
  border-radius: 9px;
  background: transparent;
  color: #5b6d84;
  font-size: 26px;
  line-height: 1;
  cursor: pointer;
}
.px-modal-x:hover { background: rgba(3, 31, 66, .06); color: #031f42; }
.px-modal-flag {
  display: inline-block;
  margin: 0 0 12px;
  padding: 5px 11px;
  border-radius: 999px;
  background: rgba(0, 226, 197, .16);
  color: #067a68;
  font-size: 12px;
  font-weight: 800;
  letter-spacing: .02em;
}
@media (prefers-reduced-motion: reduce) {
  .px-modal, .px-modal-card { transition: none; }
}


/* The card must never be taller than the screen it is centred in. */
.px-modal-card {
  max-height: min(calc(100vh - 40px), calc(100dvh - 40px));
  -webkit-overflow-scrolling: touch;
}

.px-modal-card[tabindex='-1']:focus,
.px-modal-card[tabindex='-1']:focus-visible {
  outline: none;
}

/* ------------------------------------------------------------------ */
/* Full screen                                                         */
/*                                                                     */
/* Two paths, because iOS Safari does not implement requestFullscreen  */
/* on anything but a <video>, and phones are the smallest screens and  */
/* the ones that need this most. The native path gets :fullscreen; the */
/* fallback pins the drill over the viewport and looks the same.       */
/* ------------------------------------------------------------------ */

.px-hud-fs {
  flex: none;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 7px;
  min-height: 38px;
  padding: 0 12px;
  border-radius: 10px;
  font: inherit;
  font-size: 13px;
  font-weight: 700;
  white-space: nowrap;
  border: 1px solid #dbe7f7;
  background: #fff;
  color: #31507d;
  cursor: pointer;
  padding: 0;
  transition:
    background 0.15s,
    color 0.15s,
    border-color 0.15s;
}
.px-hud-fs:hover {
  background: #f2f7ff;
  color: #06306e;
  border-color: #b9d3f5;
}
.px-hud-fs.is-on {
  background: var(--px-accent, #005bff);
  border-color: var(--px-accent, #005bff);
  color: #fff;
}
.px-hud-fs svg {
  display: block;
}

/* The drill fills whatever it is given, native or faux. */
.px-drill:fullscreen,
.px-drill:-webkit-full-screen,
.px-drill.is-faux-fs {
  background: var(--px-canvas, #f4f9ff);
  overflow-y: auto;
  -webkit-overflow-scrolling: touch;
}
.px-drill:fullscreen,
.px-drill:-webkit-full-screen {
  width: 100%;
  height: 100%;
  padding: 20px 16px 32px;
}

/* The fallback. Fixed rather than absolute so it is not affected by an  */
/* ancestor's transform or overflow, and inset:0 rather than width/height */
/* so it tracks the visual viewport when the iOS toolbars slide away.     */
.px-drill.is-faux-fs {
  position: fixed;
  inset: 0;
  z-index: 9999;
  padding: max(16px, env(safe-area-inset-top)) 16px
    max(24px, env(safe-area-inset-bottom)) 16px;
}

/* Whatever is behind must not scroll under the pinned drill. */
html.px-fs-lock,
html.px-fs-lock body {
  overflow: hidden;
  /* touch-action alone does not stop iOS rubber-banding the document. */
  overscroll-behavior: none;
}

/* The feedback dock is fixed to the viewport in the normal flow. Inside a
   native fullscreen element that is the same viewport, so it still works;
   inside the faux one it must sit above the pinned drill. */
.px-drill.is-faux-fs .px-fb {
  z-index: 10000;
}

/* The label goes on a narrow phone, where the HUD carries a back button, a
   two-line title and an XP pill already. The icon keeps the function; the
   aria-label and title keep the meaning for anyone who needs it. */
@media (max-width: 560px) {
  .px-hud-fs-label {
    display: none;
  }
  .px-hud-fs {
    padding: 0;
    width: 38px;
  }
}


/* The site header is position:fixed, z-50, 65px tall, and is not hidden during
   a drill. The HUD is sticky at top:0 with z-30, so the moment the student
   scrolled, the only "Leave this level" button went under it. Sit below the
   header and above everything else in the drill. */
.px-hud {
  top: 65px;
  z-index: 60;
}
.px-drill.is-faux-fs .px-hud {
  /* In faux full screen the site header is not on screen, so reclaim the gap. */
  top: 0;
}


/* ------------------------------------------------------------------ */
/* RECOVERED from 83e32c55^                                            */
/*                                                                     */
/* 83e32c55 was a COPY fix. It also deleted about 470 lines of CSS.    */
/* .px-skin-pick and .px-flag-tip were noticed and restored; these 48  */
/* selectors were not, and have been live-and-unstyled in production   */
/* since 30 August: the paywall modal, the transcript block inside the */
/* feedback dock, the lock note, the feedback input, and the whole     */
/* .px-ms-* milestone writing UI.                                      */
/*                                                                     */
/* Recovered verbatim rather than rewritten, so this restores the       */
/* previous behaviour exactly and nothing new is invented.             */
/* ------------------------------------------------------------------ */

.px-locknote {
  position: absolute;
  z-index: 60;
  max-width: 250px;
  padding: 9px 13px;
  border-radius: 10px;
  background: #031f42;
  color: #fff;
  font-size: 13px;
  font-weight: 600;
  line-height: 1.45;
  box-shadow: 0 8px 24px rgba(3, 31, 66, .28);
  opacity: 0;
  transform: translateY(4px);
  transition: opacity .16s ease, transform .16s ease;
  pointer-events: none;
}

.px-locknote.is-in { opacity: 1; transform: translateY(0); }

.px-locknote::after {
  content: '';
  position: absolute;
  left: var(--px-note-arrow, 50%);
  margin-left: -6px;
  border: 6px solid transparent;
}

.px-locknote.is-above::after { top: 100%; border-top-color: #031f42; }

.px-locknote.is-below::after { bottom: 100%; border-bottom-color: #031f42; }

.px-fb-tr {
  margin-top: 9px;
  border-top: 1px solid rgba(3, 31, 66, .1);
  padding-top: 8px;
}

.px-fb-tr > summary {
  cursor: pointer;
  font-size: 12.5px;
  font-weight: 700;
  color: #2c4a6e;
  list-style: none;
  display: inline-flex;
  align-items: center;
  gap: 5px;
}

.px-fb-tr > summary::-webkit-details-marker { display: none; }

.px-fb-tr > summary::before {
  content: '';
  width: 0;
  height: 0;
  border: 4px solid transparent;
  border-left-color: currentColor;
  transition: transform .15s ease;
}

.px-fb-tr[open] > summary::before { transform: rotate(90deg) translateX(-1px); }

.px-fb-tr > summary:hover { color: #031f42; }

.px-fb-tr-body {
  margin-top: 7px;
  max-height: 190px;
  overflow-y: auto;
  font-size: 13px;
  line-height: 1.65;
  color: #2c4a6e;
  white-space: pre-wrap;
}

.px-ms-flag {
  display: inline-block;
  margin-bottom: 10px;
  padding: 5px 11px;
  border-radius: 999px;
  background: rgba(255, 210, 93, .22);
  color: #8a6100;
  font-size: 11.5px;
  font-weight: 800;
  letter-spacing: .05em;
  text-transform: uppercase;
}

.px-ms-brief {
  margin: 14px 0 18px;
  padding: 18px 20px;
  border-radius: 14px;
  background: #fff;
  border: 1px solid var(--px-line);
  font-size: 15px;
  line-height: 1.65;
  color: #2c4a6e;
}

.px-ms-brief p { margin: 0 0 10px; }

.px-ms-brief p:last-child { margin-bottom: 0; }

.px-ms-dir { font-weight: 700; color: #031f42; }

.px-ms-mate {
  margin: 0 0 12px;
  padding-left: 13px;
  border-left: 3px solid var(--px-line);
}

.px-ms-mate em {
  display: block;
  font-style: normal;
  font-size: 12px;
  font-weight: 800;
  text-transform: uppercase;
  letter-spacing: .05em;
  color: #5b6d84;
  margin-bottom: 3px;
}

.px-ms-prof { border-left-color: var(--px-accent); }

.px-ms-meter {
  display: flex;
  align-items: baseline;
  gap: 12px;
  flex-wrap: wrap;
  margin-bottom: 8px;
  font-size: 13px;
}

.px-ms-words { font-weight: 800; color: #5b6d84; font-variant-numeric: tabular-nums; }

.px-ms-words.is-ok { color: #0f7a3d; }

.px-ms-target { color: #9aa9ba; }

.px-ms-time {
  margin-left: auto;
  font-weight: 700;
  color: #5b6d84;
  font-variant-numeric: tabular-nums;
}

.px-ms-time.is-low { color: #b3282c; }

.px-ms-input {
  width: 100%;
  min-height: 230px;
  padding: 15px 16px;
  border: 2px solid var(--px-line);
  border-radius: 14px;
  background: #fff;
  font: inherit;
  font-size: 15.5px;
  line-height: 1.7;
  color: #031f42;
  resize: vertical;
}

.px-ms-input:focus { outline: none; border-color: var(--px-accent); }

.px-ms-input:disabled { background: #f7f9fc; color: #5b6d84; }

.px-ms-note {
  margin: 10px 0 0;
  text-align: center;
  font-size: 12.5px;
  color: #9aa9ba;
}

.px-ms-feedback {
  margin: 14px 0 18px;
  padding: 18px 20px;
  border-radius: 14px;
  background: #fff;
  border: 1px solid var(--px-line);
  font-size: 15px;
  line-height: 1.7;
  color: #2c4a6e;
  text-align: left;
}

.px-ms-feedback p { margin: 0 0 12px; }

.px-ms-feedback p:last-child { margin-bottom: 0; }

.px-ms-check-label {
  margin: 4px 0 8px;
  font-size: 11.5px;
  font-weight: 800;
  letter-spacing: .06em;
  text-transform: uppercase;
  color: #5b6d84;
}

.px-ms-check {
  margin: 0 0 18px;
  padding: 0;
  list-style: none;
  text-align: left;
}

.px-ms-check li {
  position: relative;
  padding: 7px 0 7px 26px;
  font-size: 14px;
  line-height: 1.55;
  color: #2c4a6e;
  border-bottom: 1px solid rgba(3, 31, 66, .07);
}

.px-ms-check li:last-child { border-bottom: 0; }

.px-ms-check li::before {
  content: '';
  position: absolute;
  left: 6px;
  top: 15px;
  width: 7px;
  height: 7px;
  border-radius: 50%;
  background: var(--px-accent);
}

.px-fbk {
  margin: 18px 0 4px;
  padding: 16px 16px 12px;
  border-radius: 13px;
  background: #f7f9fc;
  border: 1px solid rgba(3, 31, 66, .08);
  text-align: left;
}

.px-fbk-label {
  display: block;
  margin-bottom: 8px;
  font-size: 13px;
  font-weight: 700;
  color: #031f42;
}

.px-fbk-in {
  width: 100%;
  padding: 10px 12px;
  border: 1.5px solid var(--px-line);
  border-radius: 10px;
  background: #fff;
  font: inherit;
  font-size: 14px;
  color: #031f42;
}

.px-fbk-msg { resize: vertical; line-height: 1.55; }

.px-fbk-in:focus { outline: none; border-color: var(--px-accent); }

.px-fbk-row {
  display: flex;
  gap: 8px;
  margin-top: 8px;
}

.px-fbk-row .px-fbk-in { flex: 1 1 0; min-width: 0; }

.px-fbk-status {
  margin: 8px 0 0;
  min-height: 17px;
  font-size: 12.5px;
  font-weight: 600;
  color: #5b6d84;
}

/* The last two from the same recovery, verbatim: their outer selectors
   already existed, so the sweep above did not pick them up. */
.px-ms-prof em,
.px-ms-mate em {
  display: block;
  font-style: normal;
  font-size: 12px;
  font-weight: 800;
  text-transform: uppercase;
  letter-spacing: .05em;
  color: #5b6d84;
  margin-bottom: 3px;
}
.px-fbk-msg:focus,
.px-fbk-in:focus { outline: none; border-color: var(--px-accent); }

/* iOS zooms the whole page when a text field under 16px takes focus, and the
   page does not zoom back. The rules recovered above carry the sizes they had
   before 83e32c55 deleted them: .px-ms-input at 15.5px and .px-fbk-in /
   .px-fbk-msg at 14px. Restoring them verbatim was right for layout and wrong
   here, because .px-ms-input is the box a student types a timed 7-minute email
   into, and having the viewport jump on the first keystroke is the worst moment
   for it to happen.
   16px is the threshold, not a preference. Held only where the keyboard opens. */
.px-ms-input,
.px-fbk-in,
.px-fbk-msg {
  font-size: 16px;
}

/* The close button is absolute inside a card that scrolls, so on a short phone
   it scrolls out of the viewport with the card's top. The backdrop and the
   in-card button still dismiss, so this was never a trap, but the X is the
   control people reach for first. Sticky keeps it in reach without changing
   where it sits on a tall screen. */
.px-modal-x {
  position: sticky;
  float: right;
  top: 10px;
  margin-right: -2px;
}

/* The drill HUD's two controls are 36x36 and 38x38, both under the 40px floor,
   and they are the only two controls on that screen: leaving the level, and
   full screen. The speaking tutor already grew its equivalents to a 44px touch
   area; this was never applied here. Visual size unchanged, hit area grown
   underneath. */
.px-hud-back,
.px-hud-fs {
  position: relative;
}
.px-hud-back::after,
.px-hud-fs::after {
  content: '';
  position: absolute;
  top: 50%;
  left: 50%;
  width: 44px;
  height: 44px;
  transform: translate(-50%, -50%);
}
