/* Components (cards, buttons, forms, ...). Each is written for phones first, with
   @media (min-width: 48rem) for laptops. */
@layer components {
  /* ---- The app shell (shell.html, _shell.html) ---- */

  /* On laptops the sidebar and the page sit side by side; on phones the page is alone and the
     tab bar is fixed over the bottom of the screen. */
  .app-shell {
    min-height: 100dvh;

    @media (min-width: 48rem) {
      display: flex;
      align-items: flex-start;
    }
  }
  .app-frame {
    flex: 1;
    min-width: 0;
  }

  /* The slim dark bar at the top: a back link and the page's context, a status pill on the right. */
  .app-bar {
    position: sticky;
    top: 0;
    z-index: 30;
    display: flex;
    align-items: center;
    gap: var(--space-2xs);
    min-height: 3rem;
    padding-inline: var(--space-s);
    background: var(--night);
    color: var(--on-bar);
    font-size: var(--step--1);

    & a {
      color: inherit;
      text-decoration: none;
    }
    /* The default brand focus ring is too dark to see on the night bar. */
    & :focus-visible {
      outline-color: var(--on-bar);
    }
    & .back {
      display: inline-flex;
      align-items: center;
      justify-content: center;
      min-width: 2.75rem;
      min-height: 2.75rem;
      margin-inline-start: calc(-1 * var(--space-xs));
      font-size: var(--step-3);
      line-height: 1;
    }
    & .context {
      min-width: 0;
      overflow: hidden;
      text-overflow: ellipsis;
      white-space: nowrap;
      color: color-mix(in srgb, var(--on-bar) 80%, transparent);
    }
    /* A lesson's course line above its title, both cut short rather than wrapped. */
    & :is(.kicker, .title) {
      display: block;
      overflow: hidden;
      text-overflow: ellipsis;
    }
    & .kicker {
      font-size: var(--step--2);
      letter-spacing: 0.06em;
      text-transform: uppercase;
    }
    & .title {
      color: var(--on-bar);
      font-weight: 600;
    }
    /* The trailing area: the status pill, then any controls the page passes in. */
    & .end {
      display: flex;
      flex-shrink: 0;
      align-items: center;
      gap: var(--space-2xs);
      margin-inline-start: auto;
    }
    & .action {
      display: inline-flex;
      align-items: center;
      min-height: 2.75rem;
      padding-inline: var(--space-xs);
      border: 1px solid color-mix(in srgb, var(--on-bar) 35%, transparent);
      border-radius: var(--radius-s);
      background: transparent;
      color: inherit;
      font-weight: 600;
      cursor: pointer;
    }
    & .action:hover {
      background: color-mix(in srgb, var(--on-bar) 12%, transparent);
    }
    /* The slide view is for laptops and presenting; phones keep the reading view. */
    & [data-view-switch="slides"] {
      display: none;
    }
    /* A drop-down hung from the bar (the lesson's contents), in page colours. */
    & .menu {
      position: absolute;
      top: 100%;
      inset-inline: 0;
      max-width: var(--measure-wide);
      margin-inline: auto;
      padding: var(--space-2xs) var(--space-s);
      color: var(--ink);
    }
    & .menu :focus-visible {
      outline-color: var(--brand);
    }
    & .menu ol {
      max-height: 70dvh;
      overflow-y: auto;
      padding: var(--space-3xs);
      list-style: none;
      box-shadow: var(--shadow-2);
    }
    & .menu a {
      display: flex;
      gap: var(--space-xs);
      min-height: 2.75rem;
      padding: var(--space-2xs) var(--space-xs);
      border-radius: var(--radius-s);
      font-weight: 500;
    }
    & .menu a:hover {
      background: var(--paper-2);
    }
    /* The part's number ("1.3"), in a column so the titles line up. */
    & .menu .label {
      flex: none;
      width: 3.5rem;
      color: var(--amber);
      font-family: var(--font-serif);
      font-weight: 400;
    }
    /* The exam timer: a pill that stays in view while the student scrolls a long question. */
    & .timer {
      padding: 0.125rem var(--space-xs);
      border-radius: var(--radius-pill);
      background: var(--paper-2);
      color: var(--ink);
      font-family: var(--font-mono);
      font-size: var(--step-1);
      font-variant-numeric: tabular-nums;
    }
    /* The last minute (prep.js's examTimer sets data-urgent through Alpine). */
    & .timer[data-urgent] {
      background: var(--red-soft);
      color: var(--red);
    }
    & .status {
      flex-shrink: 0;
      padding: 0.125rem var(--space-2xs);
      border-radius: var(--radius-pill);
      background: var(--brand-soft);
      color: var(--brand-strong);
      font-size: var(--step--2);
      font-weight: 600;
      white-space: nowrap;
    }
  }

  @media (min-width: 48rem) {
    .app-bar [data-view-switch="slides"] {
      display: inline-flex;
    }
  }

  /* With nothing but the wordmark to show, the bar gives way to the sidebar's own wordmark. */
  @media (min-width: 48rem) {
    .app-sidebar + .app-frame > .app-bar[data-kind="wordmark"] {
      display: none;
    }
  }

  .wordmark {
    display: inline-flex;
    align-items: center;
    min-height: 2.75rem;
    font-family: var(--font-serif);
    font-size: var(--step-1);
    font-weight: 600;
    letter-spacing: -0.01em;
    white-space: nowrap;
  }

  /* Phones: the five destinations along the bottom, within thumb reach. */
  .tab-bar {
    position: fixed;
    inset-inline: 0;
    bottom: 0;
    z-index: 30;
    padding-bottom: env(safe-area-inset-bottom);
    background: var(--surface);
    border-top: 1px solid var(--rule);

    & ul {
      display: grid;
      grid-template-columns: repeat(5, 1fr);
      height: var(--tab-bar-height);
      padding: 0;
      list-style: none;
    }
    & a {
      display: flex;
      flex-direction: column;
      align-items: center;
      justify-content: center;
      gap: 0.125rem;
      height: 100%;
      color: var(--muted);
      font-size: var(--step--2);
      font-weight: 500;
      text-decoration: none;
    }
    & a[aria-current="page"] {
      color: var(--brand);
      font-weight: 600;
    }
    & svg {
      width: 1.5rem;
      height: 1.5rem;
    }
    @media (min-width: 48rem) {
      display: none;
    }
  }

  /* Laptops: the same destinations down a dark left column, the theme switch at its foot. */
  .app-sidebar {
    display: none;
    background: var(--night);
    color: var(--on-bar);

    @media (min-width: 48rem) {
      position: sticky;
      top: 0;
      display: flex;
      flex: 0 0 var(--sidebar-width);
      flex-direction: column;
      gap: var(--space-s);
      height: 100dvh;
      overflow-y: auto;
      padding: var(--space-xs);
    }
    & .wordmark {
      padding-inline: var(--space-xs);
      font-size: var(--step-0);
      color: inherit;
      text-decoration: none;
    }
    & :focus-visible {
      outline-color: var(--on-bar);
    }
    & ul {
      display: flex;
      flex: 1;
      flex-direction: column;
      gap: var(--space-3xs);
      padding: 0;
      list-style: none;
    }
    /* Me sits apart at the bottom, as the account does in most apps. */
    & li:last-child {
      margin-top: auto;
    }
    & ul a {
      display: flex;
      align-items: center;
      gap: var(--space-xs);
      min-height: 2.75rem;
      padding-inline: var(--space-xs);
      border-radius: var(--radius-s);
      color: color-mix(in srgb, var(--on-bar) 78%, transparent);
      font-weight: 500;
      text-decoration: none;
    }
    & ul a:hover {
      background: color-mix(in srgb, var(--on-bar) 8%, transparent);
      color: var(--on-bar);
    }
    & a[aria-current="page"] {
      background: color-mix(in srgb, var(--on-bar) 14%, transparent);
      color: var(--on-bar);
      font-weight: 600;
    }
  }

  /* The page itself. Below 48rem it leaves room for the fixed tab bar, notch area included. */
  .app-main {
    max-width: 64rem;
    margin-inline: auto;
    padding: var(--space-l) var(--space-s);
    padding-bottom: calc(var(--tab-bar-height) + env(safe-area-inset-bottom) + var(--space-l));

    @media (min-width: 48rem) {
      padding-bottom: var(--space-l);
    }
  }

  /* Inherits its colour, so it reads on the dark sidebar and on the page alike. */
  .theme-button {
    display: inline-flex;
    flex-shrink: 0;
    align-items: center;
    gap: var(--space-2xs);
    min-height: 2.75rem;
    padding-inline: var(--space-xs);
    border: 1px solid color-mix(in srgb, currentColor 35%, transparent);
    border-radius: var(--radius-s);
    background: transparent;
    font-size: var(--step--1);
    cursor: pointer;

    &:hover {
      background: color-mix(in srgb, currentColor 10%, transparent);
    }
  }

  /* ---- Me (me.html): grouped rows of account details and settings ---- */
  .settings {
    overflow: hidden;
    background: var(--surface);
    border: 1px solid var(--rule);
    border-radius: var(--radius-m);

    & .row {
      display: flex;
      flex-wrap: wrap;
      align-items: center;
      justify-content: space-between;
      gap: var(--space-2xs) var(--space-s);
      width: 100%;
      min-height: 3rem;
      padding: var(--space-2xs) var(--space-s);
      border: 0;
      background: none;
      color: var(--ink);
      text-align: start;
      text-decoration: none;
      overflow-wrap: anywhere;
    }
    & .row + .row {
      border-top: 1px solid var(--rule);
    }
    & :is(a, button).row {
      color: var(--brand);
      font-weight: 600;
      cursor: pointer;
    }
    & :is(a, button).row:hover {
      background: var(--paper-2);
    }
    & .label {
      font-weight: 600;
    }
  }

  /* Printed pages (formula sheets above all) are the content alone. */
  @media print {
    .app-bar,
    .tab-bar,
    .app-sidebar {
      display: none;
    }
    .app-main {
      padding: 0;
    }
  }

  /* ---- Reading size: the Aa button and the prose it resizes ---- */
  .reading-button {
    min-height: 2.75rem;
    min-width: 2.75rem;
    padding-inline: var(--space-xs);
    border: 1px solid color-mix(in srgb, currentColor 35%, transparent);
    border-radius: var(--radius-s);
    background: transparent;
    font-family: var(--font-serif);
    cursor: pointer;

    &[aria-pressed="true"] {
      background: color-mix(in srgb, currentColor 15%, transparent);
    }
  }

  /* ---- Prose: study pages, written content. ---- */
  .prose {
    font-size: var(--prose-size);
    line-height: var(--prose-leading);
    overflow-wrap: break-word;

    & > * + * {
      margin-top: var(--space-s);
    }
    & h2 {
      margin-top: var(--space-l);
      font-size: var(--step-3);
    }
    & h3 {
      margin-top: var(--space-m);
      font-size: var(--step-2);
    }
    & ul {
      list-style: disc;
      padding-inline-start: var(--space-l);
    }
    & ol {
      list-style: decimal;
      padding-inline-start: var(--space-l);
    }
    & a {
      text-underline-offset: 4px;
    }
    & table {
      display: block;
      max-width: 100%;
      overflow-x: auto;
    }
    & :is(th, td) {
      padding: var(--space-3xs) var(--space-2xs);
      border: 1px solid var(--rule);
    }
    & code {
      font-size: 0.9em;
    }
    & pre {
      padding: var(--space-xs);
      overflow-x: auto;
      border: 1px solid var(--rule);
      border-radius: var(--radius-m);
      background: var(--surface);
    }
    & :is(svg, img) {
      height: auto;
      max-width: 100%;
    }
    & details.deeper {
      padding: var(--space-2xs) var(--space-s);
      border: 1px solid var(--rule);
      border-radius: var(--radius-m);
      background: var(--surface);
    }
    & details.deeper > summary {
      font-weight: 600;
      cursor: pointer;
    }
  }

  /* ---- Tiles: dense, task-first blocks with a coloured left edge. Put them in a grid.
     Set the colour with data-accent; data-size="wide" spans the whole row. ---- */
  .tile {
    --accent: var(--brand);
    display: block;
    padding: var(--space-xs);
    border: 1px solid var(--rule);
    border-inline-start: 4px solid var(--accent);
    border-radius: var(--radius-m);
    background: var(--surface);
    color: var(--ink);
    text-decoration: none;

    &[data-accent="brand"] { --accent: var(--brand); }
    &[data-accent="amber"] { --accent: var(--amber); }
    &[data-accent="green"] { --accent: var(--green); }
    &[data-accent="violet"] { --accent: var(--violet); }
    &[data-size="wide"] {
      grid-column: 1 / -1;
    }
    & .figure {
      display: block;
      color: var(--accent);
      font-family: var(--font-serif);
      font-size: var(--step-2);
      font-weight: 700;
      line-height: 1.2;
    }
    /* "/ 30" after a score. */
    & .figure small {
      color: var(--muted);
      font-size: var(--step-0);
      font-weight: 500;
    }
    & .label {
      display: block;
      color: var(--muted);
      font-size: var(--step--2);
    }
    /* Headings inside a tile are titles, not page sections. */
    & :is(h2, h3) {
      font-size: var(--step-2);
    }
    & :is(h2, h3) a {
      color: inherit;
      text-decoration: none;
    }
    & :is(h2, h3) a:hover {
      color: var(--brand);
    }
    & p {
      color: var(--ink-soft);
    }
    /* A tile's call to action ("Start reading →"), in its accent. */
    & .go {
      color: var(--accent);
      font-weight: 600;
    }
    /* The tile's top line: an eyebrow on the left, a badge on the right. */
    & .head {
      display: flex;
      align-items: center;
      justify-content: space-between;
      gap: var(--space-2xs);
    }
    & .track {
      color: var(--accent);
      font-size: var(--step--1);
      font-weight: 600;
    }
    & details {
      padding-top: var(--space-2xs);
      border-top: 1px solid var(--rule);
      font-size: var(--step--1);
    }
    & details ol {
      margin-top: var(--space-3xs);
      padding-inline-start: var(--space-m);
      color: var(--ink-soft);
    }
    & summary {
      padding-block: var(--space-3xs);
      color: var(--accent);
      font-weight: 600;
      cursor: pointer;
    }
    /* A tile with a .foot is a column whose foot (its buttons) sits at the bottom, so the
       tiles in a row line up. */
    &:has(> .foot) {
      display: flex;
      flex-direction: column;
    }
    & > .foot {
      margin-top: auto;
      padding-top: var(--space-xs);
    }
    /* Not ready yet (a course in development): a grey edge. */
    &[data-accent="neutral"] { --accent: var(--muted); }
  }
  /* A tile alone in a grid cell fills it, so the tiles in a row are the same height. */
  .grid > li > .tile {
    height: 100%;
  }
  a.tile:hover {
    box-shadow: var(--shadow-2);
  }
  a.tile:hover .go {
    text-decoration: underline;
  }

  /* ---- Eyebrow: the small capitals above a heading ("Step 1", "Course 3"). In a tile it takes
     the tile's accent. ---- */
  .eyebrow {
    display: block;
    color: var(--accent, var(--amber));
    font-size: var(--step--2);
    font-weight: 600;
    letter-spacing: 0.08em;
    text-transform: uppercase;
  }

  /* ---- Lede: the paragraph under a page's title that says what the page is for. ---- */
  .lede {
    max-width: var(--measure);
    color: var(--ink-soft);
    font-size: var(--step-1);
  }

  /* ---- Hero: the landing page's opening, on the paper, with an amber rule under the title. ---- */
  .hero {
    padding-block: var(--space-m) var(--space-l);

    & h1 {
      max-width: 20ch;
      font-size: clamp(var(--step-3), 6vw, 3rem);
      line-height: 1.12;
    }
    & h1::after {
      content: "";
      display: block;
      width: 4.5rem;
      height: 4px;
      margin-top: var(--space-s);
      border-radius: var(--radius-pill);
      background: var(--amber);
    }
    & p {
      max-width: 36rem;
      color: var(--ink-soft);
      font-size: var(--step-1);
    }
  }

  /* ---- Pager: previous and next, side by side on laptops; next sits on the right even alone.
     Its links are tiles with rel="prev" / rel="next". ---- */
  .pager {
    display: grid;
    gap: var(--space-xs);

    @media (min-width: 48rem) {
      grid-template-columns: 1fr 1fr;

      & [rel="next"] {
        grid-column: 2;
        text-align: end;
      }
    }
  }

  /* Under a lesson: its notebook and the pager, set off like the lesson's own sections. */
  .lesson-footer {
    padding-top: var(--space-m);
    border-top: 1px solid var(--rule);
  }

  /* ---- Toc: a book's contents, one row per page, with a badge or lock at the end.
     A nested row (a dive-deeper page under its chapter) carries data-depth="2". ---- */
  .toc {
    padding: 0;
    overflow: hidden;
    list-style: none;
    border: 1px solid var(--rule);
    border-radius: var(--radius-m);
    background: var(--surface);

    & li + li {
      border-top: 1px solid var(--rule);
    }
    & a {
      display: flex;
      align-items: center;
      gap: var(--space-2xs);
      min-height: 2.75rem;
      padding: var(--space-2xs) var(--space-s);
      color: var(--ink);
      text-decoration: none;
    }
    & a:hover {
      background: var(--paper-2);
      color: var(--brand);
    }
    & [data-depth="2"] a {
      padding-inline-start: var(--space-l);
      font-size: var(--step--1);
    }
    & .title {
      flex: 1;
      min-width: 0;
    }
    & svg {
      flex: none;
      width: 1rem;
      height: 1rem;
      color: var(--muted);
    }
  }

  /* ---- Continue: the blue banner that resumes where the student left off. ---- */
  .continue {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: var(--space-s);
    min-height: 3.5rem;
    padding: var(--space-xs) var(--space-s);
    border-radius: var(--radius-m);
    background: var(--brand);
    color: var(--surface);
    text-decoration: none;
    box-shadow: var(--shadow-1);

    &:hover {
      background: var(--brand-strong);
    }
    &:focus-visible {
      outline-color: var(--ink);
    }
    /* "Pick up where you left off" above the step itself. */
    & small {
      display: block;
      opacity: 0.85;
      font-size: var(--step--1);
    }
    & b {
      display: block;
      font-size: var(--step-1);
    }
  }

  /* ---- Mastery: a topic's name above its meter and percentage (the dashboard). ---- */
  .mastery {
    font-size: var(--step--1);

    & .topic {
      display: block;
      color: var(--ink-soft);
    }
  }

  /* ---- Meter: a thin bar. Templates write
     <div class="meter" data-tone="good" role="meter" aria-label="Chapters read"
          aria-valuemin="0" aria-valuemax="100" aria-valuenow="62"><span style="width:62%"></span></div>
     so assistive technology gets the value and the span only draws it. ---- */
  .meter {
    --tone: var(--brand);
    height: 4px;
    overflow: hidden;
    border-radius: var(--radius-pill);
    background: var(--brand-soft);

    &[data-tone="good"] { --tone: var(--green); }
    &[data-tone="fair"] { --tone: var(--amber); }
    &[data-tone="weak"] { --tone: var(--red); }
    & span {
      display: block;
      height: 100%;
      border-radius: inherit;
      background: var(--tone);
    }
  }

  /* ---- Buttons: primary unless data-kind says otherwise. 44px tall at least. ---- */
  .button {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    gap: var(--space-2xs);
    min-height: 2.75rem;
    padding: var(--space-2xs) var(--space-s);
    border: 1px solid var(--brand);
    border-radius: var(--radius-m);
    background: var(--brand);
    color: var(--surface);
    font-weight: 600;
    line-height: 1.2;
    text-align: center;
    text-decoration: none;
    cursor: pointer;

    &:hover {
      background: var(--brand-strong);
      border-color: var(--brand-strong);
    }
    &[data-kind="quiet"] {
      background: transparent;
      color: var(--brand);
      border-color: var(--rule);
    }
    &[data-kind="quiet"]:hover {
      background: var(--brand-soft);
    }
    &[data-kind="danger"] {
      background: var(--red);
      border-color: var(--red);
    }
    &[data-kind="danger"]:hover {
      background: var(--red);
      border-color: var(--red);
      filter: brightness(0.9);
    }
    /* A toggle that is on (the exam's Flag button). */
    &[aria-pressed="true"] {
      border-color: var(--amber);
      background: var(--amber-soft);
      color: var(--amber);
    }
    &:disabled,
    &[aria-disabled="true"] {
      opacity: 0.55;
      cursor: not-allowed;
    }
    /* Full width on phones, natural width on laptops. */
    &[data-size="large"] {
      width: 100%;
      min-height: 3.25rem;
      font-size: var(--step-1);

      @media (min-width: 48rem) {
        width: auto;
      }
    }
  }
  /* ---- Choice: a large rounded answer. A button or label holding <span class="letter">. ---- */
  .choice {
    display: flex;
    align-items: center;
    gap: var(--space-xs);
    width: 100%;
    min-height: 3.25rem;
    padding: var(--space-2xs) var(--space-xs);
    border: 1px solid var(--rule);
    border-radius: var(--radius-m);
    background: var(--surface);
    color: var(--ink);
    text-align: start;

    /* Only answers you can pick react to the pointer; a review's <li> choices don't. */
    &:is(label, button) {
      cursor: pointer;
    }
    &:is(label, button):hover {
      border-color: var(--brand);
    }
    /* A label around a visually hidden radio is "chosen" while its radio is checked, and
       shows the radio's keyboard focus. */
    &:has(:checked) {
      border-color: var(--brand);
      background: var(--brand-soft);
    }
    &:has(:focus-visible) {
      outline: 2px solid var(--brand);
      outline-offset: 2px;
    }
    /* The choice's text (rendered Markdown) takes the rest of the row. */
    & > .prose {
      flex: 1;
      min-width: 0;
    }
    &[data-state="chosen"] {
      border-color: var(--brand);
      background: var(--brand-soft);
    }
    &[data-state="right"] {
      border-color: var(--green);
      background: var(--green-soft);
    }
    &[data-state="wrong"] {
      border-color: var(--red);
      background: var(--red-soft);
    }
    & .letter {
      display: grid;
      flex: none;
      place-items: center;
      width: 1.75rem;
      height: 1.75rem;
      border: 1.5px solid var(--muted);
      border-radius: var(--radius-pill);
      font-size: var(--step--1);
      font-weight: 600;
    }
    &:is([data-state="chosen"], :has(:checked)) .letter {
      border-color: var(--brand);
      background: var(--brand);
      color: var(--surface);
    }
    &[data-state="right"] .letter {
      border-color: var(--green);
      background: var(--green);
      color: var(--surface);
    }
    &[data-state="wrong"] .letter {
      border-color: var(--red);
      background: var(--red);
      color: var(--surface);
    }
  }
  .choice + .choice {
    margin-top: var(--space-2xs);
  }

  /* ---- Panel, chips, badge ---- */
  .panel {
    padding: var(--space-s);
    border: 1px solid var(--rule);
    border-radius: var(--radius-m);
    background: var(--surface);
    box-shadow: var(--shadow-1);
  }

  /* Filter chips; mark the current one with aria-current. They scroll sideways on phones. */
  .chips {
    display: flex;
    gap: var(--space-2xs);
    overflow-x: auto;
    padding-block: var(--space-3xs);

    @media (min-width: 48rem) {
      flex-wrap: wrap;
      overflow-x: visible;
    }
    & a {
      display: inline-flex;
      flex: none;
      align-items: center;
      min-height: 2.75rem;
      padding-inline: var(--space-xs);
      border: 1px solid var(--rule);
      border-radius: var(--radius-pill);
      background: var(--surface);
      color: var(--ink-soft);
      font-size: var(--step--1);
      text-decoration: none;
    }
    & a[aria-current] {
      border-color: var(--brand);
      background: var(--brand-soft);
      color: var(--brand-strong);
      font-weight: 600;
    }
  }

  /* Badges are styled only with data-tone. deck.css has its own bare .badge (inside
     .deck .vsc), which these rules must not touch, so no bare .badge rule exists. */
  .badge[data-tone] {
    display: inline-block;
    padding: 0.125rem var(--space-2xs);
    border-radius: var(--radius-pill);
    background: var(--paper-2);
    color: var(--ink-soft);
    font-size: var(--step--2);
    font-weight: 600;
    line-height: 1.4;
    white-space: nowrap;

    &[data-tone="draft"] { background: var(--amber-soft); color: var(--amber); }
    &[data-tone="review"] { background: var(--violet-soft); color: var(--violet); }
    &[data-tone="live"] { background: var(--green-soft); color: var(--green); }
    &[data-tone="public"] { background: var(--brand-soft); color: var(--brand-strong); }
    /* Exam prep: the meter's tones (a right or wrong answer, a topic's standing, review due). */
    &[data-tone="good"] { background: var(--green-soft); color: var(--green); }
    &[data-tone="fair"] { background: var(--amber-soft); color: var(--amber); }
    &[data-tone="weak"] { background: var(--red-soft); color: var(--red); }
  }

  /* ---- Field: label, input, hint and error stacked; data-invalid marks a bad value. ---- */
  .field {
    display: flex;
    flex-direction: column;
    gap: var(--space-3xs);

    & label {
      font-weight: 600;
    }
    & :is(input, select, textarea) {
      width: 100%;
      min-height: 2.75rem;
      padding: var(--space-2xs) var(--space-xs);
      border: 1px solid var(--muted);
      border-radius: var(--radius-m);
      background: var(--surface);
    }
    & .hint {
      color: var(--muted);
      font-size: var(--step--1);
    }
    & .error {
      color: var(--red);
      font-size: var(--step--1);
      font-weight: 600;
    }
    &[data-invalid] :is(input, select, textarea) {
      border-color: var(--red);
      background: var(--red-soft);
    }
  }

  /* A password input with its Show/Hide toggle laid over the right end. */
  .password-input {
    position: relative;

    & input {
      padding-inline-end: 4.5rem;
    }
    & button {
      position: absolute;
      inset-block: 0;
      inset-inline-end: 0;
      min-width: 3.5rem;
      padding-inline: var(--space-xs);
      border: 0;
      background: none;
      color: var(--brand);
      font-size: var(--step--1);
      font-weight: 600;
      cursor: pointer;
    }
    & button:hover {
      text-decoration: underline;
    }
  }

  /* The password tips: a pop-over under the field (and the same box, open, without JavaScript). */
  .tips {
    padding: var(--space-xs);
    border: 1px solid var(--rule);
    border-radius: var(--radius-m);
    background: var(--surface);
    color: var(--ink-soft);
    font-size: var(--step--1);

    & ul {
      padding-inline-start: var(--space-m);
      list-style: disc;
    }
    & summary {
      color: var(--amber);
      font-weight: 600;
      cursor: pointer;
    }
  }
  /* With JavaScript the tips hang from a button that opens them on hover, focus and tap. */
  .tips-menu {
    position: relative;

    & > button {
      min-height: 2.75rem;
      padding: 0;
      border: 0;
      background: none;
      color: var(--amber);
      font-size: var(--step--1);
      font-weight: 600;
      text-align: start;
      cursor: pointer;
    }
    & > button:hover {
      text-decoration: underline;
    }
    & .tips {
      position: absolute;
      inset-inline: 0;
      z-index: 10;
      margin-top: var(--space-3xs);
      box-shadow: var(--shadow-2);

      @media (min-width: 48rem) {
        inset-inline-end: auto;
        width: 24rem;
      }
    }
  }

  /* ---- The JavaScript warning above the page (_script_check.html); slides.css overlays it. ---- */
  .script-banner {
    padding: var(--space-2xs) var(--space-s);
    border-bottom: 1px solid var(--amber);
    background: var(--amber-soft);
    color: var(--ink);
    font-size: var(--step--1);

    & > div {
      display: flex;
      align-items: flex-start;
      gap: var(--space-xs);
      max-width: 64rem;
      margin-inline: auto;
    }
    & p {
      flex: 1;
      min-width: 0;
    }
    & button {
      flex-shrink: 0;
      min-height: 2.75rem;
      padding-inline: var(--space-2xs);
      border: 0;
      background: none;
      font-weight: 500;
      text-decoration: underline;
      text-underline-offset: 4px;
      cursor: pointer;
    }
  }

  /* ---- Data table: the wrapper (.data-table) scrolls sideways, its table is styled inside. ---- */
  .data-table {
    max-width: 100%;
    overflow-x: auto;
    border: 1px solid var(--rule);
    border-radius: var(--radius-m);
    background: var(--surface);

    & table {
      width: 100%;
      font-size: var(--step--1);
    }
    & :is(th, td) {
      padding: var(--space-2xs) var(--space-xs);
      border-bottom: 1px solid var(--rule);
      text-align: start;
      white-space: nowrap;
    }
    & th {
      background: var(--paper-2);
      color: var(--muted);
      font-size: var(--step--2);
      text-transform: uppercase;
      letter-spacing: 0.04em;
    }
    & tr:last-child td {
      border-bottom: 0;
    }
  }

  /* ---- Callout: always carries data-kind (note is the plain one). The deck's own
     .deck .callout (.tip, .warn, ...) has no data-kind and is not touched. ---- */
  .callout[data-kind] {
    --kind: var(--brand);
    padding: var(--space-xs) var(--space-s);
    border-inline-start: 4px solid var(--kind);
    border-radius: var(--radius-m);
    background: var(--brand-soft);

    &[data-kind="note"] {
      --kind: var(--muted);
      background: var(--paper-2);
    }
    &[data-kind="example"] {
      --kind: var(--green);
      background: var(--green-soft);
    }
    &[data-kind="deeper"] {
      --kind: var(--violet);
      background: var(--violet-soft);
    }
    &[data-kind="error"] {
      --kind: var(--red);
      background: var(--red-soft);
    }
    /* A right answer. (Any kind not listed here, such as "brand", is the plain blue.) */
    &[data-kind="success"] {
      --kind: var(--green);
      background: var(--green-soft);
    }
  }

  /* ---- Revision diffs ---- */
  /* The box that holds a diff, one <span> per line; it scrolls sideways itself. */
  .diff {
    min-width: 0;
    overflow-x: auto;
    padding: var(--space-xs);
    border: 1px solid var(--rule);
    border-radius: var(--radius-m);
    background: var(--surface);
    font-size: var(--step--2);
  }
  .diff[data-wrap] {
    white-space: pre-wrap;
  }
  .diff-add {
    background: var(--green-soft);
    color: var(--green);
  }
  .diff-del {
    background: var(--red-soft);
    color: var(--red);
    text-decoration: line-through;
  }

  /* ---- Admin ---- */
  /* A row of text with its actions at the end (a page in the outline, a revision and its
     Restore button); the first child takes the room and wraps before the actions do. */
  .list-row {
    display: flex;
    flex-wrap: wrap;
    align-items: flex-start;
    justify-content: space-between;
    gap: var(--space-2xs);

    & > :first-child {
      flex: 1;
      min-width: min(100%, 12rem);
    }
  }
  /* Code-like fields: a Markdown box, a slug, a check expression. */
  .field[data-kind="code"] :is(input, textarea) {
    font-family: var(--font-mono);
    font-size: var(--step--1);
  }
  .field :is(input, textarea):is(:disabled, [readonly]) {
    color: var(--muted);
  }
  /* The Markdown editor: Write and Preview tabs on phones, side by side on laptops. */
  .editor-tabs {
    display: flex;
    gap: var(--space-2xs);
    margin-bottom: var(--space-xs);

    & .button {
      flex: 1;
    }
    @media (min-width: 64rem) {
      display: none;
    }
  }
  .editor-panes {
    display: grid;
    gap: var(--space-m);

    @media (min-width: 64rem) {
      grid-template-columns: 1fr 1fr;
    }
  }
  .editor-pane {
    min-width: 0;

    &[data-shown="no"] {
      display: none;
    }
    @media (min-width: 64rem) {
      height: calc(100dvh - 12rem);
      overflow-y: auto;
      padding-inline-end: var(--space-2xs);

      &[data-shown="no"] {
        display: block;
      }
    }
  }

  /* A lesson preview's render errors: each is a button that puts the caret on its line. */
  .line-errors {
    padding: 0;
    list-style: none;

    & button {
      display: block;
      inline-size: 100%;
      min-block-size: 2.75rem;
      padding: var(--space-3xs) 0;
      border: 0;
      background: none;
      color: inherit;
      font: inherit;
      font-family: var(--font-mono);
      font-size: var(--step--1);
      text-align: start;
      text-decoration: underline dotted;
      text-underline-offset: 0.2em;
      cursor: pointer;
    }
    & button:hover {
      color: var(--red);
    }
  }

  /* ---- Exam prep (aet-testprep) ---- */

  /* Under the app bar while staff practice as a demo student, since answers go to its history. */
  .demo-bar {
    display: flex;
    align-items: center;
    gap: var(--space-xs);
    padding: var(--space-3xs) var(--space-s);
    border-bottom: 1px solid var(--amber);
    background: var(--amber-soft);
    font-size: var(--step--1);

    & .who {
      min-width: 0;
      overflow: hidden;
      text-overflow: ellipsis;
      white-space: nowrap;
      color: var(--ink-soft);
    }
    & a {
      display: inline-flex;
      align-items: center;
      min-height: 2.75rem;
      margin-inline-start: auto;
      font-weight: 600;
    }
  }

  /* Staff's demo-student picker: stacked on phones, select and button in a row on laptops. */
  .switch-form {
    display: grid;
    gap: var(--space-xs);

    @media (min-width: 48rem) {
      grid-template-columns: 1fr auto;
    }
  }

  /* An ability meter with its percentage after it (_macros.html ability_bar). */
  .ability {
    display: flex;
    align-items: center;
    gap: var(--space-xs);

    & .meter {
      flex: 1;
    }
    & .value {
      min-width: 3.5ch;
      font-size: var(--step--1);
      font-variant-numeric: tabular-nums;
      text-align: end;
    }
  }

  /* The line above a practice question: the topic, "Topic mastery 58%", and its meter. */
  .topic-line {
    display: flex;
    flex-direction: column;
    gap: var(--space-3xs);
    color: var(--ink-soft);
    font-size: var(--step--1);

    & .line {
      display: flex;
      flex-wrap: wrap;
      align-items: baseline;
      justify-content: space-between;
      gap: var(--space-3xs) var(--space-xs);
    }
    & b {
      color: var(--ink);
    }
  }

  /* A practice-exam question's header: number and topic, the Flag button on the right. */
  .question-head {
    display: flex;
    align-items: flex-start;
    justify-content: space-between;
    gap: var(--space-xs);
    font-size: var(--step--1);

    & > div {
      display: flex;
      flex-direction: column;
      min-width: 0;
    }
    & .button {
      flex-shrink: 0;
    }
  }

  /* Previous and Next: two equal halves, Next always on the right. */
  .pair {
    display: grid;
    grid-template-columns: 1fr 1fr;
    gap: var(--space-xs);
  }

  /* The practice exam's question list. On phones it folds behind a toggle (Alpine sets
     data-open on the section); from 48rem it is always open and the toggle is a heading. */
  .exam-questions {
    & .head {
      display: flex;
      align-items: center;
      gap: var(--space-2xs);
    }
    & .head > a {
      margin-inline-start: auto;
    }
    & .heading {
      display: none;
      font-weight: 600;
    }
    & .body {
      display: none;
      margin-top: var(--space-xs);
    }
    &[data-open] .body {
      display: block;
    }
    @media (min-width: 48rem) {
      & .toggle {
        display: none;
      }
      & .heading,
      & .body {
        display: block;
      }
    }
    & .body > nav > p {
      margin-bottom: var(--space-2xs);
      font-size: var(--step--1);
    }
  }

  /* Numbered squares, one per question: filled once answered, ringed where you are, an amber
     dot when flagged. Also lists the blank and flagged ones on the review screen. */
  .question-grid {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(2.75rem, 1fr));
    gap: var(--space-3xs);
    padding: 0;
    list-style: none;

    & a {
      position: relative;
      display: flex;
      align-items: center;
      justify-content: center;
      min-height: 2.75rem;
      border: 1px solid var(--rule);
      border-radius: var(--radius-s);
      background: var(--surface);
      color: var(--ink-soft);
      font-size: var(--step--1);
      font-variant-numeric: tabular-nums;
      text-decoration: none;
    }
    & a:hover {
      border-color: var(--brand);
    }
    & a[data-answered] {
      border-color: var(--brand);
      background: var(--brand-soft);
      color: var(--ink);
      font-weight: 600;
    }
    & a[aria-current] {
      outline: 2px solid var(--brand);
      outline-offset: 1px;
    }
    & .flag-dot {
      position: absolute;
      top: 3px;
      right: 3px;
      width: 0.5rem;
      height: 0.5rem;
      border-radius: var(--radius-pill);
      background: var(--amber);
    }
  }

  /* A topic's skills on the exam page: a dot coloured by the success rate, name, tally. */
  .skill-list {
    display: flex;
    flex-direction: column;
    gap: var(--space-2xs);
    margin-block: var(--space-2xs);
    padding: 0;
    list-style: none;

    & li {
      display: flex;
      align-items: flex-start;
      gap: var(--space-xs);
    }
    & .dot {
      flex: none;
      width: 0.625rem;
      height: 0.625rem;
      margin-top: 0.4em;
      border-radius: var(--radius-pill);
      background: var(--rule);
    }
    & .dot[data-tone="good"] { background: var(--green); }
    & .dot[data-tone="fair"] { background: var(--amber); }
    & .dot[data-tone="weak"] { background: var(--red); }
    & .name {
      flex: 1;
      min-width: 0;
      color: var(--ink-soft);
    }
    & .tally {
      flex: none;
      color: var(--muted);
      font-variant-numeric: tabular-nums;
    }
  }

  /* A results page's questions, each a <details> whose summary is one row. */
  .solution-list {
    display: flex;
    flex-direction: column;
    gap: var(--space-2xs);
    padding: 0;
    list-style: none;

    & details {
      border: 1px solid var(--rule);
      border-radius: var(--radius-m);
      background: var(--surface);
    }
    & summary {
      display: flex;
      align-items: center;
      gap: var(--space-xs);
      min-height: 3rem;
      padding: var(--space-2xs) var(--space-s);
      list-style: none;
      cursor: pointer;
    }
    & summary::-webkit-details-marker {
      display: none;
    }
    & .topic {
      flex: 1;
      min-width: 0;
      overflow: hidden;
      text-overflow: ellipsis;
      white-space: nowrap;
    }
    & .answer {
      flex: none;
      color: var(--muted);
      font-size: var(--step--1);
    }
    & .body {
      padding: var(--space-s);
      border-top: 1px solid var(--rule);
    }
  }

  /* A secondary section one tap away under a solution ("Why this question?", "Ask the tutor",
     "Report a problem"); data-kind="quiet" for the muted one. */
  .disclosure {
    font-size: var(--step--1);

    & > summary {
      display: flex;
      align-items: center;
      min-height: 2.75rem;
      color: var(--brand);
      font-weight: 600;
      cursor: pointer;
    }
    &[data-kind="quiet"] > summary {
      color: var(--muted);
      font-weight: 400;
    }
    &[data-kind="quiet"] > summary:hover {
      color: var(--brand);
    }
  }

  /* A radio button and its label, as one 44px tap target. */
  .radio {
    display: flex;
    align-items: center;
    gap: var(--space-xs);
    min-height: 2.75rem;
    cursor: pointer;

    & input {
      width: 1rem;
      height: 1rem;
      accent-color: var(--brand);
    }
  }

  /* The tutor conversation: the student's messages on the right in blue, the tutor's on the left. */
  .tutor {
    padding: var(--space-xs);
    border: 1px solid var(--rule);
    border-radius: var(--radius-m);
    background: var(--paper);

    & .message {
      padding: var(--space-2xs) var(--space-xs);
      border-radius: var(--radius-m);
    }
    & .message[data-role="student"] {
      margin-inline-start: var(--space-xl);
      background: var(--brand-soft);
    }
    & .message[data-role="tutor"] {
      margin-inline-end: var(--space-xl);
      border: 1px solid var(--rule);
      background: var(--surface);
    }
    & .remaining {
      margin-inline-start: auto;
      font-size: var(--step--2);
    }
  }

  /* ---- Certificates site: a bare header and one column of content, no app navigation. ---- */
  .site-header {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: var(--space-xs);
    padding: var(--space-3xs) var(--space-s);
    border-bottom: 1px solid var(--rule);

    & .wordmark {
      color: var(--ink);
      text-decoration: none;
    }
    & .wordmark span {
      color: var(--brand);
    }
    & a:not(.wordmark) {
      font-size: var(--step--1);
      font-weight: 600;
      color: var(--ink-soft);
    }
  }
  .site-main {
    padding: var(--space-m) var(--space-s) var(--space-xl);
    overflow-wrap: anywhere;

    & .callout h1 {
      font-size: var(--step-2);
    }
  }

  /* The certificate itself stays on white paper in dark mode, as a printed one would.
     A revoked one is greyed out. */
  .certificate-sheet {
    overflow: hidden;
    border: 1px solid var(--rule);
    border-radius: var(--radius-m);
    background: #fff;
    box-shadow: var(--shadow-1);

    & > svg {
      display: block;
      width: 100%;
      height: auto;
    }
    &[data-revoked] {
      opacity: 0.4;
      filter: grayscale(1);
    }
  }

  /* Download and share buttons: stacked on phones, in a row on laptops. */
  .certificate-actions {
    display: grid;
    gap: var(--space-2xs);

    @media (min-width: 40rem) {
      display: flex;
      flex-wrap: wrap;
    }
  }

  /* Label and value pairs; the labels sit beside the values from tablet width. */
  .facts {
    display: grid;
    gap: var(--space-3xs) var(--space-l);
    padding-top: var(--space-m);
    border-top: 1px solid var(--rule);
    font-size: var(--step--1);

    & dt {
      color: var(--muted);
    }
    & dd {
      margin: 0 0 var(--space-2xs);
      overflow-wrap: anywhere;
    }
    & dd[data-kind="id"] {
      font-family: var(--font-mono);
    }
    @media (min-width: 40rem) {
      grid-template-columns: auto 1fr;

      & dd {
        margin: 0;
      }
    }
  }

  /* ---- Typst figures: aet/markup.py wraps the inline SVG that aet/typst_render.py
     compiles in figure.typst; a figure that failed to compile shows its error instead. ---- */
  .typst {
    overflow-x: auto;
  }
  .typst-figure {
    margin-inline: auto;
  }
  .typst-error {
    padding: var(--space-xs) var(--space-s);
    border-inline-start: 4px solid var(--red);
    border-radius: var(--radius-m);
    background: var(--red-soft);

    & pre {
      font-size: var(--step--1);
      white-space: pre-wrap;
    }
  }

  /* ---- Sandboxed frame (_sandbox.html): unreviewed staff HTML in an opaque-origin iframe.
     app.js sets its height from the frame's own report; until then it is a short box. ---- */
  .sandbox-frame {
    display: block;
    inline-size: 100%;
    block-size: 8rem;
    border: 0;
  }
  /* A Reveal canvas has no content height to report, so it fills the screen between the notice
     above it and the lesson footer below it. */
  .sandbox-frame[data-fit="viewport"] { block-size: calc(100dvh - 5rem); }
  /* That footer (lesson_slides.html): slides.css, which pins it over the slide, loads only inside
     the frame, so below the frame it is a quiet strip of its own. */
  .sandbox-frame + .deck-footer {
    display: flex;
    gap: 1.25em;
    padding: 0.2rem 1.375rem;
    border-block-start: 1px solid var(--rule);
    font-size: 12px;
    letter-spacing: 0.06em;
  }
  .sandbox-frame + .deck-footer a { color: var(--muted); text-decoration: none; }
  .sandbox-frame + .deck-footer a:is(:hover, :focus-visible) { color: var(--brand); text-decoration: underline; }
  /* The frame's own page: transparent so the panel around it shows through, and no
     viewport-tall minimum, or the height it reports could never shrink. */
  body[data-frame] {
    display: flow-root;
    min-height: 0;
    margin: 0;
    background: transparent;
  }
}
