/* ==========================================================================
   CareerStart.ai — Learning Modules
   Single stylesheet, ten numbered sections. Design tokens are the prototype's
   (docs/prototype.html), with two corrections for contrast — see
   docs/brand-guidelines.md before changing any token.

   Structure follows app-DoMoreWithSlidesCom-design: the page IS its slides.
   The one departure is navigation — this site's nav is a left rail, not a top
   bar, because a module has a table of contents and a marketing site does
   not. See docs/site-architecture.md.
   ========================================================================== */

/* --------------------------------------------------------------------------
   1. Tokens
   -------------------------------------------------------------------------- */

/* The two families are variable fonts: one file each covers the whole weight
   range this site uses. Vendored, not fetched — CLAUDE.md rule 7 forbids a
   third-party request, and the footer says so on every page. */
@font-face {
  font-family: "Plus Jakarta Sans";
  font-style: normal;
  font-weight: 400 800;
  font-display: swap;
  src: url("../fonts/plus-jakarta-sans.woff2") format("woff2");
}

@font-face {
  font-family: "Sora";
  font-style: normal;
  font-weight: 700 800;
  font-display: swap;
  src: url("../fonts/sora.woff2") format("woff2");
}

:root {
  /* Brand — the prototype's green ramp. One hue, not two: this is a single
     product, and a second brand colour would have nothing to mean. */
  --green-300: #7ee2a8;
  --green-400: #4fc07a;
  --green-500: #3aaa5c;
  --green-600: #1e7e3e; /* the brand green */
  --green-700: #176331;
  --green-800: #134d27;
  --green-050: #e8f5ec;
  --green-glow: rgba(30, 126, 62, 0.22);

  /* The functional signal, used for assessments and cautions only. Amber in
     the prototype; it is not a second brand colour and never carries chrome. */
  --amber-600: #b45309;
  --amber-050: #fffbeb;
  --amber-200: #fcd34d;

  /* Tinted neutrals — grey with a hint of blue, as the prototype has them. */
  --ink-900: #0f1923;
  --ink-800: #1c2b3a;
  --ink-700: #4a5568;
  --ink-600: #6b7787; /* darkened from the prototype's #8a96a3 — see below */
  --ink-300: #c9d6e2;
  --ink-200: #e3e9ef;
  --ink-100: #eaeef3;
  --ink-050: #f4f6f9;
  --white: #ffffff;

  /* Semantic */
  --bg: var(--ink-050);
  --bg-alt: var(--ink-100);
  --bg-sunken: var(--ink-100);
  --surface: var(--white);
  --border: var(--ink-200);
  --border-strong: var(--ink-300);
  --text: var(--ink-700);
  --text-muted: var(--ink-600);
  --text-heading: var(--ink-900);
  --link: var(--green-800);
  --link-hover: var(--green-600);
  --accent: var(--green-600);
  --accent-alt: var(--green-800);
  --good: var(--green-700);
  --warn: var(--amber-600);
  --shadow-color: 15 25 35;

  /* Type. Sora carries display, Plus Jakarta Sans carries everything else —
     the prototype's pairing. Both stacks end in the system font, so a reader
     who blocks the woff2 still gets the right shape of page. */
  --font-sans: "Plus Jakarta Sans", "Segoe UI", system-ui, -apple-system,
    "Helvetica Neue", Arial, sans-serif;
  --font-display: "Sora", "Plus Jakarta Sans", "Segoe UI", system-ui,
    -apple-system, "Helvetica Neue", Arial, sans-serif;
  --font-mono: "Cascadia Code", "SFMono-Regular", Consolas, "Liberation Mono",
    monospace;

  /* Fluid type scale */
  --fs-xs: 0.8125rem;
  --fs-sm: 0.9375rem;
  --fs-base: clamp(1rem, 0.97rem + 0.15vw, 1.0625rem);
  --fs-lg: clamp(1.125rem, 1.06rem + 0.3vw, 1.25rem);
  --fs-xl: clamp(1.25rem, 1.15rem + 0.5vw, 1.5rem);
  --fs-2xl: clamp(1.5rem, 1.3rem + 1vw, 2rem);
  --fs-3xl: clamp(1.875rem, 1.5rem + 1.7vw, 2.75rem);

  /* Space */
  --sp-1: 0.25rem;
  --sp-2: 0.5rem;
  --sp-3: 0.75rem;
  --sp-4: 1rem;
  --sp-5: 1.5rem;
  --sp-6: 2rem;
  --sp-7: 3rem;
  --sp-8: 4rem;

  --radius-sm: 7px;
  --radius: 11px;
  --radius-lg: 15px;
  --radius-pill: 999px;

  --shadow-xs: 0 1px 2px rgb(var(--shadow-color) / 0.05);
  --shadow: 0 4px 14px rgb(var(--shadow-color) / 0.07);
  --shadow-lg: 0 14px 38px rgb(var(--shadow-color) / 0.13);
  --ease: cubic-bezier(0.22, 1, 0.36, 1);

  --measure: 68ch;
  /* The slide track's cap. It has to stay above the 960px stacking
     threshold, or it would re-impose the problem the shell width solves. */
  --container: 1140px;
  --rail-w: 292px;
}

/* Dark mode is one block of token overrides and nothing else. The green steps
   two shades lighter: --green-600 on a dark ground is 2.6:1, which fails
   everything. --green-400 clears 7.9:1. */
@media (prefers-color-scheme: dark) {
  :root {
    --bg: #0d1219;
    --bg-alt: #151c26;
    --bg-sunken: #1a222d;
    --surface: #141b24;
    --border: #2a3542;
    --border-strong: #3c4a5a;
    --text: #ccd5df;
    --text-muted: #93a0af;
    --text-heading: #f0f4f8;
    --link: var(--green-300);
    --link-hover: var(--green-400);
    --accent: var(--green-400);
    --accent-alt: var(--green-300);
    --good: var(--green-400);
    --warn: #e8b45f;
    --green-050: #14301f;
    --amber-050: #2b2413;
    --amber-200: #6b5a20;
    --shadow-color: 0 0 0;
  }
}

/* --------------------------------------------------------------------------
   2. Reset & base
   -------------------------------------------------------------------------- */

*,
*::before,
*::after {
  box-sizing: border-box;
  margin: 0;
  padding: 0;
}

/* The `hidden` attribute has to actually hide things.
   The UA rule for it is `[hidden] { display: none }`, which any class that
   sets `display` outranks — so `<div class="grid-2" hidden>` stayed visible,
   and the compound-interest inputs appeared for readers with JavaScript off,
   where they do nothing. This is the standard defensive rule and it is worth
   having site-wide: every `[data-js-only]` control on the site depends on it. */
[hidden] {
  display: none !important;
}

html {
  scroll-behavior: smooth;
  -webkit-text-size-adjust: 100%;
}

body {
  font-family: var(--font-sans);
  font-size: var(--fs-base);
  line-height: 1.6;
  color: var(--text);
  background: var(--bg);
  -webkit-font-smoothing: antialiased;
}

h1,
h2,
h3,
h4 {
  font-family: var(--font-display);
  font-weight: 800;
  line-height: 1.2;
  letter-spacing: -0.02em;
  color: var(--text-heading);
}

p {
  max-width: var(--measure);
}

a {
  color: var(--link);
  text-underline-offset: 0.18em;
}

a:hover {
  color: var(--link-hover);
}

img,
svg {
  max-width: 100%;
}

:focus-visible {
  outline: 2.5px solid var(--accent);
  outline-offset: 2px;
  border-radius: 4px;
}

/* One rule, honoured everywhere. slides/scripts.js switches reveal's own
   transitions off for the same reason. */
@media (prefers-reduced-motion: reduce) {
  *,
  *::before,
  *::after {
    animation-duration: 0.01ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.01ms !important;
    scroll-behavior: auto !important;
  }
}

/* --------------------------------------------------------------------------
   3. Layout primitives

   The shell is the whole difference between this site and the design repo's:
   a two-column grid with the navigation rail on the left, sticky, and the
   content beside it. Below the rail breakpoint it becomes one column and the
   rail turns into a drawer.
   -------------------------------------------------------------------------- */

.skip-link {
  position: absolute;
  left: var(--sp-4);
  top: -6rem;
  z-index: 200;
  padding: var(--sp-3) var(--sp-5);
  background: var(--surface);
  border: 2px solid var(--accent);
  border-radius: var(--radius);
  font-weight: 700;
  text-decoration: none;
  transition: top 0.15s var(--ease);
}

.skip-link:focus {
  top: var(--sp-4);
}

/* 1440, not the 1240 this started at, and the number is derived rather than
   chosen.

   A slide stacks below 960px of TRACK. The track is the shell less its own
   padding (48), the rail (292), the grid gap (24) and the slide section's
   padding (48) - so the shell has to reach 1372 before an article can show a
   slide in its wide layout at all. At 1240 it never could: even on a 4K
   display the track was 828px and every slide was stacked, which meant the
   1280x800 layout the slides are authored in only ever appeared in a deck.

   1440 gives a 1028px track, comfortably clear of the threshold, and still
   stacks at 1280 of viewport - which is the right place for it to stack,
   because that is where the rail and a readable slide stop both fitting.
   See docs/responsive-slides.md. */
.shell {
  max-width: 1440px;
  margin-inline: auto;
  padding: var(--sp-5) var(--sp-5) var(--sp-8);
  display: grid;
  grid-template-columns: var(--rail-w) minmax(0, 1fr);
  gap: var(--sp-5);
  align-items: start;
}

.main {
  min-width: 0;
}

.visually-hidden {
  position: absolute !important;
  width: 1px;
  height: 1px;
  padding: 0;
  margin: -1px;
  overflow: hidden;
  clip: rect(0, 0, 0, 0);
  white-space: nowrap;
  border: 0;
}

@media (max-width: 74.99rem) {
  .shell {
    grid-template-columns: 266px minmax(0, 1fr);
  }
}

/* --------------------------------------------------------------------------
   4. Header

   Deliberately slim: the logo, the breadcrumb, the Article / Slides switch,
   and — below the rail breakpoint — the button that opens the rail. There is
   no "Book" call to action: this is a course, not a sales page.
   -------------------------------------------------------------------------- */

.site-header {
  position: sticky;
  top: 0;
  z-index: 70;
  background: var(--surface);
  border-bottom: 1px solid var(--border);
  box-shadow: var(--shadow-xs);
}

.site-header__inner {
  max-width: 1440px;
  margin-inline: auto;
  padding-inline: var(--sp-5);
  min-height: 60px;
  display: flex;
  align-items: center;
  gap: var(--sp-4);
}

.brand {
  display: flex;
  align-items: center;
  gap: var(--sp-2);
  font-family: var(--font-display);
  font-weight: 800;
  font-size: 1.03rem;
  color: var(--accent);
  text-decoration: none;
  flex-shrink: 0;
}

.brand img {
  display: block;
  width: 28px;
  height: 28px;
}

.crumb {
  font-family: var(--font-display);
  font-size: 0.845rem;
  font-weight: 700;
  color: var(--text-heading);
  border-left: 1px solid var(--border);
  padding-left: var(--sp-4);
}

/* The Article / Slides switch. Two links and a span, never a scripted toggle:
   it survives JavaScript being off, middle-click opens the other view in a
   tab, and the current half carries aria-current rather than being disabled.
   The deck side of this pair lives in slides/styles.css section 8 — change
   the two together. */
.view-switch {
  display: flex;
  align-items: stretch;
  margin-left: auto;
  border: 1.5px solid var(--border);
  border-radius: var(--radius-pill);
  overflow: hidden;
  background: var(--surface);
  flex-shrink: 0;
}

.view-switch__opt {
  display: flex;
  align-items: center;
  gap: var(--sp-2);
  padding: var(--sp-2) var(--sp-4);
  font-size: var(--fs-xs);
  font-weight: 700;
  color: var(--text-muted);
  text-decoration: none;
  white-space: nowrap;
  transition: color 0.18s var(--ease), background 0.18s var(--ease);
}

.view-switch__opt:hover {
  color: var(--accent);
  background: var(--bg-alt);
}

.view-switch__opt[aria-current] {
  color: var(--accent);
  background: var(--green-050);
}

.view-switch__opt svg {
  width: 15px;
  height: 15px;
  flex-shrink: 0;
}

/* The rail toggle. Hidden above the rail breakpoint, where the rail is simply
   there. Below it, this is the only way to reach the navigation — which is
   why section 5 keeps the rail in the document and merely collapses it. */
.rail-toggle {
  display: none;
  align-items: center;
  gap: var(--sp-2);
  font-family: inherit;
  font-size: var(--fs-xs);
  font-weight: 700;
  color: var(--text);
  background: var(--surface);
  border: 1.5px solid var(--border);
  border-radius: var(--radius-pill);
  padding: var(--sp-2) var(--sp-4);
  cursor: pointer;
}

.rail-toggle:hover {
  border-color: var(--accent);
  color: var(--accent);
}

.rail-toggle__bars {
  position: relative;
  display: block;
  width: 15px;
  height: 2px;
  background: currentColor;
  border-radius: 2px;
}

.rail-toggle__bars::before,
.rail-toggle__bars::after {
  content: "";
  position: absolute;
  left: 0;
  display: block;
  width: 15px;
  height: 2px;
  background: currentColor;
  border-radius: 2px;
}

.rail-toggle__bars::before {
  top: -5px;
}

.rail-toggle__bars::after {
  top: 5px;
}

@media (max-width: 61.24rem) {
  .site-header__inner {
    flex-wrap: wrap;
    padding-block: var(--sp-3);
  }

  .crumb {
    border-left: 0;
    padding-left: 0;
  }

  .rail-toggle {
    display: inline-flex;
    margin-left: auto;
  }

  .view-switch {
    order: 3;
    margin-left: 0;
    width: 100%;
  }

  .view-switch__opt {
    flex: 1;
    justify-content: center;
  }
}

/* --------------------------------------------------------------------------
   5. The rail — this site's navigation

   The prototype's left rail, made into real navigation. It carries the module
   head, the reader's progress, and every page of the module as a link. It is
   duplicated in every HTML file; there is no templating. Change one, change
   all of them, and move aria-current="page" to the right link.

   Two things are load-bearing:

     The rail is a <nav> full of <a>, not a scripted list. With JavaScript off
     every page of the module is still reachable from every other page, which
     is CLAUDE.md rule 8. The progress bar is the only scripted part, and it
     is hidden until script fills it in.

     Below the breakpoint the rail is NOT display:none. It collapses to zero
     height with overflow hidden and the toggle expands it. The design repo
     logged a known bug where its nav became unreachable with JavaScript off
     on a narrow screen; this avoids it by keeping the rail open by default
     and letting script close it on load.
   -------------------------------------------------------------------------- */

.rail {
  position: sticky;
  top: 82px;
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius-lg);
  box-shadow: var(--shadow-xs);
  overflow: hidden;
  max-height: calc(100vh - 104px);
  display: flex;
  flex-direction: column;
}

.rail__head {
  padding: var(--sp-4);
  border-bottom: 1px solid var(--border);
  background: linear-gradient(160deg, var(--ink-900), var(--ink-800));
  color: var(--white);
}

.rail__eyebrow {
  font-size: 0.6rem;
  font-weight: 800;
  letter-spacing: 0.11em;
  text-transform: uppercase;
  color: rgba(255, 255, 255, 0.62);
}

.rail__title {
  font-family: var(--font-display);
  font-size: 1rem;
  font-weight: 800;
  line-height: 1.25;
  margin-top: var(--sp-1);
  color: var(--white);
}

.rail__sub {
  font-size: 0.72rem;
  color: rgba(255, 255, 255, 0.72);
  margin-top: var(--sp-1);
}

/* Progress. Written by assets/js/site.js from what the reader has completed,
   which lives in localStorage. Hidden entirely when script is off — an empty
   progress bar claiming 0% would be a lie about a reader who has read
   everything. */
.rail__progress[hidden] {
  display: none;
}

.rail__bar {
  height: 6px;
  background: rgba(255, 255, 255, 0.15);
  border-radius: var(--radius-pill);
  margin-top: var(--sp-3);
  overflow: hidden;
}

.rail__bar i {
  display: block;
  height: 100%;
  background: linear-gradient(90deg, var(--green-500), var(--green-300));
  border-radius: var(--radius-pill);
  transition: width 0.7s var(--ease);
}

.rail__count {
  display: flex;
  justify-content: space-between;
  font-size: 0.66rem;
  font-weight: 600;
  color: rgba(255, 255, 255, 0.72);
  margin-top: var(--sp-1);
}

.rail__body {
  overflow-y: auto;
  padding-block: var(--sp-2) var(--sp-3);
}

.rail__unit {
  font-size: 0.625rem;
  font-weight: 800;
  letter-spacing: 0.09em;
  text-transform: uppercase;
  color: var(--text-muted);
  padding: var(--sp-3) var(--sp-4) var(--sp-1);
}

.rail__list {
  list-style: none;
  padding: 0;
  margin: 0;
}

.rail__link {
  display: flex;
  align-items: flex-start;
  gap: var(--sp-3);
  padding: var(--sp-2) var(--sp-4);
  border-left: 3px solid transparent;
  text-decoration: none;
  color: inherit;
  transition: background 0.15s var(--ease);
}

.rail__link:hover {
  background: var(--bg);
  color: inherit;
}

.rail__link[aria-current="page"] {
  background: var(--green-050);
  border-left-color: var(--accent);
}

/* The completion mark. Empty until script says otherwise — see .is-done. */
.rail__mark {
  width: 19px;
  height: 19px;
  border-radius: var(--radius-pill);
  border: 1.8px solid var(--border-strong);
  background: var(--surface);
  flex-shrink: 0;
  margin-top: 2px;
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: 0.62rem;
  line-height: 1;
  color: var(--white);
  transition: background 0.2s var(--ease), border-color 0.2s var(--ease);
}

.rail__link[aria-current="page"] .rail__mark {
  border-color: var(--accent);
}

.rail__link.is-done .rail__mark {
  background: var(--accent);
  border-color: var(--accent);
}

.rail__name {
  font-size: 0.78rem;
  font-weight: 600;
  line-height: 1.4;
  color: var(--text);
}

.rail__link[aria-current="page"] .rail__name {
  font-weight: 700;
  color: var(--link);
}

.rail__meta {
  font-size: 0.66rem;
  color: var(--text-muted);
  margin-top: 1px;
}

/* An assessment row. Amber is the functional signal, not a brand colour. */
.rail__link--check .rail__name {
  color: var(--warn);
}

.rail__link--check.is-done .rail__mark {
  background: var(--warn);
  border-color: var(--warn);
}

@media (max-width: 61.24rem) {
  .shell {
    grid-template-columns: minmax(0, 1fr);
  }

  .rail {
    position: static;
    max-height: none;
  }

  /* Closed only once script has proved the toggle exists. Without JS the rail
     stays open and every page is reachable. */
  .rail[data-open="false"] {
    max-height: 0;
    border-width: 0;
    margin: 0;
    padding: 0;
    overflow: hidden;
  }
}

/* --------------------------------------------------------------------------
   6. Buttons
   -------------------------------------------------------------------------- */

.btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: var(--sp-2);
  font-family: inherit;
  font-size: var(--fs-sm);
  font-weight: 700;
  line-height: 1.2;
  padding: var(--sp-3) var(--sp-5);
  border-radius: var(--radius-pill);
  border: 1.5px solid transparent;
  text-decoration: none;
  cursor: pointer;
  transition: transform 0.2s var(--ease), box-shadow 0.2s var(--ease),
    border-color 0.2s var(--ease), color 0.2s var(--ease);
}

.btn--primary {
  background: linear-gradient(135deg, var(--green-600), var(--green-800));
  color: var(--white);
  box-shadow: 0 4px 14px var(--green-glow);
}

.btn--primary:hover:not(:disabled) {
  color: var(--white);
  transform: translateY(-1px);
  box-shadow: 0 8px 22px var(--green-glow);
}

.btn--primary:disabled {
  background: var(--bg-alt);
  color: var(--text-muted);
  box-shadow: none;
  cursor: not-allowed;
  transform: none;
}

.btn--secondary {
  background: var(--surface);
  color: var(--text);
  border-color: var(--border);
}

.btn--secondary:hover {
  border-color: var(--accent);
  color: var(--link);
}

/* The previous / next pair at the foot of every article page. */
.pager {
  display: flex;
  align-items: center;
  gap: var(--sp-3);
  flex-wrap: wrap;
  padding: var(--sp-5);
  border-top: 1px solid var(--border);
}

.pager__next {
  margin-left: auto;
}

/* --------------------------------------------------------------------------
   7. Slides

   The page IS its deck. Each section holds one slide as inline <svg> — the
   same markup as assets/img/slides/<deck>/slideN.svg, and the same markup
   slides/*.html inlines. See docs/slide-images.md.

   Inline rather than <img> for one reason: an SVG behind an <img> is its own
   document and cannot see this file, so it would need a frozen copy of the
   palette and could never follow a token change. Inlined, every colour below
   is the page's own token — which is what makes a slide sit in the page
   instead of on top of it. The slides carry no background of their own.
   -------------------------------------------------------------------------- */

.article {
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius-lg);
  box-shadow: var(--shadow-xs);
  overflow: hidden;
}

.slide-section {
  padding-block: var(--sp-4);
  padding-inline: var(--sp-4);
  border-bottom: 1px solid var(--border);
}

.slide-section--alt {
  background: var(--bg-alt);
}

@media (min-width: 48rem) {
  .slide-section {
    padding-block: var(--sp-5);
    padding-inline: var(--sp-5);
  }
}

/* The track. It caps the width, centres the slide, and — since it is what
   assets/js/site.js observes — is the box the responsive layout measures. A
   slide is a self-contained component and what matters to it is the space it
   actually has, not how wide the browser happens to be. It does not scroll. */
.slide-track {
  max-width: var(--container);
  margin-inline: auto;
}

.slide-svg {
  display: block;
  width: 100%;
  height: auto;
}

/* --------------------------------------------------------------------------
   7a. The narrow layout

   A slide is a fixed 1280x800 canvas, so it scales with the space it is given
   and its text scales with it — at 360px that puts the smallest type near
   4px, and no CSS fixes it, because SVG text does not re-wrap.

   It re-wraps here. Two mechanisms, separate on purpose:

     Blocks move.  Anything repositionable is a <g data-lg="…">, and the rule
       below is the only place a transform is applied. The coordinates in the
       file stay absolute and stay the wide layout, which is what
       tools/svg-to-pptx.ps1 reads.

     Prose flows.  A line too wide for the 356-unit column is not <text> at
       all: it is a <switch><foreignObject> block of real HTML that wraps to
       whatever width it is given, with a hand-broken <text> fallback behind
       it. See docs/responsive-slides.md.

   With JavaScript off neither happens and every page renders as it would have
   anyway — .is-narrow is only ever added by script.
   -------------------------------------------------------------------------- */

.slide-svg.is-narrow [data-lg] {
  transform: translate(var(--nx, 0px), var(--ny, 0px));
}

/* Stacked, a slide is a phone-shaped column ~436 units wide. Edge to edge on a
   900px tablet that would be a 2x zoom of a phone screen, so it is capped. */
.slide-svg.is-narrow {
  max-width: 460px;
  margin-inline: auto;
}

.slide-svg foreignObject {
  overflow: visible;
}

/* The reset here is load-bearing, not tidiness. A flow is an HTML box living
   inside the page, so every page rule that matches it applies — and the slide
   vocabulary and the page vocabulary share class names. The box declares what
   it is rather than hoping nothing reaches it. */
.slide-svg .flow {
  max-width: none;
  min-width: 0;
  margin: 0;
  padding: 0;
  text-align: left;
  font-family: var(--font-sans);
  line-height: 1.45;
  color: var(--text);
  -webkit-hyphens: none;
  hyphens: none;
}

/* No text-wrap in the wide layout, deliberately: a flow's box is exactly as
   wide as the longest line the copy was hand-broken at, which is what makes
   the browser break it where the writer did. Stacked there is no authored
   break to preserve, so `pretty` earns its place there and only there. */
.slide-svg.is-narrow .flow {
  text-wrap: pretty;
  max-width: 356px;
}

/* A flow sets its own leading, and these are not free choices: they are the
   pitches the slides were hand-broken at, so a block that becomes a flow
   keeps the vertical rhythm it had. */
.slide-svg .flow.h1 {
  line-height: 1.1;
}

.slide-svg .flow.h2 {
  line-height: 1.15;
}

.slide-svg .flow.lede {
  line-height: 1.38;
}

.slide-svg .flow.punch {
  line-height: 1.6;
}

/* The flow equivalent of text-anchor="middle". */
.slide-svg .flow.mid {
  text-align: center;
}

.slide-svg .flow strong {
  font-weight: 700;
  color: var(--text-heading);
}

.slide-svg .flow em {
  font-style: italic;
}

/* Display sizes are set for a 1120-unit measure. In a 356-unit column a 60px
   headline gets about twelve characters to the line, so the display classes —
   and only those — step down. Scoped to .flow so a <text> that does fit the
   narrow column keeps the size it was authored at. */
.slide-svg.is-narrow .flow.h1 {
  font-size: 34px;
  letter-spacing: -0.6px;
}

.slide-svg.is-narrow .flow.h2 {
  font-size: 28px;
  letter-spacing: -0.4px;
}

.slide-svg.is-narrow .flow.lede {
  font-size: 20px;
}

.slide-svg.is-narrow .flow.punch {
  font-size: 20px;
}

.slide-svg.is-narrow .flow.num {
  font-size: 40px;
}

/* --------------------------------------------------------------------------
   8. The slide's own vocabulary

   These class names are the slide's, not the page's, and they are duplicated
   in two other places: slides/styles.css section 1 (pointing at the deck's
   --d-* tokens, which resolve to these same values) and inside each .svg file
   (so the file still previews when opened on its own). Change one, change all
   three — docs/slide-images.md has the check.

   Every class that sets a colour sets `fill` and `color` together. The same
   class dresses two different things: an SVG <text>, which takes `fill`, and
   the HTML inside a <foreignObject>, which takes `color` and ignores `fill`
   entirely. Drop the `color` half and flowing text silently falls back to
   whatever it inherited, which is nearly right and therefore hard to spot.
   -------------------------------------------------------------------------- */

.slide-svg text {
  font-family: var(--font-sans);
  fill: var(--text);
}

/* The gradient stops live in the page's .slide-defs block, a sibling of the
   slides rather than a child, so these cannot be scoped to .slide-svg. The
   stops also carry literal stop-color attributes as a floor: CSS still wins,
   so dark mode works, but a selector mistake degrades to the light palette
   rather than to black. */
.slide-defs .gs-a {
  stop-color: var(--green-500);
}

.slide-defs .gs-b {
  stop-color: var(--green-800);
}

.slide-svg .grad {
  fill: url(#g-slide);
}

/* The same treatment on flowing text. An HTML box cannot reference an SVG
   paint server, so the ramp is re-declared as a CSS gradient clipped to the
   glyphs. background-clip: text needs a transparent colour to show through,
   which means the text has no colour at all where the clip is not honoured —
   hence the forced-colors block. */
.slide-svg .flow.grad,
.slide-svg .flow .grad {
  background-image: linear-gradient(90deg, var(--green-500), var(--green-800));
  -webkit-background-clip: text;
  background-clip: text;
  color: transparent;
}

@media (forced-colors: active) {
  .slide-svg .flow.grad,
  .slide-svg .flow .grad {
    background-image: none;
    color: CanvasText;
  }
}

/* Display type is Sora; everything else is Plus Jakarta Sans. That split is
   the prototype's and it is why .eyebrow, .h1, .h2, .h3, .punch and .num name
   --font-display while .body, .sm and .xs do not. */
.slide-svg .eyebrow {
  font-family: var(--font-sans);
  font-size: 19px;
  font-weight: 800;
  letter-spacing: 1.8px;
  text-transform: uppercase;
  fill: var(--accent);
  color: var(--accent);
}

.slide-svg .head {
  font-family: var(--font-sans);
  font-size: 16px;
  font-weight: 800;
  letter-spacing: 1.4px;
  text-transform: uppercase;
  fill: var(--text-muted);
  color: var(--text-muted);
}

.slide-svg .h1 {
  font-family: var(--font-display);
  font-size: 60px;
  font-weight: 800;
  letter-spacing: -1.2px;
  fill: var(--text-heading);
  color: var(--text-heading);
}

.slide-svg .h2 {
  font-family: var(--font-display);
  font-size: 42px;
  font-weight: 800;
  letter-spacing: -0.8px;
  fill: var(--text-heading);
  color: var(--text-heading);
}

.slide-svg .h3 {
  font-family: var(--font-display);
  font-size: 23px;
  font-weight: 800;
  fill: var(--text-heading);
  color: var(--text-heading);
}

.slide-svg .lede {
  font-size: 24px;
  fill: var(--text-muted);
  color: var(--text-muted);
}

.slide-svg .body {
  font-size: 19px;
}

.slide-svg .sm {
  font-size: 17px;
}

.slide-svg .xs {
  font-size: 15px;
}

.slide-svg .muted {
  fill: var(--text-muted);
  color: var(--text-muted);
}

.slide-svg .b {
  font-weight: 700;
  fill: var(--text-heading);
  color: var(--text-heading);
}

.slide-svg .i {
  font-style: italic;
}

.slide-svg .punch {
  font-family: var(--font-display);
  font-size: 22px;
  font-weight: 800;
  fill: var(--text-heading);
  color: var(--text-heading);
}

.slide-svg .num {
  font-family: var(--font-display);
  font-size: 52px;
  font-weight: 800;
  fill: var(--accent);
  color: var(--accent);
}

.slide-svg .card {
  fill: var(--surface);
  stroke: var(--border);
  stroke-width: 1.5;
}

/* --bg-sunken, not --bg-alt: a panel has to stay visible on an alternating
   section, whose background already is --bg-alt. */
.slide-svg .panel {
  fill: var(--bg-sunken);
  stroke: var(--border);
  stroke-width: 1.5;
}

/* The tinted surface an assessment or a caution sits on. */
.slide-svg .panel-warn {
  fill: var(--amber-050);
  stroke: var(--amber-200);
  stroke-width: 1.5;
}

.slide-svg .panel-good {
  fill: var(--green-050);
  stroke: var(--green-500);
  stroke-width: 1.5;
}

/* The icon tile in the top-right of a card. The gradient is the brand ramp at
   low alpha over whatever surface is behind it, so the tile follows the theme
   the way the slide does. The glyphs are built from <line> and <circle> only:
   no slide contains a <path>, and tools/svg-to-pptx.ps1 reads no shape other
   than those two, the rect and the text — a path would export as nothing. */
.slide-svg .tile {
  fill: url(#g-slide-tile);
  stroke: none;
}

.slide-svg .tile-glyph {
  fill: none;
  stroke: var(--link);
  stroke-width: 2;
  stroke-linecap: round;
  stroke-linejoin: round;
}

.slide-svg .feature {
  stroke: var(--accent);
  stroke-width: 3;
}

.slide-svg .rule {
  stroke: var(--accent);
  stroke-width: 5;
  stroke-linecap: round;
}

.slide-svg .hair {
  stroke: var(--border);
  stroke-width: 1.5;
}

.slide-svg .good {
  fill: var(--good);
  color: var(--good);
}

.slide-svg .warn {
  fill: var(--warn);
  color: var(--warn);
}

.slide-svg .accent {
  fill: var(--link);
  color: var(--link);
}

.slide-svg .qbar {
  fill: var(--accent);
}

.slide-svg .tbl {
  font-size: 14px;
}

.slide-svg .tblhead {
  font-size: 14px;
  font-weight: 700;
  fill: var(--text-heading);
  color: var(--text-heading);
}

/* Bars in a drawn chart — the budget stack and the growth columns. They are
   plain rects so the PowerPoint export sees them. */
.slide-svg .bar-fixed {
  fill: var(--ink-800);
}

.slide-svg .bar-flex {
  fill: var(--warn);
}

.slide-svg .bar-future {
  fill: var(--good);
}

.slide-svg .bar-rest {
  fill: var(--border-strong);
}

.slide-svg .pill-live {
  fill: none;
  stroke: var(--good);
  stroke-width: 1.5;
}

.slide-svg .pill-soon {
  fill: none;
  stroke: var(--warn);
  stroke-width: 1.5;
}

.slide-svg .pill-text-live {
  font-size: 14px;
  font-weight: 700;
  letter-spacing: 0.8px;
  fill: var(--good);
  color: var(--good);
}

.slide-svg .pill-text-soon {
  font-size: 14px;
  font-weight: 700;
  letter-spacing: 0.8px;
  fill: var(--warn);
  color: var(--warn);
}

/* The call-to-action shapes. This gradient does not move between themes:
   white clears 4.5:1 at both ends only on these two shades. */
.slide-svg .btn-fill {
  fill: url(#g-slide-btn);
}

.slide-svg .btn-ghost {
  fill: none;
  stroke: var(--border-strong);
  stroke-width: 1.5;
}

.slide-svg .btn-label {
  font-size: 20px;
  font-weight: 700;
  fill: var(--white);
  color: var(--white);
}

.slide-svg .btn-label-ghost {
  font-size: 20px;
  font-weight: 700;
  fill: var(--link);
  color: var(--link);
}

/* Links drawn into a slide. The <text> inside gives each its accessible name. */
.slide-svg a {
  cursor: pointer;
}

.slide-svg a:focus-visible {
  outline: 3px solid var(--accent);
  outline-offset: 3px;
  border-radius: 4px;
}

.slide-svg a:hover .btn-label-ghost,
.slide-svg a:focus-visible .btn-label-ghost {
  fill: var(--link-hover);
}

.slide-svg a:hover .btn-ghost,
.slide-svg a:focus-visible .btn-ghost {
  stroke: var(--accent);
}

/* Hidden, but it must stay in the layout tree: display:none on an ancestor
   makes a referenced gradient unresolvable in some engines. */
.slide-defs {
  position: absolute;
  width: 0;
  height: 0;
  overflow: hidden;
}

@media print {
  .slide-track {
    break-inside: avoid;
  }
}

/* --------------------------------------------------------------------------
   9. Prose pages

   The assessments, the practice activities and 404 are the pages that are NOT
   slides — a quiz is not slide content and drawing one would make it a
   picture of a quiz. They use this section's vocabulary instead, which is the
   prototype's, mapped onto the tokens in section 1.
   -------------------------------------------------------------------------- */

.prose {
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius-lg);
  box-shadow: var(--shadow-xs);
  padding: var(--sp-6) var(--sp-6) var(--sp-7);
}

.prose > * + * {
  margin-top: var(--sp-4);
}

.kicker {
  display: inline-flex;
  align-items: center;
  gap: var(--sp-2);
  font-size: 0.625rem;
  font-weight: 800;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--link);
  background: var(--green-050);
  border: 1px solid var(--green-500);
  padding: var(--sp-1) var(--sp-3);
  border-radius: var(--radius-pill);
}

.kicker--warn {
  color: var(--warn);
  background: var(--amber-050);
  border-color: var(--amber-200);
}

.prose h1 {
  font-size: var(--fs-2xl);
  margin-top: var(--sp-3);
}

.prose h2 {
  font-size: var(--fs-xl);
  margin-top: var(--sp-6);
}

.prose h3 {
  font-size: var(--fs-lg);
  margin-top: var(--sp-5);
}

.lede {
  font-size: var(--fs-lg);
  color: var(--text);
  line-height: 1.65;
}

.note {
  font-size: var(--fs-sm);
  color: var(--text-muted);
  line-height: 1.55;
}

.callout {
  background: var(--green-050);
  border: 1px solid var(--green-500);
  border-radius: var(--radius);
  padding: var(--sp-4) var(--sp-5);
  font-size: var(--fs-sm);
  line-height: 1.65;
}

.term {
  background: var(--amber-050);
  border: 1px solid var(--amber-200);
  border-left: 4px solid var(--warn);
  border-radius: var(--radius-sm);
  padding: var(--sp-3) var(--sp-4);
}

.term b {
  display: block;
  font-size: var(--fs-sm);
  color: var(--warn);
}

/* --- 9a. The quiz --------------------------------------------------------

   The assessments are the one place on this site that genuinely needs
   JavaScript: a quiz that cannot mark itself is not a quiz. So they are built
   the other way up — the questions, the options, the correct answers and the
   explanations are all in the markup as a real <form> with <fieldset>,
   <legend> and <input type="radio">. With script off a reader can still read
   every question, see every option, and open the answer in a <details>. With
   script on, the <details> become live marking.

   That is why nothing here is generated. See docs/assessments.md. */

.quiz {
  border: 0;
  padding: 0;
  margin: 0;
}

.q {
  border: 0;
  padding: var(--sp-5) 0;
  margin: 0;
  border-bottom: 1px solid var(--border);
}

.q:last-of-type {
  border-bottom: 0;
}

.q__text {
  font-size: var(--fs-base);
  font-weight: 700;
  line-height: 1.5;
  color: var(--text-heading);
  padding: 0;
  margin-bottom: var(--sp-3);
}

.opt {
  display: flex;
  align-items: flex-start;
  gap: var(--sp-3);
  padding: var(--sp-3) var(--sp-4);
  border: 1.5px solid var(--border);
  border-radius: var(--radius);
  margin-bottom: var(--sp-2);
  cursor: pointer;
  font-size: var(--fs-sm);
  background: var(--surface);
  transition: border-color 0.16s var(--ease), background 0.16s var(--ease);
}

.opt:hover {
  border-color: var(--border-strong);
  background: var(--bg);
}

.opt input {
  margin-top: 0.2em;
  accent-color: var(--accent);
  flex-shrink: 0;
}

.opt:has(input:checked) {
  border-color: var(--accent);
  background: var(--green-050);
}

/* Marked state. The colour is never the only signal — .opt__mark carries a
   check or a cross as text, so the result survives a colour-blind reader and
   forced-colors alike. */
.opt.is-correct {
  border-color: var(--good);
  background: var(--green-050);
}

.opt.is-wrong {
  border-color: var(--warn);
  background: var(--amber-050);
}

.opt__mark {
  margin-left: auto;
  font-weight: 800;
  flex-shrink: 0;
}

.opt.is-correct .opt__mark {
  color: var(--good);
}

.opt.is-wrong .opt__mark {
  color: var(--warn);
}

/* The explanation. A <details> with script off, forced open and restyled as
   feedback once an answer has been marked. */
.q__why {
  font-size: var(--fs-sm);
  line-height: 1.6;
  margin-top: var(--sp-2);
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  background: var(--bg);
  padding: 0;
}

.q__why > summary {
  padding: var(--sp-2) var(--sp-4);
  font-weight: 700;
  cursor: pointer;
  font-size: var(--fs-xs);
}

.q__why > div {
  padding: 0 var(--sp-4) var(--sp-3);
}

.q__why.is-ok {
  border-color: var(--good);
  background: var(--green-050);
}

.q__why.is-no {
  border-color: var(--warn);
  background: var(--amber-050);
}

.score {
  text-align: center;
  padding: var(--sp-5);
  border-radius: var(--radius-lg);
  border: 1.5px solid var(--border);
}

.score[hidden] {
  display: none;
}

.score b {
  font-family: var(--font-display);
  font-size: 2.5rem;
  font-weight: 800;
  display: block;
  line-height: 1;
}

.score.is-pass {
  background: var(--green-050);
  border-color: var(--green-500);
}

.score.is-pass b {
  color: var(--link);
}

.score.is-fail {
  background: var(--amber-050);
  border-color: var(--amber-200);
}

.score.is-fail b {
  color: var(--warn);
}

/* --- 9b. The practice activities ----------------------------------------

   Every activity on activities.html works twice over: the worked example is
   in the markup as a real table or list, and script upgrades it in place to
   the interactive version. Turn script off and the numbers stop moving but
   the teaching does not disappear. The same figures are drawn into the
   lesson's own activity slide. See docs/activities.md. */

.act {
  border: 1.5px solid var(--border);
  border-radius: var(--radius-lg);
  padding: var(--sp-5);
  background: var(--surface);
}

.act__head {
  display: flex;
  align-items: center;
  gap: var(--sp-3);
  flex-wrap: wrap;
}

.act__head h2 {
  font-size: var(--fs-lg);
  margin: 0;
}

.slider-row {
  display: grid;
  grid-template-columns: 150px 1fr 74px;
  gap: var(--sp-3);
  align-items: center;
  padding: var(--sp-2) 0;
}

.slider-row label {
  font-size: var(--fs-sm);
  font-weight: 600;
}

.slider-row input[type="range"] {
  width: 100%;
  accent-color: var(--accent);
  cursor: pointer;
}

.amt {
  font-family: var(--font-display);
  font-weight: 800;
  text-align: right;
  font-variant-numeric: tabular-nums;
}

.stack-bar {
  display: flex;
  height: 26px;
  border-radius: var(--radius-sm);
  overflow: hidden;
  background: var(--bg-alt);
  margin-block: var(--sp-3);
}

.stack-bar i {
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: 0.66rem;
  font-weight: 800;
  font-style: normal;
  color: var(--white);
  overflow: hidden;
  white-space: nowrap;
  transition: width 0.45s var(--ease);
}

/* A data table that has to stay a table. It scrolls inside its own box rather
   than making the page scroll sideways. */
.table-wrap {
  overflow-x: auto;
}

.data-table {
  width: 100%;
  border-collapse: collapse;
  font-size: var(--fs-sm);
}

.data-table caption {
  text-align: left;
  font-size: var(--fs-xs);
  color: var(--text-muted);
  padding-bottom: var(--sp-2);
}

.data-table th,
.data-table td {
  text-align: left;
  padding: var(--sp-2) var(--sp-4);
  border-bottom: 1px solid var(--border);
}

.data-table td:last-child,
.data-table th:last-child {
  text-align: right;
  font-variant-numeric: tabular-nums;
}

.data-table tbody tr:last-child th,
.data-table tbody tr:last-child td {
  border-bottom: 0;
  font-weight: 800;
  background: var(--bg);
}

/* A stub line, once activities.js has made it a button. It has to keep
   looking like the table cell it replaced — the row is the control, and a
   button that looked like a button here would read as a second column. */
.linkish {
  font: inherit;
  color: var(--link);
  background: none;
  border: 0;
  padding: 0;
  text-align: left;
  cursor: pointer;
  text-decoration: underline;
  text-underline-offset: 0.18em;
  text-decoration-thickness: 1px;
}

.linkish:hover {
  color: var(--link-hover);
}

.data-table tr.is-open {
  background: var(--green-050);
  box-shadow: inset 3px 0 0 var(--accent);
}

.fld label {
  display: block;
  font-size: var(--fs-xs);
  font-weight: 700;
  color: var(--text-muted);
  text-transform: uppercase;
  letter-spacing: 0.05em;
  margin-bottom: var(--sp-1);
}

.fld input {
  width: 100%;
  font-family: inherit;
  font-size: var(--fs-base);
  font-weight: 600;
  padding: var(--sp-2) var(--sp-3);
  border: 1.5px solid var(--border);
  border-radius: var(--radius-sm);
  background: var(--surface);
  color: var(--text);
}

.fld input:focus {
  outline: none;
  border-color: var(--accent);
  box-shadow: 0 0 0 3px var(--green-glow);
}

.grid-2 {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: var(--sp-4);
}

@media (max-width: 40rem) {
  .grid-2 {
    grid-template-columns: 1fr;
  }

  .slider-row {
    grid-template-columns: 110px 1fr 62px;
  }

  .prose {
    padding: var(--sp-5) var(--sp-4) var(--sp-6);
  }
}

/* --------------------------------------------------------------------------
   10. Footer & utilities
   -------------------------------------------------------------------------- */

.site-footer {
  background: var(--bg-alt);
  border-top: 1px solid var(--border);
  padding-block: var(--sp-6);
  font-size: var(--fs-sm);
}

.site-footer__inner {
  max-width: 1440px;
  margin-inline: auto;
  padding-inline: var(--sp-5);
  display: flex;
  flex-wrap: wrap;
  gap: var(--sp-5);
  justify-content: space-between;
  align-items: baseline;
}

.site-footer__links {
  list-style: none;
  padding: 0;
  margin: 0;
  display: flex;
  flex-wrap: wrap;
  gap: var(--sp-4);
}

.site-footer p {
  color: var(--text-muted);
  max-width: 44ch;
}

/* Forced colours: the slides are the site's content, so every colour class in
   section 8 has to hand back control here — the `color` half as well as the
   `fill` half, or a slide's prose keeps its own palette in exactly the mode
   that exists to prevent that. */
@media (forced-colors: active) {
  .slide-svg text,
  .slide-svg .flow,
  .slide-svg .muted,
  .slide-svg .b,
  .slide-svg .good,
  .slide-svg .warn,
  .slide-svg .accent,
  .slide-svg .eyebrow,
  .slide-svg .head,
  .slide-svg .h1,
  .slide-svg .h2,
  .slide-svg .h3,
  .slide-svg .num,
  .slide-svg .punch,
  .slide-svg .lede {
    fill: CanvasText;
    color: CanvasText;
  }

  .slide-svg .card,
  .slide-svg .panel,
  .slide-svg .panel-warn,
  .slide-svg .panel-good {
    fill: Canvas;
    stroke: CanvasText;
  }

  .slide-svg .btn-label,
  .slide-svg .btn-label-ghost {
    fill: LinkText;
    color: LinkText;
  }

  .rail__link[aria-current="page"] {
    border-left-color: Highlight;
  }
}
