/* Six Strategic Views — learner interface.
 *
 * A reading-heavy screen: the guide writes several sentences per turn and the
 * final evaluation is a page of prose. Measure, line height and contrast are
 * therefore doing more work here than layout is.
 *
 * Two voices, told apart by type rather than by bubbles. The guide, the case
 * and the headings are set in Newsreader, a serif drawn for reading on screen,
 * so the coaching reads like a note from a mentor rather than a system
 * message. The learner's own writing, and every control, is in Geist, a
 * neutral sans. Micro-labels are Geist Mono. All three are self-hosted from
 * fonts/ (SIL Open Font License, texts beside them) rather than linked from a
 * font CDN, so a learner's browser asks nobody but this server for anything.
 *
 * The recurring mark is the lens: one side of a polygon per view, filled in
 * as the session moves through them. It is drawn by lens.py on the server and
 * by app.js in the browser; its colours are the --lens-* tokens below.
 *
 * Palette is University of Michigan blue/maize on a warm off-white. Every
 * colour that carries text, a focus ring, or a control boundary is a token
 * (--accent and --accent-ink flip between navy-on-white and maize-on-navy per
 * colour scheme) so contrast holds in both modes rather than being tuned once
 * for light mode and hoped for in dark mode. The ratios quoted in this file
 * were computed with the WCAG 2 formula, not estimated.
 */

@font-face {
  font-family: "Newsreader";
  src: url("fonts/newsreader-latin-opsz-normal.f94a697b1650.woff2") format("woff2");
  font-style: normal;
  font-weight: 200 800;
  font-display: swap;
}
@font-face {
  font-family: "Newsreader";
  src: url("fonts/newsreader-latin-opsz-italic.e97067eb043b.woff2") format("woff2");
  font-style: italic;
  font-weight: 200 800;
  font-display: swap;
}
@font-face {
  font-family: "Geist";
  src: url("fonts/geist-latin-wght-normal.a147f99cd533.woff2") format("woff2");
  font-style: normal;
  font-weight: 100 900;
  font-display: swap;
}
@font-face {
  font-family: "Geist Mono";
  src: url("fonts/geist-mono-latin-wght-normal.a15f9d568fb0.woff2") format("woff2");
  font-style: normal;
  font-weight: 100 900;
  font-display: swap;
}

:root {
  /* Lets the UA paint form controls, scrollbars and the checkbox in the same
   * scheme as the page instead of forcing light widgets onto a dark ground. */
  color-scheme: light dark;

  /* Each stack falls back to something of the same kind, so a font that has
   * not arrived yet (font-display: swap) leaves the page readable, not blank. */
  --font-display: "Newsreader", "Iowan Old Style", Charter, Georgia, serif;
  --font-body: "Geist", -apple-system, BlinkMacSystemFont, "Segoe UI",
    "Helvetica Neue", Arial, sans-serif;
  --font-mono: "Geist Mono", ui-monospace, SFMono-Regular, Menlo, monospace;

  --ink:        #15181d;
  --ink-soft:   #4b5561;
  /* Text tokens are split by the job they do, because one grey cannot pass
   * contrast everywhere it was being used. --ink-faint is for ordinary small
   * print (5.39:1 on --page, 5.93:1 on --card, 5.33:1 on --current-bg);
   * --ink-meta is for the uppercase micro-labels and table headers, which are
   * small enough to want the extra headroom (6.89:1 on --page). */
  --ink-faint:  #5b6570;
  --ink-meta:   #4b5561;
  --page:       #f6f4ee;
  --card:       #ffffff;
  /* The case column, a shade off the page so it reads as a separate thing to
   * refer to rather than more of the conversation. */
  --panel:      #fbfaf6;
  /* --line is decorative hairline only, and exempt from contrast. Anything
   * that bounds a control uses --line-control, which clears 3:1 against both
   * --page (3.88:1) and --card (4.27:1). */
  --line:         #e4e0d6;
  --line-control: #767b80;

  /* Brand: Michigan navy carries text/UI at 13.2:1 on --page, so every
   * accent role (links, focus rings, primary buttons) can point at one
   * token. --maize is decorative only (rules, badge fills, the current side of
   * the lens) — it is never used for text on its own, since maize-on-light
   * and white-on-maize both fail 4.5:1. */
  --accent:      #00275c;
  --accent-ink:  #ffffff;
  --accent-tint: #e8edf5;
  --maize:       #ffcb05;

  /* The learner's turns get a faint maize tint; the guide's get none, and are
   * told apart by their typeface instead. --ink holds 16.4:1 on the tint. */
  --learner-bg: #fff6d6;
  /* The current view's row in the rail. --accent holds 13.1:1 on it. */
  --current-bg: #fff3c4;
  /* Icon tiles on the consent card; navy holds 12.9:1 on it. */
  --tile:       #eef2f7;

  /* The lens. It never carries anything alone: the rail beside it says the
   * same position in words, and the review table prints a summary under each
   * one. That matters because maize, the current side, is only 1.38:1 against
   * the page -- it is there to be seen at a glance, not to be relied on.
   * Visited is 13.2:1, and the review's "left at the question limit" shade is
   * 5.49:1 against visited, so the two it has to tell apart differ in
   * lightness, not hue alone. */
  --lens-visited:  #00275c;
  --lens-current:  #ffcb05;
  --lens-upcoming: #e4e0d6;
  --lens-capped:   #8fa0bd;

  --danger:  #a3352b;
  --success: #2f6b46;

  --radius-lg: 20px;
  --radius: 16px;
  --radius-sm: 10px;
  --measure: 62ch;

  --shadow-sm: 0 1px 2px rgba(15, 23, 42, .06), 0 1px 1px rgba(15, 23, 42, .04);
  --shadow-md: 0 10px 30px rgba(15, 23, 42, .08), 0 2px 8px rgba(15, 23, 42, .05);
}

@media (prefers-color-scheme: dark) {
  :root {
    --ink:        #ece9e2;
    --ink-soft:   #b3bcc6;
    --ink-faint:  #a3adb8;
    --ink-meta:   #b3bcc6;
    --page:       #121417;
    --card:       #1d2126;
    --panel:      #16191d;
    --line:         #2a2f36;
    /* 4.86:1 on --page, 4.26:1 on --card. */
    --line-control: #7b848e;

    /* Navy vanishes against a near-black page, so dark mode flips the accent
     * role to maize (12.1:1 on --page) with navy ink on top of it -- the same
     * pairing as light mode, mirrored. */
    --accent:      #ffcb05;
    --accent-ink:  #00275c;
    --accent-tint: #1e2a3d;
    --maize:       #ffcb05;

    --learner-bg: #262216;
    /* --accent (maize) holds 9.8:1 on it. */
    --current-bg: #2b2718;
    --tile:       #1e2a3d;

    /* Navy would vanish here too; visited becomes a light slate (5.91:1 on
     * --page), and the current side stays maize. */
    --lens-visited:  #7c93b8;
    --lens-current:  #ffcb05;
    --lens-upcoming: #2c323a;
    --lens-capped:   #46556d;

    --danger:  #e2857a;
    --success: #7fc79c;

    --shadow-sm: 0 1px 2px rgba(0, 0, 0, .35), 0 1px 1px rgba(0, 0, 0, .25);
    --shadow-md: 0 10px 30px rgba(0, 0, 0, .45), 0 2px 8px rgba(0, 0, 0, .3);
  }
}

* { box-sizing: border-box; }

/* An author `display` rule beats the UA stylesheet's [hidden] rule, so any
 * element given a display here would ignore its own hidden attribute. */
[hidden] { display: none !important; }

html { color-scheme: light dark; }

body {
  margin: 0;
  background: var(--page);
  color: var(--ink);
  /* Not a fixed px size: the shorthand would override whatever the reader set
   * as their browser default. 100% honours it and every rem below scales. */
  font-family: var(--font-body);
  font-size: 100%;
  line-height: 1.6;
  -webkit-font-smoothing: antialiased;
}

::selection { background: var(--maize); color: #00275c; }

/* Without JS this page is six hidden sections and nothing else, so say so. */
noscript { display: block; padding: 2rem 1.25rem; }

/* ------------------------------------------------------------- header -- */

/* A constant brand bar: navy holds 14.8:1 for white text regardless of the
 * reader's colour scheme, so unlike every other surface on the page it does
 * not need a dark-mode variant. It is decorative chrome, not a heading — the
 * screens beneath still carry their own <h1>. */
.site-header {
  display: flex;
  align-items: center;
  gap: .75rem;
  background: #00275c;
  color: #ffffff;
  padding: .55rem 1.5rem;
  min-height: 3.5rem;
  border-bottom: 3px solid #ffcb05;
}
/* The Block M as shipped: maize artwork on transparency, sitting directly on
 * the navy bar. That pairing is the university's own, which is why there is no
 * maize tile behind it any more — the letter it replaced needed one to be
 * legible, and a maize mark on a maize tile would not be.
 *
 * Height sets the size and width follows the artwork, so the mark keeps its
 * proportions whatever the file's pixel dimensions are; overriding the width
 * and height attributes in the markup is the point of setting both here. */
.site-header .brand-mark {
  flex: none;
  width: auto;
  height: 1.75rem;
}
.site-header .brand-name {
  font-family: var(--font-display);
  font-weight: 500;
  font-size: 1.25rem;
  letter-spacing: -.01em;
}
.site-header .brand-tag {
  font-size: .78rem;
  color: #cfe0f5;
  margin-left: .1rem;
}
@media (max-width: 30rem) { .site-header .brand-tag { display: none; } }

/* The navigation sits in the brand bar, so no page is more than one click
 * from any other page the account opens. Which links exist is decided in
 * access.NAV, never here and never in a template.
 *
 * Not the page's --accent: this bar is navy in both colour schemes (see
 * above), and --accent is chosen for contrast against a light card. Each link
 * is a pill at least 44px tall, so it is an easy target on a touchscreen. The
 * other links are a pale blue-grey (9.63:1 here); the current page is white on
 * a lighter pill with a maize rule under its text (9.55:1), so the marker is
 * a shape and a rule as well as a colour and never relies on weight alone. */
.site-nav {
  display: flex; align-items: center; gap: .25rem;
  margin-left: 1.25rem;
  font-size: .9rem;
}
.site-nav a {
  display: inline-flex; align-items: center;
  min-height: 2.75rem;
  padding: 0 1rem;
  border-radius: 999px;
  color: #c9d3e3;
  font-weight: 500;
  text-decoration: none;
}
.site-nav a:hover { color: #ffffff; background: rgba(255, 255, 255, .08); }
.site-nav a[aria-current="page"] {
  color: #ffffff;
  background: rgba(255, 255, 255, .14);
  text-decoration: underline;
  text-decoration-color: #ffcb05;
  text-decoration-thickness: 2px;
  text-underline-offset: 6px;
}
.site-nav a:focus-visible { outline: 2px solid #ffcb05; outline-offset: 2px; }

/* Pushed right as a group, so the account controls stay together however
 * many nav links there are. */
.site-account {
  display: flex; align-items: center; gap: .75rem;
  margin-left: auto;
  font-size: .8125rem;
}
.site-account .who { color: #cfe0f5; font-family: var(--font-mono); font-size: .8rem; }
.site-account form { margin: 0; }
/* Sign-out has to be a POST from Django 5 on, so it cannot be an <a>. It is
 * drawn as an outlined pill the height of the nav links beside it, which is
 * also what gives it a 44px target. */
.site-account button.linkish {
  min-height: 2.75rem;
  padding: 0 1rem;
  border: 1px solid rgba(255, 255, 255, .4);
  border-radius: 999px;
  background: none;
  font: inherit; font-size: .85rem; font-weight: 500;
  color: #ffffff; text-decoration: none; cursor: pointer;
}
.site-account button.linkish:hover { background: rgba(255, 255, 255, .08); }
.site-account button.linkish:focus-visible { outline: 2px solid #ffcb05; outline-offset: 2px; }

/* Narrow enough that the bar cannot hold both rows: wrap the links under the
 * brand rather than squeezing the name to nothing. */
@media (max-width: 34rem) {
  /* Two rows: the brand on the first, the links and Sign out on the second.
   * The username is the one thing dropped to make that fit -- the consent
   * card names the account in full, and a phone header three rows deep would
   * take a sixth of the screen for the whole session. */
  /* No forced break: the brand and the links do not fit one row at these
   * widths, so the links wrap on their own and Sign out follows them. */
  .site-header { flex-wrap: wrap; row-gap: .35rem; padding-inline: 1rem; }
  .site-nav { margin-left: -.75rem; }
  .site-nav a { padding: 0 .75rem; }
  .site-account .who { display: none; }
}

.shell { max-width: 60rem; margin: 0 auto; padding: 2rem 1.25rem 4rem; }

/* The chat screen alone is allowed to be wider. 60rem is a measure for a
 * single column of prose, which is what every other screen is; chat is two
 * columns, and the 13rem sidebar comes straight out of the reading column, so
 * the same cap leaves the case narrower than the pages either side of it.
 *
 * 68rem is not a round number picked for looks -- it is the widest the case
 * can be and still read at a sane measure. Card content is shell minus 320px
 * (shell padding, sidebar, gap, card padding), so 68rem gives the case 768px,
 * which at its 1rem size is ~76ch. Wider than this and the lines get too long
 * to track; the rest of a large monitor is deliberately margin. */
body[data-screen="chat"] .shell { max-width: 68rem; }
/* From 80rem the case moves out to a third column (see .chat below), and
 * that column's 23rem comes on top of the 68rem above rather than out of the
 * conversation -- which is what keeps the conversation at a reading measure
 * instead of squeezing it to make room. */
@media (min-width: 80rem) {
  body[data-screen="chat"] .shell { max-width: 92rem; }
}
/* The consent screen is two columns -- the introduction and the consent card
 * -- and the evaluation puts the transcript beside the coaching, so both are
 * wider than a single column of prose. Each column inside still keeps its own
 * measure. */
body[data-screen="consent"] .shell { max-width: 80rem; padding-top: 3rem; }
body[data-screen="evaluation"] .shell { max-width: 76rem; padding-top: 3rem; }

/* Only the screen matching body[data-screen] is shown. */
.screen { display: none; }
body[data-screen="loading"]    .screen[data-for="loading"],
body[data-screen="consent"]    .screen[data-for="consent"],
body[data-screen="courses"]    .screen[data-for="courses"],
body[data-screen="chat"]       .screen[data-for="chat"],
body[data-screen="evaluation"] .screen[data-for="evaluation"],
body[data-screen="feedback"]   .screen[data-for="feedback"],
body[data-screen="thanks"]     .screen[data-for="thanks"] { display: block; }
body[data-screen="chat"] .screen[data-for="chat"],
body[data-screen="consent"] .screen[data-for="consent"] { display: grid; }

/* Screen changes move focus to the new heading so a screen reader announces
 * it. That focus is programmatic, not the keyboard landing somewhere, so it
 * must not paint a ring -- it reads as a stray box drawn around the heading.
 * Real controls still get their :focus-visible outlines. */
[tabindex="-1"]:focus { outline: none; }

/* Absolutely positioned, so it sits wherever its containing block says --
 * and a box that scrolls sideways clips only what it contains. Text hidden
 * this way inside a scroller whose containing block is further out escapes
 * the scroller and widens the whole page: at 320px the view strip's
 * "(not yet reached)" labels made the chat screen 762px wide, and the review
 * table's "none"s did the same to its page. So every box here that scrolls
 * sideways is also `position: relative` (.rail ol, and .table-wrap in
 * review.css), which makes it the containing block and keeps these inside. */
.sr-only {
  position: absolute; width: 1px; height: 1px; margin: -1px;
  padding: 0; overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap;
}

/* Off-screen until tabbed to: the first stop on every page skips the
 * brand bar/rail and lands on the content. */
.skip {
  position: absolute; left: .75rem; top: -3rem; z-index: 100;
  background: var(--card); color: var(--accent);
  padding: .6rem 1rem; border-radius: var(--radius-sm); border: 1px solid var(--accent);
  transition: top .15s;
}
.skip:focus { top: .75rem; }
@media (prefers-reduced-motion: reduce) { .skip { transition: none; } }

/* ---------------------------------------------------------- typography -- */

/* Headings are the serif's: they are the page's own voice, the same one the
 * guide speaks in. Weight 500, not bold -- a serif at 700 on a screen this
 * calm shouts. */
h1, h2 { font-family: var(--font-display); font-weight: 500; letter-spacing: -.015em; }
h1 { font-size: 2.25rem; line-height: 1.15; margin: 0 0 .5rem; }
h2 { font-size: 1.5rem; line-height: 1.25; margin: 0 0 .75rem; }
p  { margin: 0 0 1rem; max-width: var(--measure); }
ul { max-width: var(--measure); padding-left: 1.25rem; }
li { margin-bottom: .35rem; }

.masthead { margin-bottom: 1.5rem; }
.lede  { color: var(--ink-soft); font-size: 1.1rem; }
.muted { color: var(--ink-faint); }
.hint  { color: var(--ink-faint); font-size: .875rem; margin: .35rem 0 0; }
.error { color: var(--danger); font-size: .925rem; }

/* The small uppercase label over a thing: "The case", "View 4 of 6". Mono, so
 * it reads as a label rather than as a sentence that lost its case. Also used
 * on real headings (h1.eyebrow, h2.eyebrow), which is why it resets the
 * heading's font and size rather than assuming a span. */
.eyebrow {
  display: block;
  margin: 0;
  font-family: var(--font-mono);
  font-size: .75rem;
  font-weight: 500;
  line-height: 1.4;
  letter-spacing: .1em;
  text-transform: uppercase;
  color: var(--ink-meta);
}

/* The one display size, for the line that opens a screen. */
.display {
  font-size: clamp(2.5rem, 4.5vw + 1rem, 4.25rem);
  line-height: 1.04;
  letter-spacing: -.025em;
  margin: 0 0 1.25rem;
  max-width: 14em;
}
.display em { font-style: italic; font-weight: 400; color: var(--accent); }

.chips { display: flex; flex-wrap: wrap; gap: .5rem; margin: 0 0 1.5rem; }
.chip {
  display: inline-block;
  padding: .3rem .75rem;
  border: 1px solid var(--line-control);
  border-radius: 999px;
  font-family: var(--font-mono);
  font-size: .72rem;
  letter-spacing: .08em;
  text-transform: uppercase;
  color: var(--ink-soft);
}
.chip.solid { background: var(--accent); border-color: var(--accent); color: var(--accent-ink); }

.card {
  background: var(--card);
  border: 1px solid var(--line);
  border-radius: var(--radius-lg);
  box-shadow: var(--shadow-sm);
  padding: 1.75rem;
  margin-bottom: 1.25rem;
}
.card.centred {
  text-align: center;
  max-width: 34rem;
  margin-inline: auto;
}
.card.warn { border-left: 4px solid var(--danger); }
.card.warn p { margin: 0; }
.card.centred p { margin-inline: auto; }
.card.centred .muted { margin: 1.25rem 0 0; font-size: .9rem; }
/* The evaluation is a page of prose with section headings; richText() builds
 * real elements for it, so no pre-wrap is wanted. */
/* Set in the serif at a reading size: it is a page of coaching written to one
 * person, and should read like a letter rather than a report. */
.prose, .prose p { max-width: var(--measure); }
/* As a card, the box fills its column like the cards around it; the measure
 * is held by the paragraphs inside, which is where it does its work. */
.card.prose { max-width: none; }
.prose p { font-family: var(--font-display); font-size: 1.15rem; line-height: 1.65; }
.prose > :first-child { margin-top: 0; }
.prose h2 { font-size: 1.4rem; margin: 2rem 0 .5rem; }
.prose h3 { font-family: var(--font-body); font-size: 1rem; font-weight: 600; margin: 1.25rem 0 .4rem; }

/* -------------------------------------------------------------- forms -- */

.field { margin-bottom: 1.5rem; }
.field > label, legend {
  display: block; font-weight: 600; margin-bottom: .35rem; padding: 0;
}
fieldset.field { border: 0; margin-inline: 0; padding: 0; }

input[type="text"], input[type="password"], textarea {
  width: 100%;
  font: inherit;
  color: var(--ink);
  background: var(--card);
  border: 1px solid var(--line-control);
  border-radius: var(--radius-sm);
  padding: .7rem .85rem;
  resize: vertical;
  transition: border-color .12s ease;
}
/* Placeholders carry information here -- an example label, and the only
 * visible cue in the composer -- so they are text, and held to 4.5:1. Left to
 * the browser they were not: Chromium paints #757575 in both schemes (3.5:1
 * on the dark card), Firefox the text colour at 54% (3.84:1 on white).
 * Opacity 1 undoes Firefox's own dimming. --ink-faint is 5.93:1 on the light
 * card and 7.11:1 on the dark one. */
::placeholder { color: var(--ink-faint); opacity: 1; }

input[type="text"]:hover, input[type="password"]:hover, textarea:hover { border-color: var(--accent); }
input[type="text"]:focus-visible, input[type="password"]:focus-visible,
textarea:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 1px;
}

.check { display: flex; gap: .6rem; align-items: flex-start; margin: 1.25rem 0; }
.check input { margin-top: .35rem; flex: none; }
.check span { max-width: var(--measure); }
/* One card per course on the "Your courses" screen. */
.course-list { list-style: none; margin: 0; padding: 0; }
.course-list > li { margin-bottom: 1rem; }
.course-list h2 { margin: 0 0 .25rem; font-size: 1.1rem; }
.course-list .actions { display: flex; flex-wrap: wrap; gap: .75rem;
                        align-items: center; margin-top: .75rem; }

/* `a.primary` is here because signing in with U-M is a navigation, not a form
 * submit: it has to be a link so it works the way links do (middle-click, copy
 * address, the status bar showing where it goes). It should not look like a
 * different kind of control for that, so the two share one rule rather than
 * one being a copy of the other that drifts. The :disabled pair below stays
 * button-only -- a link has no disabled state to dim. */
button.primary,
a.primary {
  display: inline-block;
  text-decoration: none;
  text-align: center;
  font: inherit;
  font-weight: 600;
  background: var(--accent);
  color: var(--accent-ink);
  border: 0;
  border-radius: 12px;
  /* At least 44px tall at the default text size. */
  min-height: 2.75rem;
  padding: .7rem 1.35rem;
  cursor: pointer;
  box-shadow: var(--shadow-sm);
  transition: background-color .12s ease, box-shadow .12s ease, transform .12s ease;
}
button.primary:hover:not(:disabled):not([aria-disabled="true"]),
a.primary:hover {
  background: color-mix(in srgb, var(--accent) 88%, black);
  box-shadow: var(--shadow-md);
}
button.primary:active:not(:disabled):not([aria-disabled="true"]),
a.primary:active { transform: translateY(1px); }
@media (prefers-reduced-motion: reduce) {
  button.primary, a.primary { transition: none; }
}
/* aria-disabled rather than disabled keeps the button in the tab order, so it
 * has to be dimmed here as well as by the UA's :disabled rule. */
button.primary:disabled,
button.primary[aria-disabled="true"] { opacity: .45; cursor: not-allowed; }
button.primary:focus-visible,
a.primary:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
/* The button is a filled shape with no border, and forced colours discard
 * the fill -- leaving "Begin the simulation" as a line of text with nothing
 * to say it can be pressed. The system's own button edge puts it back. */
@media (forced-colors: active) {
  button.primary, a.primary { border: 1px solid ButtonText; }
}
@media (prefers-color-scheme: dark) {
  /* The default outline colour is maize in dark mode, same as --accent, which
   * would sit invisibly on top of a maize button. Ring it in ink instead. */
  button.primary:focus-visible,
  a.primary:focus-visible { outline-color: var(--ink); }
}

/* The one action a card exists for -- Begin -- spans the card. */
button.primary.block { display: block; width: 100%; padding: .95rem 1.35rem; font-size: 1.05rem; border-radius: 14px; }

/* On the navy invitation card, where a navy button would vanish: maize with
 * navy ink, the university's own pairing (9.55:1), in both colour schemes
 * because the card is navy in both. */
button.primary.maize { background: #ffcb05; color: #00275c; }
button.primary.maize:hover:not(:disabled) { background: #ffd633; }
button.primary.maize:focus-visible { outline-color: #ffffff; }

/* Anchors carry this class too (the raw-JSON link on the review page); without
 * them in the selector they fell back to the UA's default blue. */
.linkish, button.linkish {
  font: inherit;
  font-size: .9rem;
  background: none;
  border: 0;
  padding: 0;
  color: var(--accent);
  text-decoration: underline;
  text-underline-offset: 3px;
  cursor: pointer;
}
.linkish:focus-visible, button.linkish:focus-visible { outline: 2px solid var(--accent); outline-offset: 3px; }

/* The 1–5 scales, built by app.js into .scale containers. */
.scale { display: flex; align-items: center; gap: .5rem; flex-wrap: wrap; }
.scale .end { color: var(--ink-faint); font-size: .85rem; }
.scale label {
  display: grid; place-items: center;
  /* min-, not fixed: at increased text spacing a fixed box clips its digit. */
  min-width: 2.5rem; min-height: 2.5rem;
  border: 1px solid var(--line-control); border-radius: var(--radius-sm);
  background: var(--card); cursor: pointer;
  transition: border-color .12s ease;
}
.scale input { position: absolute; opacity: 0; pointer-events: none; }
/* Colour alone cannot carry "chosen", so the selected box also gains an inset
 * ring -- a shape difference that survives a monochrome or colour-blind view. */
.scale label:has(input:checked) {
  background: var(--accent); color: var(--accent-ink); border-color: var(--accent);
  box-shadow: inset 0 0 0 2px var(--card);
}
.scale label:has(input:focus-visible) { outline: 2px solid var(--accent); outline-offset: 2px; }
/* In forced-colours mode the author's background is discarded, taking the only
 * selected cue with it. Show the real radio and mark the choice with a system
 * colour instead. */
@media (forced-colors: active) {
  .scale input { opacity: 1; position: static; pointer-events: auto; }
  .scale label:has(input:checked) { outline: 3px solid Highlight; }
}

/* --------------------------------------------------------------- chat -- */

/* Two independent columns: the patient and the view rail down the side, the
 * case and the conversation stacked beside them. The sidebar spans both rows
 * so its contents stack on their own rather than lining up with a row height
 * set by the case.
 *
 * Named areas rather than source order, so the markup can keep the case
 * first, where a screen reader and a no-CSS render both want it. */
.chat {
  grid-template-columns: 13rem 1fr;
  grid-template-areas:
    "aside case"
    "aside conversation";
  gap: 1.5rem 2rem;
  align-items: start;
}
.chat > .case-col     { grid-area: case; }
.chat > .sidebar      { grid-area: aside; }
.chat > .conversation { grid-area: conversation; }

/* The picture beside the case rather than above it, while there is room: the
 * case is the thing being read, and a picture on top of it pushes the text
 * down by its whole height. When the scenario has no picture the case takes
 * the full width -- the :has() below -- instead of starting in a column
 * sized for one. */
.case-col {
  min-width: 0;
  display: grid;
  grid-template-columns: 11rem minmax(0, 1fr);
  gap: 1.25rem;
  align-items: start;
}
.case-col:has(> .case-figure[hidden]) { grid-template-columns: minmax(0, 1fr); }

/* Wide enough for three columns: progress, conversation, case. The case gets
 * a column of its own and stays in view while the conversation scrolls, which
 * is what a learner wants of it -- something to glance back at mid-sentence,
 * not something to scroll up to. It scrolls inside itself when it is taller
 * than the window; app.js gives it a tab stop only while it does. The shell
 * widens at the same breakpoint (above), so the conversation keeps roughly
 * the width it had in two columns. */
@media (min-width: 80rem) {
  .chat {
    grid-template-columns: 15rem minmax(0, 1fr) 23rem;
    grid-template-areas: "aside conversation case";
    gap: 2.5rem;
  }
  .case-col {
    display: block;
    position: sticky;
    top: 1.5rem;
    max-height: calc(100vh - 3rem);
    overflow-y: auto;
    overscroll-behavior: contain;
    padding: 1.25rem;
    background: var(--panel);
    border: 1px solid var(--line);
    border-radius: var(--radius-lg);
  }
  .case-col .case-figure { margin: 0 0 1rem; }
  /* Already inside the column's own panel; a card inside a card is a box for
   * the sake of a box. */
  .case-col .case { background: none; border: 0; padding: 0; }
  .case-col .case[open] { padding-bottom: 0; }
  .case-col .case-body p { font-size: 1rem; }
}
.case-col:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }

/* Stack well before phone width. Two columns are only worth having while the
 * case column is wide enough to hold the case at its measure: below roughly
 * 60rem the sidebar's 13rem comes straight out of the case, and at 760px it
 * left the text a 438px column that clipped -- while the same window stacked
 * gave it 562px and showed the case whole. Measured, not guessed.
 *
 * Dropping this to 52rem was tried, to give the two-column layout a wider
 * flowing range now that the cap above lets it grow. It made things worse:
 * at 840px the case column is 518px against the 748px the same window gets
 * stacked, so widening the window past the breakpoint shrank the case by
 * 230px and made it 150px taller. The sidebar is a fixed 13rem, so every
 * pixel below 60rem comes out of the reading column. Left at 60rem. */
@media (max-width: 60rem) {
  .chat {
    grid-template-columns: 1fr;
    grid-template-areas: "aside" "case" "conversation";
    gap: 1rem;
  }
  /* The sidebar's `position: static` for this width belongs here with the
   * rest of the stacked layout, and cannot live here: the base `.sidebar`
   * rule further down sets `position: sticky` at the same specificity, and
   * the later rule wins. It is overridden just after that rule instead --
   * search for "Nothing to stick to up here". */
  /* Six names down the full width is a column of whitespace. Wrapped, they
   * are two short rows. */
  .rail ol { display: flex; flex-wrap: wrap; gap: .25rem 1rem; }
  /* The rail says where the learner is in words, and there is no column for
   * a picture of it at this width. */
  .sidebar .lens-wrap { display: none; }
}
/* Below the three-column width the picture and the case sit side by side at
 * the head of the conversation; the stacked phone layout puts the picture
 * back above the text (further down). */
@media (max-width: 46rem) {
  .case-col { display: block; }
  .case-figure { width: 9rem; margin: 0 0 .75rem; }
}

/* position: relative makes the list the containing block for the sr-only
 * state labels inside it; see .sr-only for what they did without it. */
.rail ol { list-style: none; margin: 0; padding: 0; font-size: .9rem; position: relative; }
/* Below 46rem the list is a strip that scrolls sideways, and app.js gives
 * it a tab stop while it does -- a box that scrolls but cannot be focused
 * cannot be scrolled from a keyboard. The stop needs a ring like any other. */
.rail ol:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
.rail li {
  display: flex; gap: .6rem; align-items: baseline;
  padding: .4rem 0; color: var(--ink-faint);
}
/* Visited views are marked as visited, never as passed: a view can be left at
 * the probe cap without being passed, and the learner is not told which. */
.rail li::before {
  content: ""; flex: none;
  width: .55rem; height: .55rem; border-radius: 50%;
  border: 1.5px solid currentColor;
}
/* Above 60rem app.js builds each item as a disclosure -- the name is the
 * summary, the view's purpose is the panel, and the current one arrives open.
 * The dot stays on the li, so it keeps its baseline against the name and the
 * panel hangs below both. */
.rail details { flex: 1; min-width: 0; }
.rail summary {
  cursor: pointer;
  /* Kills the native triangle in Firefox; the ::-webkit- rule does WebKit.
   * The chevron below replaces it on the other side of the row, where it
   * cannot be read as a second status dot. */
  list-style: none;
  display: flex; align-items: baseline; gap: .5rem;
}
.rail summary::-webkit-details-marker { display: none; }
/* Empty alt text after the slash: without it a reader announces the glyph's
 * name before the view's. The state is already in the sr-only span. */
.rail summary::after {
  content: "\25BE" / "";
  margin-left: auto;
  /* .7em rendered a 10px triangle that read as a second, smaller dot rather
   * than as "this opens". Sized against the rail, not the row, so it does not
   * shrink further wherever the row does. */
  font-size: .8rem;
  /* currentColor, so the chevron carries the row's own state instead of
   * sitting at one faint grey against six different foregrounds. */
  transition: transform .15s ease;
}
.rail details:not([open]) summary::after { transform: rotate(-90deg); }
@media (prefers-reduced-motion: reduce) {
  .rail summary::after { transition: none; }
}
.rail summary:focus-visible { outline: 2px solid var(--accent); outline-offset: 3px; }
/* The current item is accent and 600 for its name; the prose under it must not
 * inherit either, or the reminder shouts louder than the rail it sits in. */
.rail details > p {
  margin: .45rem 0 .15rem;
  font-size: .85rem;
  line-height: 1.5;
  font-weight: 400;
  color: var(--ink-soft);
}
.rail li[data-state="visited"] { color: var(--ink-soft); }
.rail li[data-state="visited"]::before { background: currentColor; }
.rail li[data-state="current"] { color: var(--accent); font-weight: 600; }
/* The current dot is the lens's current side in small: maize, ringed in the
 * accent so it holds its edge on the maize-tinted row behind it. */
.rail li[data-state="current"]::before { background: var(--maize); border-color: var(--accent); }
/* Forced colours replace every fill with the page background, so all six
 * dots came out as the same empty ring and visited could not be told from
 * upcoming at all. Painted in system colours instead: filled for visited,
 * the highlight colour for current, an empty ring for the rest. */
@media (forced-colors: active) {
  .rail li::before { forced-color-adjust: none; border-color: CanvasText; background: Canvas; }
  .rail li[data-state="visited"]::before { background: CanvasText; }
  .rail li[data-state="current"]::before { background: Highlight; border-color: Highlight; }
}
/* In the column, the current view is a tinted row: the one place in the rail
 * the eye should land. Every row carries the same side padding, so the tinted
 * one's text stays aligned with the rows above and below without the tint
 * being pulled out past the column. Column only -- in the wrapped row and the
 * sideways strip below 60rem a tinted block would shove its neighbours
 * about. */
@media (min-width: 60.001rem) {
  .rail li { padding-inline: .75rem; }
  .rail li[data-state="current"] {
    background: var(--current-bg);
    border-radius: 12px;
    margin: .3rem 0;
    padding-block: .6rem;
  }
}

/* ---------------------------------------------------------------- lens -- */

/* Sides are coloured by the state their drawer gave them, and nothing else;
 * see the --lens-* tokens for why it is never the only carrier of any fact.
 * The stroke is the ground colour, which is what opens the gaps between
 * sides -- so anything drawing one on a card sets the stroke to the card. */
.lens { display: block; overflow: visible; }
.lens polygon {
  fill: var(--lens-upcoming);
  stroke: var(--page);
  stroke-width: 5;
  stroke-linejoin: round;
  transition: fill .6s ease;
}
.lens polygon[data-state="visited"],
.lens polygon[data-state="passed"],
.lens polygon[data-state="on"] { fill: var(--lens-visited); }
.lens polygon[data-state="current"] { fill: var(--lens-current); }
.lens polygon[data-state="capped"] { fill: var(--lens-capped); }
@media (prefers-reduced-motion: reduce) { .lens polygon { transition: none; } }

.lens-wrap {
  position: relative;
  width: 11rem;
  aspect-ratio: 1;
  margin: 0 auto 1.75rem;
  display: grid;
  place-items: center;
}
.lens-wrap .lens { position: absolute; inset: 0; width: 100%; height: 100%; }
.lens-count {
  position: relative;
  display: flex; flex-direction: column; align-items: center; gap: .35rem;
  line-height: 1;
}
.lens-count .n { font-family: var(--font-display); font-size: 2.6rem; font-weight: 500; }
.lens-count .of {
  font-family: var(--font-mono); font-size: .68rem;
  letter-spacing: .1em; text-transform: uppercase; color: var(--ink-faint);
}

/* Where the learner is, over the conversation. The rail says it too; this is
 * the version readable from across the room. It is the screen's h2, so the
 * h2 rules are reset here: the two lines inside carry their own type. */
.view-head { margin: 0 0 1.75rem; font-size: 1rem; line-height: 1.4; letter-spacing: normal; }
.view-title {
  display: block;
  font-family: var(--font-display); font-weight: 400;
  font-size: 2.4rem; line-height: 1.1; letter-spacing: -.02em;
  margin: .4rem 0 0;
}
@media (max-width: 46rem) {
  .view-head { margin-bottom: 1.25rem; }
  .view-title { font-size: 1.75rem; }
}
/* Upcoming views used to be display:none below 46rem. That predated the
 * sideways-scrolling rail further down, and the two worked against each
 * other: the scroller exists so a row too long for the screen can still be
 * reached, and this removed two thirds of the row before it could be. It
 * cost more than room -- display:none takes the items out of the
 * accessibility tree too, so a nav announcing itself as "Progress through
 * the six views" offered two of them, and the "(not yet reached)" wording
 * app.js writes for each one was never read. All six stay; the rail scrolls,
 * and renderRail keeps the current one in view. */

/* The reminder for windows at or below 60rem, where the rail is a band above
 * the case and cannot hold the prose itself. Above 60rem app.js leaves this
 * hidden and the rail's own disclosures carry it -- see narrowRail there.
 *
 * Directly under the rail and ruled off from it: the two read as one block,
 * the rail naming the view and this saying what it is for.
 *
 * The eyebrow deliberately does not repeat the view's name. It sits under a
 * rail entry already in accent and bold, and at these widths the rail is a
 * wrapped row or a scrolling strip -- a second copy of "Human Experience View"
 * would be the same words twice within an inch. */
.view-purpose {
  margin-top: 1rem;
  padding-top: .9rem;
  border-top: 1px solid var(--line);
}
.view-purpose h2 {
  font-family: var(--font-mono); font-weight: 500;
  font-size: .72rem; letter-spacing: .1em;
  text-transform: uppercase; color: var(--ink-meta);
  margin: 0 0 .4rem;
}
.view-purpose p {
  margin: 0;
  font-size: .85rem;
  line-height: 1.5;
  color: var(--ink-soft);
}

/* The rail and the case follow the learner down the page. Six lenses over
 * fifteen-odd turns is a long scroll to lose your place in. */
.sidebar {
  position: sticky;
  top: 1.5rem;
  align-self: start;
  /* A grid item's automatic minimum size is its content, so without this the
   * sidebar refuses to be narrower than the rail's widest possible row. Below
   * 46rem that row is `white-space: nowrap` and 625px long, which pushed the
   * 1fr track to 625px on a 320px screen -- the case and the conversation
   * went with it, and the whole page scrolled sideways. The rail's own
   * overflow-x could never engage, because the rail was never the thing being
   * squeezed. Same reason .conversation and .eval-aside carry it.
   *
   * This was latent until the upcoming views stopped being display:none: two
   * items fit, six do not. */
  min-width: 0;
}
/* Nothing to stick to up here: stacked, the sidebar is a band above the case
 * rather than a column beside it, so there is nothing for it to follow.
 *
 * This has to sit after the rule above rather than up in the (max-width:
 * 60rem) block with the rest of the stacked layout, and that is not
 * cosmetic. Both selectors are `.sidebar`, so the cascade falls to source
 * order and the later one wins. Up there it was dead: between 46rem and
 * 60rem the sidebar stayed sticky inside a single-column grid and pinned
 * itself over the case the moment the learner scrolled -- a reload appeared
 * to fix it only because a page at scroll 0 has nothing to overlap yet. The
 * 46rem and max-height overrides below always worked for the same reason
 * this one now does: they come after. */
@media (max-width: 60rem) {
  .sidebar { position: static; }
}

/* At 400% zoom, or on a short landscape window, a sticky sidebar eats the
 * viewport it is pinned to. Let it scroll away instead. */
@media (max-height: 30rem) {
  .sidebar { position: static; }
  /* The case column likewise: capped to a viewport this short, it would be a
   * letterbox of a few lines. Let it run long and scroll with the page. */
  .case-col { position: static; max-height: none; overflow: visible; }
}

/* A card now that it leads the screen, rather than a bordered-off scrap in
 * the sidebar. The padding shrinks when collapsed so a closed case is a
 * single quiet line, not an empty box holding the conversation down. */
.case {
  background: var(--card);
  border: 1px solid var(--line);
  border-radius: var(--radius);
  padding: .85rem 1.25rem;
}
.case[open] { padding-bottom: 1.25rem; }
.case > summary {
  cursor: pointer;
  font-size: .9rem;
  font-weight: 600;
  color: var(--accent);
  list-style: none;
  display: flex;
  align-items: center;
  gap: .45rem;
}
.case > summary::-webkit-details-marker { display: none; }
/* The empty alt text after the slash keeps the disclosure triangle decorative:
 * without it a screen reader reads the glyph's name out before "The case". */
.case > summary::before { content: "\25B8" / ""; }
.case[open] > summary::before { content: "\25BE" / ""; }
.case > summary:focus-visible { outline: 2px solid var(--accent); outline-offset: 3px; }
/* Beside the case, or at the top of its column on a wide window. Outside the
 * case card on purpose: collapsing the case to free up room should not also
 * put the patient away, and he is the thing the whole session is about. */
.case-figure { margin: 0 0 1.25rem; min-width: 0; }
.case-figure img {
  display: block;
  width: 100%;
  height: auto;
  border-radius: var(--radius-sm);
  border: 1px solid var(--line);
}

/* Fills the card, and never scrolls inside it.
 *
 * The width cap was --measure, from when the case sat in the sidebar. Now
 * that it leads the screen as a full-width card, that cap left every line
 * stopping 97px short of the card's right edge -- a ragged column of text in
 * a box plainly wider than it. Nothing caps the text here any more; what
 * holds the measure is the chat shell's 68rem above, which is chosen so the
 * card hands the case ~76ch and no more.
 *
 * The height cap is gone with it. min(70vh, 34rem) resolved to 490px for
 * case01's 494px of text on a 700px-tall window: a scrollbar, a pair of
 * scroll shadows and a keyboard tab stop, all bought for the last sliver of
 * one line. The case is a <details> the learner can collapse in a click, and
 * that is the better escape hatch from a long case -- the same trade the
 * narrow layout below has always made. */
.case-body {
  margin-top: .8rem;
  /* Was .9rem, the smallest type on the screen, on the most-read text on it.
   * The size is also what lets the column be as wide as it now is: at .9rem
   * the case's 768px would run to ~85ch, and at 1rem it is ~76ch. Bigger type
   * and a wider column buy each other. */
  font-size: 1rem;
  /* The serif, like the guide: the case is written to be read, as the
   * coaching is, and the learner's own words are the sans beside both. */
  font-family: var(--font-display);
  line-height: 1.65;
}
/* max-width:none is doing the real work here. Clearing the cap on .case-body
 * alone only widened the container: the global `p { max-width: var(--measure) }`
 * still capped each paragraph inside it, so the box grew to the card's width
 * while the text stayed at 562px and the gap on the right survived untouched.
 * Measure the paragraph, not the wrapper, when checking this. */
.case-body p { margin-bottom: .7rem; max-width: none; }
.case-body p:last-child { margin-bottom: 0; }

@media (max-width: 46rem) {
  /* Stacked, the sidebar stays pinned to the top of the viewport instead of
   * the top of the column -- losing your place is the same problem on a
   * phone. It earns the space by staying to one short row: the rail scrolls
   * sideways rather than wrapping. The case now sits above it and scrolls
   * away, which is the right trade on a small screen -- it is a page of text
   * that would otherwise pin half the viewport for the whole session, and it
   * is one tap on the summary to get back. */
  /* The rail is what pins here, not the whole sidebar. With the patient in
   * it, a sticky sidebar would hold a picture at the top of a phone screen
   * for the entire session; this lets the image scroll away and keeps the
   * one-line rail. */
  .sidebar { position: static; }
  .rail {
    position: sticky;
    top: 0;
    z-index: 10;
    background: var(--page);
    padding: .6rem 0 .5rem;
    border-bottom: 1px solid var(--line);
  }
  /* The image stays in flow above the rail rather than floated beside it.
   * Floated, the sticky rail -- which needs an opaque background to cover
   * the text it pins over -- drew straight over the picture, leaving only
   * the strip that hung below it. In flow it simply scrolls away under the
   * rail, which is what sticky is for. */
  /* The scrollbar is left visible on purpose: hiding it left this strip
   * scrollable by touch and by nothing else. */
  .rail ol {
    flex-wrap: nowrap;
    overflow-x: auto;
    padding-bottom: .15rem;
  }
  .rail li { white-space: nowrap; }
  /* The rail carries its own border-bottom once it is the sticky strip, so
   * this one's border-top would draw a second rule directly under it. The
   * block still reads as attached to the rail -- it is the next thing on the
   * screen -- so drop the rule and keep the gap tight. */
  .view-purpose { margin-top: .75rem; padding-top: 0; border-top: 0; }
  .case { padding: .7rem 1rem; }
  .case[open] { padding-bottom: 1rem; }
}
/* The (max-height: 30rem) rule above unpins the sidebar and the case at 400%
 * zoom, but the strip is pinned by the block just above, which comes later
 * and so wins. At 320x256 -- a 1280x1024 screen at 400% -- it held 62px, a
 * quarter of the window, over the conversation for the whole session. Here,
 * after it, so the short-window rule actually applies to it too. */
@media (max-width: 46rem) and (max-height: 30rem) {
  .rail { position: static; }
}

.conversation { min-width: 0; }

/* Two voices, told apart by type. The guide's turns are not boxed at all:
 * set in the serif at a reading size, they read as the page speaking. The
 * learner's are the sans in a maize-tinted card, pulled to the right. Each
 * still says who is speaking in words (.who), so nothing rests on the
 * typeface alone. */
.transcript { display: flex; flex-direction: column; gap: 1.75rem; margin-bottom: 1.5rem; }
.turn { border-radius: var(--radius); max-width: var(--measure); }
.turn.agent { padding: 0; }
.turn.agent p { font-family: var(--font-display); font-size: 1.25rem; line-height: 1.55; }
.turn.learner {
  background: var(--learner-bg);
  border: 1px solid transparent;
  border-radius: 18px 18px 6px 18px;
  padding: 1rem 1.25rem;
  align-self: flex-end;
  max-width: min(36rem, 100%);
}
/* Forced colours drop the tint, and with it the only edge the learner's turn
 * has. Give it a real one there, in the system's own text colour. */
@media (forced-colors: active) {
  .turn.learner { border-color: CanvasText; }
}
.turn p { margin: 0 0 .6rem; }
.turn p:last-child { margin-bottom: 0; }
.turn .who {
  display: flex; align-items: center; gap: .45rem;
  font-family: var(--font-mono); font-size: .7rem; letter-spacing: .12em;
  text-transform: uppercase; color: var(--ink-meta); margin-bottom: .45rem;
}
/* A small square in the accent before the guide's name: the same mark in the
 * same place on every guide turn, so a long scroll of them has a rhythm. */
.turn.agent .who::before {
  content: ""; width: .5rem; height: .5rem; border-radius: 2px;
  background: var(--accent);
}
@media (max-width: 46rem) {
  .transcript { gap: 1.25rem; }
  .turn.agent p { font-size: 1.125rem; }
}

.waiting { display: flex; align-items: center; gap: .6rem; color: var(--ink-faint); margin-bottom: 1rem; }
.dots { display: inline-flex; gap: .25rem; }
.dots i {
  width: .4rem; height: .4rem; border-radius: 50%;
  background: currentColor; animation: pulse 1.2s infinite ease-in-out;
}
.dots i:nth-child(2) { animation-delay: .18s; }
.dots i:nth-child(3) { animation-delay: .36s; }
@keyframes pulse { 0%, 60%, 100% { opacity: .25; } 30% { opacity: 1; } }
@media (prefers-reduced-motion: reduce) { .dots i { animation: none; opacity: .6; } }

/* The composer is one card: the textarea sits borderless inside it, and the
 * card takes the focus ring when anything in it has focus -- so the ring goes
 * round everything the learner is working with, send button included. */
.composer {
  background: var(--card);
  border: 1px solid var(--line-control);
  border-radius: var(--radius-lg);
  padding: .75rem .85rem .65rem 1.1rem;
  box-shadow: var(--shadow-md);
}
.composer:focus-within { outline: 2px solid var(--accent); outline-offset: 2px; }
.composer textarea {
  border: 0;
  border-radius: 0;
  padding: .3rem 0;
  background: transparent;
}
.composer textarea:focus-visible { outline: none; }
/* The error states itself in words above the form; this is the edge a
 * sighted learner sees on the box it belongs to. */
.composer:has(textarea[aria-invalid="true"]) { border-color: var(--danger); }
.composer-foot {
  display: flex; justify-content: space-between; align-items: center;
  gap: 1rem; margin-top: .35rem;
}
.composer-foot .hint { margin: 0; }

/* --------------------------------------------------------- evaluation -- */

/* The coaching walks through the session lens by lens, so the transcript sits
 * beside it: being told what to do differently is easier to act on next to
 * what you actually wrote. */
.eval-body {
  display: grid;
  grid-template-columns: 1fr 22rem;
  gap: 2.5rem;
  align-items: start;
}
@media (max-width: 62rem) { .eval-body { grid-template-columns: 1fr; gap: 1.5rem; } }

.eval-aside { position: sticky; top: 1.5rem; min-width: 0; }
@media (max-width: 62rem) { .eval-aside { position: static; } }
.eval-aside h2 {
  font-family: var(--font-mono); font-weight: 500;
  font-size: .72rem; letter-spacing: .1em; text-transform: uppercase;
  color: var(--ink-meta); margin-bottom: .75rem;
}
.eval-aside .transcript { gap: 1rem; }
.eval-aside .turn { font-size: .85rem; max-width: none; }
.eval-aside .turn.learner { padding: .7rem .85rem; }
.eval-aside .turn.agent p { font-size: 1rem; }
.lens-marker {
  margin: 1.25rem 0 .25rem;
  font-family: var(--font-mono);
  font-size: .7rem; letter-spacing: .1em; text-transform: uppercase;
  color: var(--accent); font-weight: 500;
  border-top: 1px solid var(--line); padding-top: .75rem;
}
.transcript > .lens-marker:first-child { margin-top: 0; border-top: 0; padding-top: 0; }
.eval-aside .turn.learner { align-self: stretch; }

.scroller {
  max-height: 68vh;
  overflow-y: auto;
  padding-right: .6rem;
}
/* This box scrolls and holds no focusable content, so it is given
 * tabindex="0" in the markup; that tab stop has to be visible when it lands. */
.scroller:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
}
@media (max-width: 62rem) { .scroller { max-height: 40vh; } }

/* The result, large, with the lens beside it. The h1 is the eyebrow ("Your
 * evaluation") so that is what a screen reader lands on and announces; the
 * tier is the display line under it. */
.eval-hero {
  display: grid;
  grid-template-columns: 13rem minmax(0, 1fr);
  gap: 3rem;
  align-items: center;
  margin-bottom: 3rem;
}
.eval-hero .lens-wrap { width: 13rem; margin: 0; }
@media (max-width: 46rem) {
  .eval-hero { grid-template-columns: 1fr; gap: 1.5rem; margin-bottom: 2rem; }
  .eval-hero .lens-wrap { width: 8rem; }
}
.tier-display {
  font-family: var(--font-display);
  font-size: clamp(2.25rem, 4vw + 1rem, 3.75rem);
  font-weight: 500;
  line-height: 1.05;
  letter-spacing: -.025em;
  margin: .5rem 0 1.25rem;
  max-width: none;
}

/* Where the tier sits among the three. The current step is marked three ways
 * -- a filled bar, bold accent text and aria-current -- so no one of them has
 * to carry it alone. */
.tier-track {
  list-style: none; padding: 0; margin: 0 0 1.5rem;
  display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: .4rem;
  max-width: 40rem;
}
.tier-track li { display: flex; flex-direction: column; gap: .5rem; margin: 0; }
.tier-track .bar { height: .5rem; border-radius: 999px; background: var(--line); }
.tier-track .label { font-size: .85rem; line-height: 1.35; color: var(--ink-faint); }
.tier-track li[aria-current] .bar { background: var(--accent); }
.tier-track li[aria-current] .label { color: var(--accent); font-weight: 600; }
/* The empty bars are --line, which is decorative and exempt; give them a real
 * edge where forced colours would otherwise leave three identical blanks. */
@media (forced-colors: active) {
  .tier-track .bar { border: 1px solid CanvasText; }
  .tier-track li[aria-current] .bar { background: Highlight; }
}

/* A <dl>, each pill a <div> holding its <dt> and <dd>. As spans in divs the
 * four pills on the review page reached a screen reader as one run of words
 * -- "Overall Needs Improvement Best next step Strong Observations credited
 * 12 / 21" -- with nothing to say which value went with which name. */
.ratings { display: flex; gap: .75rem; flex-wrap: wrap; margin: 0; }
.rating .v { margin: 0; }
.rating {
  display: inline-flex; align-items: baseline; gap: .6rem;
  padding: .5rem 1rem;
  background: var(--card);
  border: 1px solid var(--line-control);
  border-radius: 999px;
}
.rating .k { font-size: .9rem; color: var(--ink-soft); }
.rating .v { font-weight: 600; color: var(--accent); }

/* The invitation to the feedback questions. Navy in both colour schemes, like
 * the brand bar, and for the same reason: its text is chosen against navy, not
 * against the page. White holds on it, the paragraph's blue-grey 10.63:1. */
.invite {
  margin: 1.5rem 0 1rem;
  padding: 1.75rem;
  border-radius: var(--radius-lg);
  background: #00275c;
  color: #ffffff;
}
.invite h2 { color: #ffffff; margin-bottom: .5rem; }
.invite p { color: #d5ddea; }

/* ------------------------------------------------------------ consent -- */

/* The introduction on the left, the case and the consent card on the right.
 * Stacked below 62rem, introduction first, as it is in the document. */
.consent {
  grid-template-columns: minmax(0, 1fr) minmax(0, 32rem);
  gap: 4rem;
  align-items: start;
}
@media (max-width: 62rem) {
  .consent { grid-template-columns: minmax(0, 1fr); gap: 2.5rem; }
}

.views-intro { margin-top: 2.75rem; }
.views-intro .eyebrow { margin-bottom: 1rem; }
.view-cards {
  list-style: none; margin: 0; padding: 0; max-width: none;
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(13rem, 1fr));
  gap: .85rem;
}
.view-card {
  margin: 0;
  padding: 1.1rem 1.25rem 1.25rem;
  background: var(--card);
  border: 1px solid var(--line);
  border-radius: var(--radius);
}
.view-card-head { display: flex; align-items: center; justify-content: space-between; margin-bottom: .6rem; }
.view-num { font-family: var(--font-mono); font-size: .8rem; color: var(--accent); }
/* Small, so the empty sides are a shade darker than in the big lens: with the
 * page's pale --lens-upcoming a 36px ring of five blanks and one filled side
 * read as a loading spinner, not as one side of a shape. */
.lens-glyph { width: 2.25rem; height: 2.25rem; }
.lens-glyph polygon { stroke: var(--card); stroke-width: 8; }
.lens-glyph polygon[data-state="off"] { fill: #d6d1c4; }
@media (prefers-color-scheme: dark) {
  .lens-glyph polygon[data-state="off"] { fill: #3a414a; }
}
.view-card h3 {
  font-family: var(--font-display); font-weight: 500;
  font-size: 1.3rem; line-height: 1.2; letter-spacing: -.01em;
  margin: 0 0 .4rem;
}
.view-card p { margin: 0; font-size: .9rem; line-height: 1.55; color: var(--ink-soft); }

.case-card {
  margin: 0 0 1.25rem;
  background: var(--card);
  border: 1px solid var(--line);
  border-radius: var(--radius-lg);
  overflow: hidden;
}
.case-card img {
  display: block; width: 100%; height: 15rem;
  object-fit: cover; object-position: 50% 30%;
}
.case-card figcaption { display: flex; flex-direction: column; gap: .3rem; padding: 1.1rem 1.5rem 1.3rem; }
.case-card-title {
  font-family: var(--font-display); font-size: 1.4rem; line-height: 1.25;
  font-weight: 500; letter-spacing: -.01em;
}

.consent-card { margin-bottom: 0; }
.consent-card h2 { font-size: 1.65rem; }
ul.recorded { list-style: none; padding: 0; margin: 0 0 1.25rem; display: flex; flex-direction: column; gap: .65rem; }
ul.recorded li { display: flex; align-items: center; gap: .85rem; margin: 0; }
ul.recorded .icon {
  flex: none; box-sizing: border-box;
  width: 2.25rem; height: 2.25rem; padding: .55rem;
  border-radius: 10px; background: var(--tile);
  fill: none; stroke: var(--accent); stroke-width: 1.8;
  stroke-linecap: round; stroke-linejoin: round;
}
.aside-note {
  margin: 0 0 1.5rem;
  padding: 1rem 1.1rem;
  border-radius: 12px;
  background: var(--page);
}
.aside-note p { font-size: .9rem; line-height: 1.55; color: var(--ink-soft); margin-bottom: .6rem; }
.aside-note p:last-child { margin-bottom: 0; }
.aside-note strong { color: var(--ink); }
