/* ================================================================
   THE PLACEMENT PAGE — one column, three blocks, no walls of text.

   Seth, 2026-08-23: the placement diagnostic had "information overload of
   text" and an "archaic less modern design". What changed here is the
   SHAPE, not the information: the page is a hero, one action card and one
   results card on a narrower measure; every explanatory sentence that used
   to sit under a control is either a chip (`.placement-chip`) or gone; and
   the numbers carry the weight instead of the prose around them.

   The results card's headline is the SAME dial as the Practice tab's idle
   screen (styles/practice/readiness.css) showing the SAME figure from
   practice/readiness.js — see the note at the top of
   practice/placement-results.js for why that stopped being two numbers.
   ================================================================ */

.placement-page { max-width: 880px; padding-bottom: 64px; padding-top: 44px; }
.diagnostic-eyebrow { color: var(--accent-text); font-size: 11px; font-weight: 700; letter-spacing: .1em; text-transform: uppercase; }
/* The title carries the page. It used to be the default h1 with a 15px
   subtitle three lines long underneath; the subtitle is one line now, so the
   heading is what the eye lands on. */
.placement-page h1 { font-size: 34px; letter-spacing: -0.02em; line-height: 1.15; margin: 8px 0 10px; }
.placement-page .subtitle { font-size: 15px; margin-bottom: 26px; max-width: 60ch; }

.diagnostic-card { border-radius: 14px; margin-top: 0; padding: 22px 24px; }
/* The status line is the card's own heading — what state your placement is
   in — so it reads as one, with a dot rather than a label saying "status". */
.diagnostic-status { align-items: baseline; display: flex; font-size: 15px; font-weight: 600; gap: 9px; margin-bottom: 14px; }
.diagnostic-status::before {
  background: var(--info);
  border-radius: 50%;
  content: "";
  flex: none;
  height: 7px;
  /* Optically centred on the first line of a 15px/1.4 label rather than sat
     on its baseline, which is where a flex `baseline` alignment puts a box. */
  transform: translateY(-2px);
  width: 7px;
}

/* ---- Chips ------------------------------------------------------------
   The unit the page's facts are in. A chip is two or three words: a date, a
   count, a duration. Four of them are read in the time one sentence takes to
   parse, which is the whole reason the sentences went. Used by the length
   line under the CTA and by the results card's meta row. */
.placement-chip {
  align-items: center;
  background: rgb(var(--tint-rgb) / calc(0.045 * var(--tint-k)));
  border: 1px solid var(--border);
  border-radius: 999px;
  color: var(--muted);
  display: inline-flex;
  font-size: 12px;
  font-variant-numeric: tabular-nums;
  gap: 6px;
  padding: 4px 11px;
  white-space: nowrap;
}
.placement-chip strong { color: var(--text); font-weight: 600; }

/* The one rule of the test, stated before it starts — the same limit on
   every question, which is what makes the probes comparable. While the
   test is running the countdown itself says it, so this line goes away.
   Written by diagnostic-page.js::renderLength as chips, so this is a chip
   ROW: no font of its own, only the flow. */
.diagnostic-timing { display: flex; flex-wrap: wrap; gap: 8px; margin: 0 0 4px; }

/* ---- How far through the test you are ---------------------------------
   One continuous track, same reading as the scaffolding bar on Practice.
   It sits at the BOTTOM of the card and survives .diagnostic-running (see
   below) because that is the only state where it says anything. */
.placement-progress { align-items: center; display: flex; gap: 12px; margin-top: 16px; }
.placement-progress.hidden { display: none; }
.placement-progress-track {
  background: rgb(var(--well-rgb) / calc(0.22 * var(--well-k)));
  border-radius: 999px;
  flex: 1 1 auto;
  height: 8px;
  overflow: hidden;
  position: relative;
}
.placement-progress-fill {
  background: var(--info);
  border-radius: 999px;
  display: block;
  height: 100%;
  transition: width 0.25s ease;
}
/* Earliest possible finish. `overflow: hidden` on the track would clip a
   zero-width child at 100%, so the tick is inset by its own width. */
.placement-progress-tick {
  background: rgb(var(--info-rgb) / 0.75);
  bottom: 0;
  margin-left: -1px;
  position: absolute;
  top: 0;
  width: 2px;
}
.placement-progress-tick.hidden { display: none; }
.placement-progress-count {
  color: var(--muted);
  flex: none;
  font-size: 12px;
  font-variant-numeric: tabular-nums;
}
/* ---- The set-up: one question at a time --------------------------------
   practice/placement-wizard.js. Seth, 2026-09-24: the card was every control
   at once — status dot, two chip rows, a native <select>, three pills and a
   plan row — and "looks ugly as hell". Now it is a stepper: a progress rail,
   ONE question in a large type, big tap targets for the answers, and Back /
   Next in the same place on every step. Selection is the placement's own
   blue (--info, the status dot and progress bar), not the brand red: red on
   a choice you just made reads as an error.

   While the set-up is open the card's run-time furniture (status line,
   length chips, action row) steps aside — it describes a run that does not
   exist yet. */
.placement-hero { align-items: flex-start; display: flex; gap: 16px; justify-content: space-between; }
.placement-skip-btn {
  background: none;
  border: 0;
  border-radius: 8px;
  color: var(--muted);
  cursor: pointer;
  flex: none;
  font: inherit;
  font-size: 13px;
  font-weight: 600;
  margin-top: 4px;
  padding: 6px 10px;
}
.placement-skip-btn:hover { background: rgb(var(--tint-rgb) / calc(0.06 * var(--tint-k))); color: var(--text); }
.placement-skip-btn.hidden { display: none; }

.diagnostic-card.is-planning { padding: 26px 28px 22px; }
.diagnostic-card.is-planning .diagnostic-status,
.diagnostic-card.is-planning .diagnostic-timing,
.diagnostic-card.is-planning .diagnostic-actions { display: none; }

.placement-plan.hidden { display: none; }

/* The rail: one segment per step, filled up to where you are. Done steps are
   buttons (jump back); the current and later ones are not. */
.placement-steps { display: flex; gap: 10px; list-style: none; margin: 0 0 26px; padding: 0; }
.placement-steps-item { flex: 1 1 0; min-width: 0; }
.placement-steps-btn {
  align-items: center;
  background: none;
  border: 0;
  border-bottom: 3px solid rgb(var(--tint-rgb) / calc(0.1 * var(--tint-k)));
  color: var(--muted-dim);
  display: flex;
  font: inherit;
  font-size: 12px;
  font-weight: 600;
  gap: 8px;
  letter-spacing: .04em;
  padding: 0 0 10px;
  text-align: left;
  text-transform: uppercase;
  width: 100%;
}
.placement-steps-btn:disabled { cursor: default; }
.placement-steps-item.is-done .placement-steps-btn { border-bottom-color: rgb(var(--info-rgb) / 0.55); color: var(--muted); cursor: pointer; }
.placement-steps-item.is-done .placement-steps-btn:hover { color: var(--text); }
.placement-steps-item.is-current .placement-steps-btn { border-bottom-color: var(--info); color: var(--text); }
.placement-steps-num {
  align-items: center;
  border: 1px solid var(--border);
  border-radius: 50%;
  display: inline-flex;
  flex: none;
  font-size: 11px;
  height: 22px;
  justify-content: center;
  letter-spacing: 0;
  width: 22px;
}
.placement-steps-item.is-current .placement-steps-num { background: var(--info); border-color: var(--info); color: var(--bg); }
.placement-steps-item.is-done .placement-steps-num { background: rgb(var(--info-rgb) / 0.16); border-color: transparent; color: var(--info); }
.placement-steps-label { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }

.placement-step.hidden { display: none; }
.placement-step { animation: placement-step-in .18s ease-out; }
@keyframes placement-step-in { from { opacity: 0; transform: translateY(4px); } to { opacity: 1; transform: none; } }
@media (prefers-reduced-motion: reduce) { .placement-step { animation: none; } }
.placement-step-q { color: var(--text); font-size: 22px; font-weight: 700; letter-spacing: -0.01em; line-height: 1.25; margin: 0 0 6px; outline: none; }
.placement-step-hint { color: var(--muted); font-size: 14px; line-height: 1.5; margin: 0 0 20px; max-width: 62ch; }
.placement-step-loading { color: var(--muted); font-size: 13px; margin: 0; }

/* Shared tile language: areas, the rapid-check alternative, experience and
   length are all "a big button you pick". */
.placement-focus-tile,
.placement-focus-alt-btn,
.placement-step .self-report-btn,
.placement-plan-option {
  background: rgb(var(--well-rgb) / calc(0.14 * var(--well-k)));
  border: 1px solid var(--border);
  border-radius: 12px;
  color: var(--text);
  cursor: pointer;
  font: inherit;
  text-align: left;
  transition: border-color .12s ease, background .12s ease, opacity .12s ease;
}
.placement-focus-tile:hover,
.placement-focus-alt-btn:hover,
.placement-step .self-report-btn:hover,
.placement-plan-option:not(.is-static):hover { border-color: rgb(var(--info-rgb) / 0.6); }
.placement-focus-tile.is-selected,
.placement-focus-alt-btn.is-selected,
.placement-step .self-report-btn.active,
.placement-plan-option.is-selected {
  background: rgb(var(--info-rgb) / 0.08);
  border-color: var(--info);
  box-shadow: inset 0 0 0 1px var(--info);
}
.placement-focus-tile:focus-visible,
.placement-focus-alt-btn:focus-visible,
.placement-plan-option:focus-visible { outline: 2px solid var(--info); outline-offset: 2px; }

.placement-focus-grid { display: grid; gap: 10px; grid-template-columns: repeat(auto-fill, minmax(240px, 1fr)); }
.placement-focus-tile,
.placement-focus-alt-btn { align-items: flex-start; display: flex; gap: 12px; padding: 14px 16px; }
.placement-focus-tile.is-muted { opacity: .45; }
.placement-focus-check {
  border: 1.5px solid var(--muted-dim);
  border-radius: 6px;
  flex: none;
  height: 18px;
  margin-top: 1px;
  position: relative;
  width: 18px;
}
.is-selected > .placement-focus-check { background: var(--info); border-color: var(--info); }
.is-selected > .placement-focus-check::after {
  border: solid var(--bg);
  border-width: 0 2px 2px 0;
  content: "";
  height: 9px;
  left: 5px;
  position: absolute;
  top: 1.5px;
  transform: rotate(45deg);
  width: 4px;
}
.placement-focus-body { display: flex; flex-direction: column; gap: 3px; min-width: 0; }
.placement-focus-name { font-size: 15px; font-weight: 650; }
.placement-focus-blurb { color: var(--muted); font-size: 12.5px; line-height: 1.45; }
.placement-focus-meta { color: var(--muted-dim); font-size: 11.5px; font-variant-numeric: tabular-nums; margin-top: 2px; }

.placement-focus-tools { align-items: center; display: flex; gap: 12px; justify-content: space-between; margin-top: 12px; }
.placement-textbtn { background: none; border: 0; color: var(--info); cursor: pointer; font: inherit; font-size: 13px; font-weight: 600; padding: 2px 0; }
.placement-textbtn:hover { text-decoration: underline; }
.placement-textbtn:disabled { color: var(--muted-dim); cursor: default; text-decoration: none; }
.placement-focus-count { color: var(--muted); font-size: 12.5px; font-variant-numeric: tabular-nums; }
.placement-focus-count.is-warning { color: rgb(var(--warn-rgb)); }

.placement-focus-alt { border-top: 1px solid var(--border); margin-top: 18px; padding-top: 18px; }
.placement-focus-alt-btn { background: none; border-style: dashed; width: 100%; }
.placement-focus-alt-btn.is-selected { border-style: solid; }
.placement-focus-alt-btn .placement-focus-name { font-size: 14px; }

/* Experience: the three self-report buttons, as tiles. Their label and its
   infotip dot are the old inline row's heading — the step's question says it
   now. adaptive.js still owns the buttons and the note. */
.placement-step .self-report-row { display: block; margin: 0; }
.placement-step .self-report-row.hidden { display: none; }
.placement-step .self-report-label,
.placement-step .self-report-label + .dd-info { display: none; }
.placement-step .self-report-options { display: grid; gap: 10px; grid-template-columns: repeat(auto-fit, minmax(170px, 1fr)); }
.placement-step .self-report-btn { font-size: 15px; font-weight: 650; padding: 18px 16px; }
.placement-step .self-report-note { display: block; margin-top: 12px; }
.placement-step .self-report-note.hidden { display: none; }

/* Length: one tile per tier, the budget large, what it buys underneath. */
.placement-plan-options { display: grid; gap: 10px; grid-template-columns: repeat(auto-fit, minmax(180px, 1fr)); }
.placement-plan-option { display: flex; flex-direction: column; gap: 3px; padding: 14px 16px 16px; }
.placement-plan-option.is-static { cursor: default; }
.placement-plan-option.is-loading { cursor: default; opacity: .55; }
.placement-plan-tier { color: var(--muted); font-size: 11px; font-weight: 700; letter-spacing: .07em; text-transform: uppercase; }
.placement-plan-option.is-selected .placement-plan-tier { color: var(--info); }
.placement-plan-hours { font-size: 26px; font-variant-numeric: tabular-nums; font-weight: 700; letter-spacing: -0.02em; line-height: 1.2; }
.placement-plan-est { color: var(--muted); font-size: 12.5px; font-variant-numeric: tabular-nums; }
.placement-plan-meter { background: rgb(var(--well-rgb) / calc(0.22 * var(--well-k))); border-radius: 999px; display: block; height: 4px; margin-top: 10px; overflow: hidden; }
.placement-plan-meter i { background: var(--info); border-radius: 999px; display: block; height: 100%; }
.placement-plan-cov { color: var(--muted-dim); font-size: 11.5px; margin-top: 2px; }
.placement-plan-note { color: var(--muted); font-size: 12.5px; margin: 12px 0 0; }

/* Back on the left, forward on the right, on every step — the forward
   control never moves, so the learner's pointer does not have to either. */
.placement-wizard-nav {
  align-items: center;
  border-top: 1px solid var(--border);
  display: flex;
  gap: 12px;
  justify-content: space-between;
  margin-top: 26px;
  padding-top: 18px;
}
.placement-back-btn {
  background: none;
  border: 0;
  border-radius: 8px;
  color: var(--muted);
  cursor: pointer;
  font: inherit;
  font-size: 14px;
  font-weight: 600;
  flex: none;
  padding: 10px 12px;
  margin-left: -12px;
  white-space: nowrap;
}
.placement-back-btn:hover { background: rgb(var(--tint-rgb) / calc(0.06 * var(--tint-k))); color: var(--text); }
.placement-back-btn.is-invisible { visibility: hidden; }
.placement-forward-btn,
.placement-wizard-nav .placement-start-btn {
  background: var(--info);
  border: 1px solid var(--info);
  border-radius: 10px;
  color: var(--bg);
  cursor: pointer;
  font: inherit;
  font-size: 14px;
  font-weight: 700;
  padding: 11px 22px;
}
.placement-forward-btn:hover:not(:disabled),
.placement-wizard-nav .placement-start-btn:hover:not(:disabled) { background: rgb(var(--info-rgb) / 0.85); border-color: rgb(var(--info-rgb) / 0.85); }
.placement-forward-btn:disabled { cursor: not-allowed; opacity: .4; }
.placement-forward-btn.hidden,
.placement-wizard-start.hidden { display: none; }

@media (max-width: 620px) {
  /* Skip rides the eyebrow line so the blurb gets the full width. */
  .placement-hero { position: relative; }
  .placement-skip-btn { position: absolute; right: 0; top: -6px; }
  .diagnostic-card.is-planning { padding: 20px 18px 18px; }
  .placement-step-q { font-size: 19px; }
  .placement-steps-label { display: none; }
  .placement-focus-grid { grid-template-columns: 1fr; }
}

/* Per-concept report inside the results card (placement-results.js renderKcs):
   the same row grid as the area bars, grouped by verdict. */
.placement-kcs { border-top: 1px solid var(--border); margin-top: 22px; padding-top: 18px; }
.placement-kc-group + .placement-kc-group { margin-top: 16px; }
.placement-kc-group--unknown .placement-area-bar i { background: var(--danger, #d9534f); }
.placement-kc-group--uncertain .placement-area-bar i { opacity: .6; }
.placement-kc-note { color: var(--muted); font-size: 12px; margin: 12px 0 0; }
.placement-kc-note--edges { color: var(--text); }

.diagnostic-actions { align-items: center; display: flex; flex-wrap: wrap; gap: 12px; margin-top: 18px; }
/* The ⓘ belongs TO the button, not under it. infotips.js forces place="after"
   for a <button> anchor and inserts the dot into the anchor's parentNode, so
   the only way to keep the two together is to give them a parent of their own
   — otherwise the dot is a sibling flex item and wraps onto its own line.
   `align-items: center` is what puts it beside the label rather than on the
   button's baseline. */
.placement-cta { align-items: center; display: inline-flex; gap: 8px; }
/* Re-assert .hidden. Same specificity (0,1,0) as the rule above, and this file
   loads AFTER components.css, so the global `.hidden { display: none }` loses
   the tie — the wrapper stayed a zero-width flex item and its `gap` shunted the
   next button 12px off the card edge. Same trap styles/practice/README.md
   records for #practice-submit-area. */
.placement-cta.hidden { display: none; }
/* `.dd-info` ships `margin: 0 0 0 6px` for the anchors it is usually squeezed
   next to; here the wrapper's gap already does that job and the two stack to
   14px, which reads as a stray dot rather than part of the control. */
.placement-cta > .dd-info { flex: none; margin: 0; position: relative; top: 0; }

/* THE FLASH THE ACCOUNT MENU LEAVES BEHIND (Seth, 2026-08-31).
   "Retake the placement test" in the account menu ROUTES here — it must not
   start the test, because a retake clears the probe log — so the learner lands
   on the Learner Home with the card scrolled up and has to find the button that
   actually does it. Two amber pulses on the control account-menu.js just
   focused: enough to say "this one", gone before it becomes decoration.

   🔴 A RING, NOT A BORDER. `box-shadow` paints outside the box without
   participating in layout; animating `border-width` or `padding` here would
   reflow .diagnostic-actions and shunt "Load next placement question" sideways
   twice a second. `outline` is spoken for — :focus-visible draws it, and
   account-menu.js focuses this same element. */
@keyframes placement-cta-flash {
  0%   { box-shadow: 0 0 0 0 rgb(var(--warn-rgb) / 0.55); }
  70%  { box-shadow: 0 0 0 8px rgb(var(--warn-rgb) / 0); }
  100% { box-shadow: 0 0 0 0 rgb(var(--warn-rgb) / 0); }
}
.placement-cta-flash { animation: placement-cta-flash 1.1s ease-out 2; }
/* The pointer is still a real signal for someone who cannot take the motion:
   a steady ring for as long as the class is on the element says the same thing
   without pulsing. */
@media (prefers-reduced-motion: reduce) {
  .placement-cta-flash {
    animation: none;
    box-shadow: 0 0 0 3px rgb(var(--warn-rgb) / 0.45);
  }
}

/* ---- Results: what the placement actually measured ---------------------
   Written by practice/placement-results.js. Two jobs: make the headline
   readable at a glance, and make an UNPROBED area visibly weaker evidence
   than a probed one — a learner who cannot tell them apart will plan around
   a number the test never earned. */
.diagnostic-results { border-radius: 14px; margin-top: 16px; padding: 22px 24px; }
.diagnostic-results h2 { font-size: 13px; font-weight: 700; letter-spacing: .08em; margin: 0 0 12px; text-transform: uppercase; color: var(--muted); }
.diagnostic-results p { color: var(--muted); margin: 0; }
/* Scoped, for the same reason the caption below is: this is a <p>, and the
   card's own `.diagnostic-results p` rule at (0,1,1) beats a bare class — the
   chip row would keep `margin: 0` and sit flush against the dial. */
.diagnostic-results .placement-results-meta { display: flex; flex-wrap: wrap; gap: 8px; margin: 0 0 18px; }
.placement-results-empty { color: var(--muted); font-size: 13px; margin: 0; }

/* The headline block: the shared dial, then what it means. NO panel of its
   own — it sits directly on the card, which is also what lets the dial's
   centre hole (styles/practice/readiness.css draws it in `--surface`) match
   the surface behind it instead of reading as a second, dimmer ring. */
.placement-overall {
  align-items: center;
  display: flex;
  flex-wrap: wrap;
  gap: 22px;
}
.placement-overall-say { flex: 1 1 240px; }
/* Scoped through .diagnostic-results, not marked important: the card ships
   `.diagnostic-results p { color: var(--muted); margin: 0 }` at (0,1,1), so a
   bare class here loses the cascade and the caption renders muted with no
   spacing. Specificity is the fix; !important would win the same argument and
   then win every future one too. */
.diagnostic-results .placement-overall-caption { color: var(--text); font-size: 16px; font-weight: 600; line-height: 1.3; }
.diagnostic-results .placement-overall-detail { font-size: 13px; margin: 6px 0 0; }
.diagnostic-results .placement-overall-caveat { color: var(--muted-dim); font-size: 12.5px; line-height: 1.5; margin: 8px 0 0; }

.placement-areas { border-top: 1px solid var(--border); margin-top: 22px; padding-top: 18px; }
.placement-areas-head { color: var(--muted); font-size: 11px; font-weight: 700; letter-spacing: .08em; margin-bottom: 8px; text-transform: uppercase; }
/* Fixed track column so every bar starts and ends on the same two pixels —
   rows that each size their own bar cannot be compared by eye, which is the
   entire point of the list. */
.placement-area {
  align-items: center;
  border-radius: 8px;
  display: grid;
  gap: 12px;
  grid-template-columns: minmax(120px, 1.4fr) minmax(80px, 3fr) 44px 40px 74px;
  padding: 7px 8px;
  margin: 0 -8px;
}
.placement-area:hover { background: rgb(var(--tint-rgb) / calc(0.04 * var(--tint-k))); }
.placement-area-name { color: var(--text); font-size: 13px; }
.placement-area-bar { background: rgb(var(--well-rgb) / calc(0.22 * var(--well-k))); border-radius: 999px; height: 6px; overflow: hidden; }
.placement-area-bar i { background: var(--info); border-radius: 999px; display: block; height: 100%; }
.placement-area-pct { color: var(--text); font-size: 13px; font-variant-numeric: tabular-nums; font-weight: 700; text-align: right; }
.placement-area-conf { color: var(--muted-dim); font-size: 11.5px; font-variant-numeric: tabular-nums; text-align: right; }
.placement-area-probes { color: var(--muted-dim); font-size: 11.5px; text-align: right; }
/* An unprobed area is a prior wearing a percentage. Everything about the row
   is dialled down, and the bar is hollow rather than filled, so it never
   reads as a measurement taken. */
.placement-area--unprobed .placement-area-name,
.placement-area--unprobed .placement-area-pct { color: var(--muted); }
.placement-area--unprobed .placement-area-bar i { background: rgb(var(--info-rgb) / 0.3); }
.placement-area--unprobed .placement-area-probes { font-style: italic; }

@media (max-width: 620px) {
  .placement-page h1 { font-size: 27px; }
  .placement-area { grid-template-columns: 1fr 44px 40px; grid-template-areas: "name pct conf" "bar bar probes"; }
  .placement-area-name { grid-area: name; }
  .placement-area-bar { grid-area: bar; }
  .placement-area-pct { grid-area: pct; }
  .placement-area-conf { grid-area: conf; }
  .placement-area-probes { grid-area: probes; text-align: left; }
}
/* ---- A PROBE ON SCREEN IS THE PRACTICE SCREEN --------------------------

   🔴 EVERYTHING THIS PAGE OWNS IS HIDDEN WHILE A PROBE IS UP. Seth,
   2026-08-23: the placement should be "indistinguishable user experience wise
   from the practice ... aside from, obviously, serving only the types of
   problems that are for the testing."

   It used to keep a header card above the workspace — the status line
   ("Placement test in progress · 3 of at most 14 questions answered") and the
   progress bar — and shave 92px off the editor to make room. That card was the
   difference: the same question, in a shorter frame, under a banner announcing
   which mode you were in. The whole overview goes now, and the workspace gets
   the same height it has on the Practice tab.

   NOTHING IS LOST, it moved. The progress the bar was carrying is in the notch
   tab's tooltip and its screen-reader phase, written by `notch-menu.js` from
   `DiagnosticPage.progressLabel()` — the same place a practice session says
   which phase it is in. That is the one surface Practice already has for this,
   which is what makes it the right one.

   `.diagnostic-running` is still the class, still written by
   diagnostic-page.js::syncWorkspace, and it still means "a probe is hosted
   here" rather than "a placement exists" — the overview has to come back the
   moment the learner is between probes or looking at their results.

   🔴 IT IS WRITTEN ON #page-placement. The Placement test was merged into the
   Learner Home on 2026-08-24 and split back onto a page of its own on
   2026-09-01 (Seth: keep the placement interface "separate ... it only gets
   displayed whenever you click on the drop-down one and you go to it
   specifically"), taking the overview, the results card and the workspace host
   with it. The selector followed both times; the class never changed.

   🔴 THE `.placement-taken` HIDE IS DELETED, not moved (it lived here from
   2026-08-31). It took a COMPLETED placement's card off the Learner Home, which
   is a rule about a card parked on the daily screen — and the card is not on
   that screen in any state any more. Re-pointed at this page it would hide the
   only content of a page the learner opened on purpose, which is the "nothing
   to click" dead end `DiagnosticPage.reveal()` existed to patch. Both are gone
   together; practice/watch.py asserts neither comes back. */
#page-placement.diagnostic-running #diagnostic-overview { display: none; }
#page-placement.diagnostic-running { padding: 0; }
/* 🔴 `#learner-areas` IS NOT LISTED HERE ANY MORE — it is on the Learner Home,
   a different page, and a probe hosted here hides that page wholesale. The rule
   below is what takes the dial and the bars off the screen for the duration of
   a probe: the whole `.session-setup` travels with the workspace.

   .practice-mode-intro was deleted on 2026-08-23; only .session-setup still
   needs hiding when the workspace is hosted here. */
.diagnostic-workspace-host .session-setup { display: none; }
/* 🔴 THE SAME ARITHMETIC AS THE PRACTICE TAB, and it has to stay that way.
   `#page-practice` is `calc(100vh - var(--dd-topbar-h))`; this was that minus
   92px for the header card, and with the card gone the 92px is a short editor
   for no reason. The 56px it was written with before that was the topbar's
   height by hand — the token is the only place that number lives
   (styles/layout.css). */
.diagnostic-workspace-host .practice-container { height: calc(100vh - var(--dd-topbar-h)); }
.diagnostic-workspace-host .practice-split { display: flex; }

/* 🔴 THE SPECIFICITY FIGHT THE MERGE CREATED, KEPT ON PURPOSE AFTER THE SPLIT.
   `#page-practice.session-idle` (styles/practice/timer.css) hides
   `.practice-split` and shows `.session-setup`. While the host lived on
   #page-practice (2026-08-24 to 2026-09-01) the hosted workspace was a
   descendant of that page, `#page-practice.session-idle .practice-split` (1,2,0)
   beat `.diagnostic-workspace-host .practice-split` (0,2,0), and a probe
   rendered as a blank idle screen. Two IDs settled it outright.

   The host is on #page-placement again, so the workspace is NOT a descendant of
   #page-practice while it is hosted and the collision cannot arise. These two
   rules are kept anyway: they are exactly the pair that has to exist if the host
   ever moves back under that page, they cost nothing while they match nothing,
   and deleting them is how the blank-probe bug returns silently.

   The learner is not "idle" during a probe, so nothing here is a lie — but the
   class is timer.js's to write and a placement is not a session, so this is
   fixed where the conflict is rather than by teaching timer.js about placements. */
#page-practice #diagnostic-workspace-host .practice-split { display: flex; }
#page-practice #diagnostic-workspace-host .session-setup { display: none; }
/* And the same pair for where the host actually is. `#page-placement` carries
   no `.session-idle`, so nothing is being beaten here — this is the positive
   statement that a hosted workspace shows its editor. */
#page-placement #diagnostic-workspace-host .practice-split { display: flex; }
#page-placement #diagnostic-workspace-host .session-setup { display: none; }
/* 🪦 `#page-practice #diagnostic-workspace-host .stage-ladder { display: block }`
   lived here for a few hours on 2026-08-24 and was WRONG twice over. Nothing
   ever needed it — the host shows placement probes, which carry no `ladder_kc`,
   so practice/stage-ladder.js keeps `.hidden` on the element throughout. And at
   (2,1,0) it outranked BOTH of the gates that are supposed to hide the ladder —
   `.stage-ladder.hidden` and `body.dd-basic-mode .stage-ladder`, each (0,2,0) —
   so it forced an empty advanced-only widget onto a basic-mode probe, with the
   wrong `display` keyword on top (the element's own rule is `flex`). Do not
   re-add it to "match" the two rules above: those exist to beat
   `#page-practice.session-idle`, which is on the page during a probe and hides
   things this host must show. The ladder is not one of them. */

/* Per-probe countdown, written by practice/placement-timer.js into the NOTCH
   TAB (Seth, 2026-08-23 — it used to be a pill beside the concept heading in
   .question-number-row, and before that in the deleted cold-start badge).

   🔴 THIS RULE DECLARES NOTHING, ON PURPOSE. `#placement-timer` carries
   `.practice-notch-clock` in the markup and takes every visual property from
   it (notch-menu.css) — Seth, 2026-08-23: the placement should be
   "indistinguishable user experience wise from the practice ... in terms of
   the user experience with the top bar for the timer and all that, it should
   just be exactly the same."

   It has been three things in one day: a bordered pill beside the concept
   heading, then the same pill on the notch tab (a capsule inside a bordered
   15px tab), then a copy of the notch clock in `--info` to mark it as the
   placement's. The tint was the last distinction left, and a distinction is
   exactly what he asked to remove. `--low` is `.practice-notch-clock--low`
   now, so the last 30 seconds are the same red in the same slot.

   The class stays because it is the ANCHOR — the id JS writes to, the hook
   `notch-menu.js` reads to decide which clock is on the tab, and what
   `watch.py` asserts. Do not give it colour, size or spacing again: that is
   how the two clocks drift back apart. */
.placement-timer.hidden { display: none; }


/* ================================================================
   WHAT YOU ARE LEARNING — the area bars on the Learner Home
   ================================================================

   The rows themselves are `.placement-area*`, styled above and unchanged: this
   is only the block that hosts them now. It used to sit inside
   #diagnostic-results, which shows only after a placement is COMPLETE — so the
   one screen a learner opens every day named nothing at all and reported "0%
   ready". Seth, 2026-08-24: "it should display the information about einops,
   numpy, and einsum to be learned".

   /api/practice/diagnostic/status returns all three areas from the first call
   on a brand-new account (theta = the prior, probes = 0), so there is always
   something honest to draw here — the rows carry their own "not probed" marking
   and their ± band, which is what keeps an unmeasured area from reading as a
   measurement. */
.learner-areas {
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: 14px;
  margin: 22px auto 0;
  max-width: 560px;
  padding: 18px 20px 16px;
  text-align: left;
  width: 100%;
}

.learner-areas-head {
  color: var(--white);
  font-size: 15px;
  font-weight: 700;
  margin: 0 0 12px;
}

.learner-areas-note {
  color: var(--muted-dim);
  font-size: 12px;
  line-height: 1.45;
  margin: 12px 0 0;
}

/* Nothing came back — a network failure, or a signed-out visitor whose status
   call has not answered yet. An empty bordered box that says nothing is worse
   than no box, so the whole block goes. Written by
   practice/placement-results.js renderAreas. */
.learner-areas.is-empty {
  display: none;
}

/* ---- WEEKLY ACTIVITY BARS ----------------------------------------------
   The problems-answered-per-day chart under the area list (Seth, 2026-09-01),
   drawn by practice/activity-chart.js from /api/practice/activity-week. Same
   card shell as .learner-areas, and the same .is-empty contract: signed out or
   fetch failed → the whole box goes. Bars scale against the week's own best
   day — the JS writes each .activity-day-bar height as a percentage of it. */
.learner-activity {
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: 14px;
  margin: 22px auto 0;
  max-width: 560px;
  padding: 18px 20px 16px;
  text-align: left;
  width: 100%;
}
.learner-activity.is-empty { display: none; }
.learner-activity-head {
  color: var(--white);
  font-size: 15px;
  font-weight: 700;
  margin: 0 0 12px;
}
.activity-week-chart {
  align-items: stretch;
  display: flex;
  gap: 10px;
}
.activity-day {
  display: flex;
  flex: 1;
  flex-direction: column;
  gap: 4px;
  min-width: 0;
  text-align: center;
}
.activity-day-count {
  color: var(--muted);
  font-size: 11.5px;
  font-variant-numeric: tabular-nums;
  /* Reserve the line even when empty so all seven tracks stay level. */
  min-height: 15px;
}
/* Fixed-height track with the bar growing up from the floor — same "every bar
   shares the same two pixels" reasoning as .placement-area-bar, rotated
   vertical. The hairline floor keeps a zero day visibly a day, not a gap. */
.activity-day-track {
  align-items: flex-end;
  background: rgb(var(--well-rgb) / calc(0.12 * var(--well-k)));
  border-bottom: 1px solid var(--border);
  border-radius: 6px 6px 0 0;
  display: flex;
  height: 88px;
}
.activity-day-bar {
  background: var(--info);
  border-radius: 4px 4px 0 0;
  display: block;
  width: 100%;
}
.activity-day-letter {
  color: var(--muted-dim);
  font-size: 11px;
  font-weight: 700;
}
/* Today reads a shade louder than the finished days — it is the bar that is
   still being earned. */
.activity-day.is-today .activity-day-letter,
.activity-day.is-today .activity-day-count { color: var(--text); }
