/* ============================================================
   THE TYPEFACES

   Self-hosted 2026-08-04, from Fontsource, both under the SIL Open
   Font License. The license text ships next to the files in
   /fonts/, because these are redistributed rather than linked.

   FIVE FILES, 78KB in total, latin subsets. Four was the intention
   and the stylesheet was measured before committing to it: 45 rules
   ask for weight 700 and 51 for weight 800, and they are the biggest
   type on the site, the hero heading, the section headings, the
   headline numbers and the stat values. With only 400 and 600 hosted
   every one of those 96 rules would have resolved down to 600 and
   the whole site would have shipped lighter than it was designed,
   which is not what a lean set was meant to buy.

   700 is the one file that fixes both, because 800 then resolves to
   700 rather than to 600, and 700 is what the system stack was
   already giving those rules. Weight 500, seven rules, resolves to
   400 and is left alone.

   A real 600 and 700 mean the browser never has to fake a bold, and
   a real italic means it never has to fake a slant. Faked weights
   are the thing that makes a self-hosted face look worse than the
   system one it replaced.

   `font-display: swap` on all four. A course read on a school
   connection should show its text immediately in the fallback and
   change face when the file lands, rather than holding a blank page.
   The stacks are chosen so that swap is a small shift: Public Sans
   against the system sans, Fraunces against the system serif.

   Preloaded in the HTML? No. Preload is worth it for a face used
   above the fold on every page, and it costs a round trip for the
   ones that are not. These are small enough that swap covers it.
   ============================================================ */
@font-face {
  font-family: "Fraunces";
  src: url("../fonts/fraunces-700.woff2") format("woff2");
  font-weight: 700;
  font-style: normal;
  font-display: swap;
}
@font-face {
  font-family: "Public Sans";
  src: url("../fonts/public-sans-400.woff2") format("woff2");
  font-weight: 400;
  font-style: normal;
  font-display: swap;
}
@font-face {
  font-family: "Public Sans";
  src: url("../fonts/public-sans-600.woff2") format("woff2");
  font-weight: 600;
  font-style: normal;
  font-display: swap;
}
@font-face {
  font-family: "Public Sans";
  src: url("../fonts/public-sans-700.woff2") format("woff2");
  font-weight: 700;
  font-style: normal;
  font-display: swap;
}
@font-face {
  font-family: "Public Sans";
  src: url("../fonts/public-sans-400-italic.woff2") format("woff2");
  font-weight: 400;
  font-style: italic;
  font-display: swap;
}

/* ============================================================
   CompoundPath: design tokens and system

   RETHEMED 2026-08-03, from a diagnosis rather than a mood.

   The old palette was Stripe's. Not Stripe-like: --contrib was
   #635bff, their brand purple, --ink was #0a2540, their navy, and
   --bg was #f6f9fc, their page wash. The header said "based on
   dylanbailes.com patterns" and those patterns trace back to the
   most copied design system on the web. That is why it read as
   boilerplate. It was not bland, it was borrowed, and it was
   borrowed from the one everybody borrows.

   It was also wearing the operating system's clothes. --font asked
   for Inter, there was no @font-face anywhere in the project, no
   font file, and a CSP of font-src 'self'. Inter had never once
   rendered. Every page had been drawing in Segoe UI on Windows and
   Roboto on a Chromebook.

   THE DIRECTION IS AN ALMANAC, not an app. Warm paper rather than a
   cold blue-gray wash, ink rather than navy, hairline rules rather
   than soft shadows, and tight radii rather than the 16px pill
   corners every SaaS card has. It should read as something a person
   made and has an opinion about, which is also what the writing
   throughout this course is trying to do.

   ---- how the colors are divided ----

   Three colors already carry MEANING and cannot be spent on brand:
   green is money, amber is a warning, red is a loss. That rules out
   the obvious accents for a finance course and is the reason the
   brand color is oxblood: it is warm, it belongs with paper, it
   reads as ink and binding rather than as a value, and nothing in
   the simulation ever means it.

   Oxblood and danger-red sit near each other on the wheel and are
   separated by depth and saturation rather than hue. Danger never
   appears without --bad-bg behind it, so the two are never asked to
   be told apart on their own.

   Every pair below is measured by verify.html in both themes. The
   contrast checker is the reason this could be changed at all: a
   repaint of a whole palette is exactly the change that breaks a
   dozen text pairs without anyone seeing it, and there is a gate
   that counts them.
   ============================================================ */

/* Course-only tokens. The shared palette lives in ../../css/tokens.css, which
   every course page links FIRST, and must not be repeated here: a duplicate
   definition would win on source order and the two halves of the site would
   drift apart again.

   --font-display is the deliberate exception. It prepends Fraunces to the same
   fallback stack the site uses, so the course reads as its own thing while
   degrading to identical typography if the webfont does not load. */
:root {
  --primary-fg: #fffdf8;
  --badge-bg: #0a5940;
  --badge-fg: #fffdf8;
  --badge-warn-bg: #93211a;
  --badge-warn-fg: #fffdf8;
  --font-display: Fraunces, "Iowan Old Style", "Palatino Linotype", Palatino, "Book Antiqua", Georgia, "Noto Serif", "Liberation Serif", serif;
  --font: "Public Sans", -apple-system, BlinkMacSystemFont, "Segoe UI Variable Text", "Segoe UI", Roboto, "Noto Sans", Helvetica, Arial, sans-serif;
}

/* Dark theme.
   Driven by a data-theme attribute, not prefers-color-scheme, so the toggle in
   the header can override the OS. An inline script in <head> stamps the
   attribute before first paint; theme.js wires the button and follows the OS
   only while the reader has not chosen for themselves. Matching the mechanism
   finance-site already uses means a shared token audit covers both. */
/* Dark is not the light theme inverted. It is the same paper at night:
   warm charcoals rather than blue-black, so the two themes read as one
   object under different light rather than as two products. */
/* Course-only tokens. The shared palette lives in ../../css/tokens.css, which
   every course page links FIRST, and must not be repeated here: a duplicate
   definition would win on source order and the two halves of the site would
   drift apart again.

   --font-display is the deliberate exception. It prepends Fraunces to the same
   fallback stack the site uses, so the course reads as its own thing while
   degrading to identical typography if the webfont does not load. */
:root[data-theme="dark"] {
  --primary-fg: #17130f;
  --badge-bg: #4ecf9a;
  --badge-fg: #06251a;
  --badge-warn-bg: #f0a79c;
  --badge-warn-fg: #2b0a06;
}

/* Applied by theme.js only while the light/dark swap is in flight. Without it,
   any element transitioning a color that comes from a token stays stuck on the
   previous theme's value, because a token change does not restart a transition. */
:root.theme-swapping,
:root.theme-swapping *,
:root.theme-swapping *::before,
:root.theme-swapping *::after { transition: none !important; }

/* The hidden attribute has to beat layout display values.

   The browser default is [hidden] { display: none }, which is a plain
   attribute selector and loses to any class rule that sets display. Every
   .callout is display:flex, so every callout carrying `hidden` rendered
   anyway. That is why an empty green bar sat on the setup screen, and why
   the retire panel, which is hidden until stopping work is plausible, was
   offering to end a career at eighteen.

   One rule, ahead of everything, because JS across this project sets
   .hidden = true and reasonably expects the element to go away. */
[hidden] { display: none !important; }

* { box-sizing: border-box; }
html { -webkit-text-size-adjust: 100%; }

/* Set by the sim while any dialog is open. Scrollbar-gutter holds the
   scrollbar's width so the page underneath does not jump sideways as the
   dialog opens and closes. */
html.has-modal { overflow: hidden; scrollbar-gutter: stable; }

body {
  margin: 0;
  font-family: var(--font);
  color: var(--ink);
  background: var(--bg);
  line-height: 1.5;
  -webkit-font-smoothing: antialiased;
  text-rendering: optimizeLegibility;
}

/* ---- HEADINGS ARE THE VOICE ----

   The serif is doing most of the work of this identity. It is the
   difference between a page that looks generated and a page that looks
   written, and it costs nothing: every target platform ships a real one.

   Letter-spacing goes back to normal. Negative tracking is a sans-serif
   habit, borrowed from the same design language as the old palette, and it
   makes a serif look squeezed. */
h1, h2, h3, h4 {
  font-family: var(--font-display);
  line-height: 1.14;
  letter-spacing: 0;
  margin: 0;
  font-weight: 700;
}

/* ---- THE ALMANAC RULE ----

   A page of this course is a piece of writing with figures in it, so the
   sections are separated the way a printed one would be: by a rule, not by
   a floating card on a tinted background. Applied to the top-level section
   headings rather than to every h3 inside a panel. */
main > .wrap > h2,
main > .wrap > section > h2 {
  padding-bottom: 10px;
  border-bottom: 2px solid var(--ink);
  margin-bottom: 18px;
}

/* Small caps eyebrows, the almanac's other habit. Keeps the sans, because
   this is a label rather than prose. */
.eyebrow, .kind, .cmp-where, .pick .m {
  font-family: var(--font);
  text-transform: uppercase;
  letter-spacing: .08em;
  font-size: .68rem;
  font-weight: 700;
}
a { color: var(--link); text-decoration: none; }
a:hover { color: var(--link-hover); }

main a:not(.btn-primary):not(.btn-ghost):not(.btn-lg):not(.tool-card):not(.nav-parent) {
  text-decoration: underline;
  text-decoration-thickness: 1px;
  text-decoration-color: color-mix(in srgb, currentColor 45%, transparent);
  text-underline-offset: 2px;
}

/* Header */
.site-header {
  position: sticky; top: 0; z-index: 40;
  background: var(--header-bg);
  backdrop-filter: saturate(160%) blur(12px);
  border-bottom: 1px solid var(--line);
}
.nav {
  max-width: var(--maxw); margin: 0 auto;
  display: flex; align-items: center; gap: 28px;
  padding: 14px 24px;
}
.brand {
  display: flex; align-items: center; gap: 10px;
  font-weight: 700; font-size: 1.05rem; color: var(--ink);
  letter-spacing: -.02em;
}
/* Solid fill rather than a gradient. A gradient's lighter stop is a second,
   untestable background color, and in dark mode the light end of an indigo
   gradient drops the glyph below AA. One fill is one contrast pair to check. */
/* THE WAY BACK OUT. Reported 2026-08-07: once you were inside /course there
   was no route to the rest of the site except a link at the very bottom of the
   footer, which nobody reaches from the middle of a module.

   IT GOES BEFORE THE WORDMARK, NOT AFTER IT. Placed after, the line read
   "CompoundPath <- Dylan Bailes" and the arrow looked like a label pointing AT
   the CompoundPath logo rather than a way out of it. Ahead of the mark it reads
   as what it is: the level above, with the arrow pointing away off the page.
   The divider is what stops the two reading as one wordmark.

   It sits here rather than in .nav-right because those links are hidden by
   :root.in-run, and a run can last an hour, which is exactly when somebody is
   most likely to want out. This one survives. */
.site-up {
  font-size: .82rem;
  font-weight: 500;
  color: var(--ink-soft);
  white-space: nowrap;
  /* The text alone is a 20px tap target, under the 24px the rest of this nav
     uses and well under what a thumb wants. 30px matches the height of the
     brand mark beside it, so the row does not change shape. */
  display: inline-flex;
  align-items: center;
  min-height: 30px;
}
.site-up:hover { color: var(--ink); }
/* A rule, not a gap. Whitespace alone still let the eye group the two as one
   lockup, which is the thing being fixed. */
.site-up::after {
  content: "";
  display: inline-block;
  width: 1px;
  height: 18px;
  margin-left: 14px;
  background: var(--rule, currentColor);
  opacity: .35;
}
:root.in-run .site-up { font-size: .76rem; }
.brand .mark {
  width: 30px; height: 30px; border-radius: 9px;
  background: var(--primary);
  display: grid; place-items: center;
  color: var(--primary-fg); font-weight: 800; font-size: .95rem;
  box-shadow: var(--shadow-sm);
}

.nav-right { margin-left: auto; display: flex; align-items: center; gap: 18px; }

/* ---- THE HEADER DID NOT FIT ON A PHONE ----

   FOUND 2026-08-04 while checking the self-hosted type had not broken any
   layout. It had not: this was already wrong. Measured at 375px, the header
   wanted 451px of a 343px row, so every page on the site scrolled sideways
   by 76px, and the outermost offender was always `.nav-right`. Public Sans
   is a shade wider than the system sans and took it to 84px, which is how
   it surfaced, but 76 of those 84 predate the typeface.

   One row cannot hold it. The brand is about 145px, the three links about
   218, the toggle 36, and no amount of tightening closes a 108px gap
   without dropping a link, which is worse: those three are the
   navigation. So it wraps, brand on top, controls beneath, spread across
   the width they now have. */
@media (max-width: 620px) {
  .nav { flex-wrap: wrap; gap: 10px; padding: 12px 16px; }
  .nav-right {
    margin-left: 0;
    width: 100%;
    justify-content: space-between;
    gap: 10px;
  }
}
/* min-height, not just line-height: WCAG 2.5.8 wants a 24px target, and a
   .93rem link is about 21px tall on its own. Measured at 375px wide, where
   the header links were the smallest targets on the page. */
.nav-right a {
  color: var(--ink-soft); font-weight: 500; font-size: .93rem;
  display: inline-flex; align-items: center; min-height: 24px;
}
.nav-right a:hover { color: var(--ink); }

/* 24px minimum target, per WCAG 2.5.8 */
#theme-toggle {
  width: 36px; height: 36px; flex: none;
  display: grid; place-items: center;
  border: 1px solid var(--line); border-radius: 10px;
  background: var(--surface); color: var(--ink-soft);
  cursor: pointer; transition: border-color .15s, color .15s;
}
#theme-toggle:hover { border-color: var(--primary); color: var(--ink); }
#theme-toggle svg { width: 17px; height: 17px; }
/* The icon shows the state the button takes you TO, which is what the
   aria-label promises. Showing the current state instead would have the
   button reading "Switch to light mode" next to a moon. */
:root[data-theme="dark"] #theme-toggle .i-moon,
:root:not([data-theme="dark"]) #theme-toggle .i-sun { display: none; }

/* Skip link: offscreen until focused. */
.skip-link {
  position: absolute; left: -9999px; top: 0; z-index: 100;
  background: var(--surface); color: var(--ink);
  border: 1px solid var(--primary); border-radius: 0 0 10px 0;
  padding: 12px 18px; font-weight: 600; font-size: .92rem;
}
.skip-link:focus { left: 0; }

/* ============================================================
   WHERE THE KEYBOARD IS

   AUDITED 2026-08-04. There were seven focus rules in this file and
   they covered text inputs, range inputs, the skip link, `.link-btn`,
   `.btn-check` and a fold's summary. Not one covered a plain button,
   a link, a tab, the theme toggle, or `.sim-pick`, which is the
   large card option that most of the simulation is played through.
   Every primary control on the site was falling back to whatever
   ring the browser happened to draw.

   That is probably not a WCAG 2.4.7 failure, because browsers do
   draw a default ring, and it is worth being straight about that:
   the fault found here is not "focus is invisible", it is that a
   project which measures contrast on 86 elements in both themes had
   left its focus indicator untested and inconsistent, and a default
   ring is not something you can measure or tune. It also cannot be
   verified from script, because `:focus-visible` depends on the
   input modality and a programmatic `.focus()` does not count as
   keyboard, so the only honest way to know what a keyboard user sees
   is to author it.

   `:focus-visible` rather than `:focus`, so a mouse click on a
   button does not leave a ring behind it. Two rings, ink inside and
   paper outside, so the indicator survives whatever color it lands
   on: the pale cards, the Prussian blue primary buttons, and the
   chapter-tinted stage. A single ring in one color disappears on
   one of those three.
   ============================================================ */
:where(a, button, summary, [tabindex], input, select, textarea):focus-visible {
  outline: 2px solid var(--ink);
  outline-offset: 2px;
  border-radius: var(--radius-sm);
  /* The outer half of the pair. box-shadow rather than a second
     outline, since an element only has one. */
  box-shadow: 0 0 0 4px var(--surface);
}

/* On the primary button the ink ring sits on Prussian blue, where it is
   dark on dark. Flipped: paper inside, ink outside. */
.btn-primary:focus-visible {
  outline-color: var(--surface);
  box-shadow: 0 0 0 4px var(--ink);
}

/* Inputs already carry their own focus treatment, a border and a soft
   ring, and stacking an outline on top of it reads as an error state. */
.input-money:focus-visible,
.input-plain:focus-visible,
select.input-plain:focus-visible,
.reflection-box textarea:focus-visible {
  outline: none;
  box-shadow: 0 0 0 3px var(--primary-100);
}

/* A panel is focusable so it can be moved into from its tab, but it is
   not a control and a ring round a whole panel is noise. It keeps a
   quiet one, enough to say where you are. */
[role="tabpanel"]:focus-visible {
  outline: 2px dashed var(--primary);
  outline-offset: 4px;
  box-shadow: none;
}

/* Footer */
.site-footer {
  border-top: 1px solid var(--line); margin-top: 40px;
  padding: 28px 24px 44px;
}
.site-footer .inner {
  max-width: var(--maxw); margin: 0 auto;
  display: flex; gap: 16px; flex-wrap: wrap; align-items: baseline;
  font-size: .85rem; color: var(--ink-faint);
}
.site-footer a {
  color: var(--ink-soft);
  display: inline-flex; align-items: center; min-height: 24px;
}
/* Decorative, but still painted text, so it still has to clear AA. This was
   set to --line, which is a border color and roughly 1.7:1 against the page. */
.site-footer .sep { color: var(--ink-faint); }

/* Main layout */
.wrap { max-width: var(--maxw); margin: 0 auto; padding: 0 24px; }
.hero { padding: 54px 24px 26px; max-width: var(--maxw); margin: 0 auto; }
.hero h1 { font-size: clamp(2rem, 4.4vw, 3rem); font-weight: 800; }
.hero p.lede {
  margin: 14px 0 0; max-width: 620px;
  color: var(--ink-soft); font-size: 1.08rem;
}

/* Module container */
.module-container {
  max-width: var(--maxw); margin: 0 auto; padding: 36px 24px 80px;
}
.module-section {
  margin-bottom: 48px;
}
.module-section h2 {
  font-size: 1.45rem; font-weight: 800; letter-spacing: -.02em; margin-bottom: 12px;
}
.module-section p {
  color: var(--ink-soft); font-size: .96rem; line-height: 1.72; margin-bottom: 14px;
}
.module-section p:last-child { margin-bottom: 0; }

/* Cards and panels */
.card {
  background: var(--surface); border: 1px solid var(--line);
  border-radius: var(--radius); box-shadow: var(--shadow-sm);
}
.panel { padding: 22px; }
.panel + .panel { border-top: 1px solid var(--line-soft); }

.callout {
  display: flex; gap: 14px; align-items: flex-start;
  background: var(--ok-bg); border: 1px solid var(--ok-border);
  border-radius: var(--radius-sm); padding: 16px 18px;
  margin-top: 16px;
}
/* The badge glyph is bold 17.6px text, so AA wants 4.5:1. White on --money is
   about 2:1 in light mode, and in dark mode --money gets lighter, which makes
   white worse rather than better. Each theme needs its own pair. */
.callout .ic {
  width: 34px; height: 34px; border-radius: 9px; background: var(--badge-bg);
  color: var(--badge-fg); display: grid; place-items: center; flex: none; font-weight: 800;
  font-size: 1.1rem;
}
.callout .txt { font-size: .92rem; color: var(--ok-text); }
.callout .txt strong { color: var(--ok-text-strong); font-weight: 700; }

.callout.warn { background: var(--bad-bg); border-color: var(--bad-border); }
.callout.warn .ic { background: var(--badge-warn-bg); color: var(--badge-warn-fg); }
.callout.warn .txt { color: var(--bad-text); }
.callout.warn .txt strong { color: var(--bad-text-strong); }

/* Forms and inputs */
.field { margin-bottom: 18px; }
.field:last-child { margin-bottom: 0; }
.field label {
  display: flex; justify-content: space-between; align-items: baseline;
  font-size: .86rem; font-weight: 600; color: var(--ink-soft);
  margin-bottom: 7px;
}
.field label .hint { font-weight: 500; color: var(--ink-faint); font-size: .78rem; }

.input-money, .input-plain, select.input-plain {
  width: 100%; font-family: var(--font); font-size: 1rem; color: var(--ink);
  border: 1px solid var(--line); border-radius: var(--radius-sm);
  padding: 11px 12px; background: var(--surface); outline: none;
  transition: border-color .15s, box-shadow .15s;
  min-width: 0;
}
.input-money:focus, .input-plain:focus, select.input-plain:focus {
  border-color: var(--primary);
  box-shadow: 0 0 0 3px var(--primary-100);
}
select.input-plain {
  appearance: none;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12' viewBox='0 0 12 12'%3E%3Cpath fill='%238792a2' d='M6 8.5 1.5 4h9z'/%3E%3C/svg%3E");
  background-repeat: no-repeat; background-position: right 12px center;
  padding-right: 32px; cursor: pointer;
}

.input-wrap { position: relative; }
.input-wrap .prefix, .input-wrap .suffix {
  position: absolute; top: 50%; transform: translateY(-50%);
  color: var(--ink-faint); font-size: .95rem; pointer-events: none;
}
.input-wrap .prefix { left: 12px; }
.input-wrap .suffix { right: 12px; }
.input-wrap.has-prefix .input-money { padding-left: 26px; }
.input-wrap.has-suffix .input-money { padding-right: 30px; }

.two-col { display: grid; grid-template-columns: 1fr 1fr; gap: 12px; }
@media (max-width: 640px) { .two-col { grid-template-columns: 1fr; } }

/* Sliders */
.slider-row { display: flex; align-items: center; gap: 12px; }
/* The element is 24px tall and transparent; the visible 6px bar is the track
   pseudo-element inside it. Styling height on the input itself makes the whole
   control a 6px tap target, which measured 277x6 and is well under the 24px
   WCAG 2.5.8 asks for. The bar looks the same and the hit area is four times
   taller. */
input[type="range"] {
  -webkit-appearance: none; appearance: none;
  height: 24px; flex: 1; background: transparent; cursor: pointer;
}
input[type="range"]::-webkit-slider-runnable-track {
  height: 6px; border-radius: 999px; background: var(--line);
}
input[type="range"]::-moz-range-track {
  height: 6px; border-radius: 999px; background: var(--line);
}
input[type="range"]::-webkit-slider-thumb {
  -webkit-appearance: none; appearance: none;
  width: 20px; height: 20px; border-radius: 50%;
  background: var(--primary); border: 3px solid var(--surface);
  box-shadow: var(--shadow-sm); cursor: pointer;
  margin-top: -7px;              /* centers a 20px thumb on a 6px track */
}
input[type="range"]::-moz-range-thumb {
  width: 18px; height: 18px; border-radius: 50%;
  background: var(--primary); border: 3px solid var(--surface); cursor: pointer;
}
input[type="range"]:focus-visible { outline: 2px solid var(--primary); outline-offset: 2px; border-radius: 999px; }
.slider-val {
  min-width: 74px; text-align: right; font-variant-numeric: tabular-nums;
  font-weight: 700; color: var(--ink); font-size: .95rem;
}

/* Results and output */
.calc-results {
  display: flex; flex-direction: column; gap: 18px; margin-top: 24px;
}
.calc-results.oc-results { margin-top: 0; }

.result-block {
  background: var(--surface-2); border: 1px solid var(--line);
  border-radius: var(--radius-sm); padding: 18px 20px;
}
.result-block .label {
  font-size: .78rem; font-weight: 700; text-transform: uppercase;
  letter-spacing: .06em; color: var(--ink-faint); margin-bottom: 6px;
}
.result-block .value {
  font-size: 1.5rem; font-weight: 800;
  font-variant-numeric: tabular-nums; color: var(--ink);
}
.result-block .sublabel {
  font-size: .8rem; color: var(--ink-faint); margin-top: 4px;
}

.stat-grid { display: grid; grid-template-columns: repeat(3, 1fr); gap: 14px; }
@media (max-width: 640px) { .stat-grid { grid-template-columns: repeat(2, 1fr); } }
@media (max-width: 480px) { .stat-grid { grid-template-columns: 1fr; } }

.stat {
  background: var(--surface); border: 1px solid var(--line);
  border-radius: var(--radius-sm); padding: 16px 16px 14px;
}
.stat .lbl { font-size: .8rem; color: var(--ink-faint); font-weight: 600; }
.stat .val {
  font-size: 1.28rem; font-weight: 800; margin-top: 6px;
  font-variant-numeric: tabular-nums; letter-spacing: -.02em;
}

/* Reflection and assessment */
.reflection-box {
  background: var(--surface-2); border-left: 4px solid var(--primary);
  border-radius: var(--radius-sm); padding: 18px 20px; margin-top: 16px;
}
.reflection-box h3, .reflection-box h4 {
  font-size: .95rem; font-weight: 700; margin: 0 0 8px; color: var(--ink);
}
.reflection-box p {
  font-size: .92rem; color: var(--ink-soft); line-height: 1.6; margin: 0;
}

/* Buttons */
.btn-primary {
  display: inline-flex; align-items: center; justify-content: center;
  background: var(--primary); color: var(--primary-fg); border: 0;
  font-family: var(--font); font-weight: 600; font-size: .95rem;
  padding: 11px 20px; border-radius: var(--radius-sm);
  cursor: pointer; transition: all .15s;
  text-decoration: none;
}
.btn-primary:hover {
  background: var(--primary-600); transform: translateY(-1px);
  box-shadow: var(--shadow-md);
}

/* Example scenarios */
.scenarios { display: grid; grid-template-columns: 1fr 1fr; gap: 14px; margin-top: 18px; }
.scenario {
  background: var(--surface-2); border: 1px solid var(--line);
  border-radius: var(--radius-sm); padding: 16px 18px;
}
.scenario h3, .scenario h4 { margin: 0 0 6px; font-size: .95rem; font-weight: 700; display: flex; align-items: center; gap: 8px; }
/* --primary-600, not --primary. The badge is 12px at weight 800, so it needs
   the 4.5:1 threshold, and --primary on --primary-100 measures 4.05:1 in the
   light theme. The darker step clears it in both themes. */
.scenario .num {
  width: 22px; height: 22px; border-radius: 7px; flex: none;
  background: var(--primary-100); color: var(--primary-600);
  display: grid; place-items: center; font-size: .75rem; font-weight: 800;
}
.scenario p { margin: 0; font-size: .88rem; color: var(--ink-soft); line-height: 1.62; }
@media (max-width: 640px) { .scenarios { grid-template-columns: 1fr; } }

/* Bridging text */
.next-module {
  background: color-mix(in srgb, var(--primary) 8%, var(--surface));
  border: 1px solid var(--primary-100); border-radius: var(--radius-sm);
  padding: 20px 22px; margin-top: 36px;
}
.next-module h2, .next-module h3 {
  font-size: 1rem; font-weight: 700; margin: 0 0 8px; color: var(--ink);
}
.next-module p {
  font-size: .92rem; color: var(--ink-soft); line-height: 1.6; margin: 0;
}
.next-module p + p { margin-top: 14px; }

/* Available to screen readers, absent from the page. */
.visually-hidden {
  position: absolute; width: 1px; height: 1px; margin: -1px; padding: 0;
  overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; border: 0;
}

/* Small print. A class rather than an inline style: an inline declaration wins
   over any stylesheet rule, which is how a dark theme ends up with one element
   stuck on a light value. Keeping it here keeps it themeable. */
.note-small {
  font-size: .8rem; color: var(--ink-faint); line-height: 1.55; margin: 16px 0 0;
}
.module-section p.after-table { margin-top: 16px; }

/* Eyebrow label */
.eyebrow {
  display: inline-flex; align-items: center; gap: 8px;
  font-size: .8rem; font-weight: 600; color: var(--primary-600);
  background: var(--primary-100); padding: 5px 12px; border-radius: 999px;
  letter-spacing: .01em; margin-bottom: 16px;
}

/* Module meta strip: duration, level, objective */
.module-meta {
  display: grid; grid-template-columns: repeat(auto-fit, minmax(180px, 1fr));
  gap: 14px; margin-top: 26px;
}
.module-meta > div {
  background: var(--surface); border: 1px solid var(--line);
  border-radius: var(--radius-sm); padding: 14px 16px;
}
.module-meta .k {
  font-size: .68rem; font-weight: 800; text-transform: uppercase;
  letter-spacing: .05em; color: var(--ink-faint);
}
.module-meta .v { font-size: .92rem; font-weight: 600; margin-top: 4px; line-height: 1.45; }

/* Prose lists inside a module section */
.module-section ul, .module-section ol {
  color: var(--ink-soft); font-size: .96rem; line-height: 1.72;
  padding-left: 22px; margin: 0 0 14px;
}
.module-section li { margin-bottom: 7px; }
.module-section li:last-child { margin-bottom: 0; }
.module-section strong { color: var(--ink); font-weight: 600; }
.module-section h3 {
  font-size: 1.08rem; font-weight: 700; margin: 26px 0 9px;
}

/* Calculator two-pane layout */
.oc-layout {
  display: grid; grid-template-columns: 360px 1fr; gap: 24px; align-items: start;
}
.oc-layout > *, .oc-layout .field, .oc-layout .input-wrap { min-width: 0; }
@media (max-width: 860px) { .oc-layout { grid-template-columns: 1fr; } }

/* Headline result */
.headline-card {
  background: var(--surface); border: 1px solid var(--line);
  border-radius: var(--radius); box-shadow: var(--shadow-md); padding: 24px 26px;
}
.headline-card .kicker {
  font-size: .78rem; font-weight: 700; color: var(--ink-faint);
  text-transform: uppercase; letter-spacing: .05em;
}
.headline-num {
  font-size: clamp(2.1rem, 5vw, 3rem); font-weight: 800;
  color: var(--ink); letter-spacing: -.03em; font-variant-numeric: tabular-nums;
  margin: 6px 0 4px; line-height: 1;
}
.headline-sub { color: var(--ink-soft); font-size: .93rem; line-height: 1.6; }
.headline-sub strong { color: var(--money-text); font-weight: 700; }

/* Split bar: what you spent vs the growth you gave up */
.split-bar {
  display: flex; height: 30px; border-radius: 8px; overflow: hidden;
  border: 1px solid var(--line); margin: 18px 0 8px;
}
.split-bar > i { display: block; height: 100%; }
.split-bar .spent { background: color-mix(in srgb, var(--ink-faint) 45%, transparent); }
.split-bar .grown { background: color-mix(in srgb, var(--money) 55%, transparent); }
.split-key { display: flex; justify-content: space-between; gap: 12px; font-size: .78rem; color: var(--ink-faint); }

/* Assumptions disclosure: what the model leaves out */
.assumption {
  background: var(--surface-2); border: 1px dashed var(--line);
  border-radius: var(--radius-sm); padding: 14px 16px; margin-top: 18px;
  font-size: .85rem; color: var(--ink-soft); line-height: 1.6;
}
.assumption b { color: var(--ink); font-weight: 700; }
.assumption ul { padding-left: 20px; margin: 8px 0 0; font-size: .85rem; }

/* Worked-example table */
.worked { width: 100%; border-collapse: collapse; font-size: .87rem; margin-top: 8px; }
.worked th {
  font-size: .68rem; font-weight: 800; text-transform: uppercase; letter-spacing: .05em;
  color: var(--ink-faint); padding: 0 10px 8px 0; border-bottom: 1px solid var(--line);
  text-align: right; white-space: nowrap;
}
.worked th:first-child { text-align: left; }
.worked td {
  padding: 9px 10px 9px 0; border-bottom: 1px solid var(--line-soft);
  text-align: right; font-variant-numeric: tabular-nums; white-space: nowrap;
}
.worked td:first-child { text-align: left; font-weight: 600; white-space: normal; }
.worked-wrap { overflow-x: auto; }

/* Reflection textarea */
.reflection-box textarea {
  width: 100%; margin-top: 10px; min-height: 84px; resize: vertical;
  font-family: var(--font); font-size: .92rem; color: var(--ink);
  background: var(--surface); border: 1px solid var(--line);
  border-radius: var(--radius-sm); padding: 10px 12px; outline: none;
}
.reflection-box textarea:focus { border-color: var(--primary); box-shadow: 0 0 0 3px var(--primary-100); }
.saved-note { font-size: .76rem; color: var(--ink-faint); margin-top: 6px; min-height: 1.1em; }

/* A button that reads as a link. Used for the scenario loaders, which change
   the calculator rather than navigating, so a button is the correct element. */
.link-btn {
  border: 0; background: none; padding: 0; cursor: pointer;
  font-family: var(--font); font-size: inherit; font-weight: 600;
  color: var(--primary); text-align: left;
  text-decoration: underline; text-decoration-thickness: 1px;
  text-underline-offset: 2px;
}
.link-btn:hover { color: var(--primary-600); text-decoration-thickness: 2px; }
.link-btn:focus-visible { outline: 2px solid var(--primary); outline-offset: 3px; border-radius: 3px; }

/* ============================================================
   Activities: predict, practice, ranking, reveals
   ============================================================ */

/* Part banner at the top of a split module */
.part-strip {
  display: flex; align-items: center; gap: 12px; flex-wrap: wrap;
  background: var(--surface-2); border: 1px solid var(--line);
  border-radius: var(--radius-sm); padding: 12px 16px; margin-bottom: 26px;
  font-size: .86rem; color: var(--ink-soft);
}
.part-strip .pip {
  font-size: .68rem; font-weight: 800; text-transform: uppercase;
  letter-spacing: .05em; padding: 3px 9px; border-radius: 999px;
  background: var(--line-soft); color: var(--ink-faint);
}
.part-strip .pip.on { background: var(--primary-100); color: var(--primary-600); }
.part-strip .done { color: var(--money-text); font-weight: 700; }
.part-strip a { display: inline-flex; align-items: center; min-height: 24px; }

/* A single activity card */
.activity {
  background: var(--surface); border: 1px solid var(--line);
  border-radius: var(--radius); box-shadow: var(--shadow-sm);
  padding: 20px 22px; margin-bottom: 16px;
}
.activity .q-num {
  font-size: .68rem; font-weight: 800; text-transform: uppercase;
  letter-spacing: .05em; color: var(--ink-faint); margin-bottom: 6px;
}
.activity h3 { font-size: 1rem; font-weight: 700; margin: 0 0 8px; }
.activity > p { font-size: .93rem; color: var(--ink-soft); line-height: 1.68; margin: 0 0 14px; }
.activity > p:last-of-type { margin-bottom: 14px; }

/* answer row: an input and its check button */
.answer-row { display: flex; gap: 10px; align-items: flex-end; flex-wrap: wrap; }
.answer-row .field { margin: 0; flex: 1 1 190px; }
/* Styled on the class itself, not on ".answer-row .btn-check". The multiple
   choice questions put the button straight on the card with no answer row, so
   a descendant selector left those buttons unstyled and 21px tall. */
.btn-check {
  border: 1px solid var(--primary); background: var(--primary);
  color: var(--primary-fg); font-family: var(--font); font-weight: 600; font-size: .9rem;
  padding: 11px 18px; border-radius: var(--radius-sm); cursor: pointer;
  min-height: 44px; transition: background .15s, border-color .15s;
}
.answer-row .btn-check { flex: none; }
.btn-check:hover { background: var(--primary-600); border-color: var(--primary-600); }
.btn-check:focus-visible { outline: 2px solid var(--ink); outline-offset: 2px; }

/* multiple choice */
.choices { display: flex; flex-direction: column; gap: 8px; margin-bottom: 14px; }
.choice {
  display: flex; gap: 11px; align-items: flex-start;
  border: 1px solid var(--line); border-radius: var(--radius-sm);
  padding: 12px 14px; cursor: pointer; background: var(--surface-2);
  font-size: .91rem; color: var(--ink-soft); line-height: 1.55;
  min-height: 44px; transition: border-color .15s;
}
.choice:hover { border-color: var(--primary); }
.choice input { margin: 3px 0 0; flex: none; width: 17px; height: 17px; accent-color: var(--primary); }
.choice.picked { border-color: var(--primary); background: var(--primary-100); color: var(--ink); }

/* verdict shown after checking */
.verdict {
  margin-top: 14px; padding: 14px 16px; border-radius: var(--radius-sm);
  font-size: .89rem; line-height: 1.65; border: 1px solid var(--line);
  background: var(--surface-2); color: var(--ink-soft);
}
.verdict[hidden] { display: none; }
.verdict .tag {
  display: block; font-size: .7rem; font-weight: 800; text-transform: uppercase;
  letter-spacing: .05em; margin-bottom: 5px;
}
.verdict.close { background: var(--ok-bg); border-color: var(--ok-border); color: var(--ok-text); }
.verdict.close .tag { color: var(--ok-text-strong); }

/* Deliberately NOT the danger palette.
   The red treatment that used to live here marked a missed estimate the way
   the site marks a broken calculator. These questions ask for a guess, and on
   the prediction step being wrong is the designed outcome, so scoring it in
   red teaches a reader that guessing is punished. They stop guessing, and the
   point of guessing first is lost. Missing gets a neutral surface with a
   left rule: visibly a different result from "close", not a failure. */
.verdict.info {
  background: var(--surface-2); border-color: var(--line);
  border-left: 3px solid var(--primary); color: var(--ink-soft);
}
.verdict.info .tag { color: var(--ink); }
.verdict strong { color: inherit; font-weight: 700; }
.verdict .figure { font-variant-numeric: tabular-nums; font-weight: 800; }
/* The explanations are multi-paragraph. Without this they render as one
   run-on block, because the reset strips paragraph margins. */
.verdict p { margin: 0 0 10px; }
.verdict p:last-child { margin-bottom: 0; }
.verdict .gap-note {
  font-size: .84rem; color: var(--ink-faint);
  border-top: 1px solid var(--line); padding-top: 10px; margin-top: 12px;
}

/* reveal-on-click answer */
.reveal-btn {
  border: 1px dashed var(--line); background: var(--surface-2); color: var(--primary-600);
  font-family: var(--font); font-weight: 600; font-size: .87rem;
  padding: 10px 16px; border-radius: var(--radius-sm); cursor: pointer;
  min-height: 44px; transition: border-color .15s, background .15s;
}
.reveal-btn:hover { border-color: var(--primary); background: var(--primary-100); }
.reveal-body {
  margin-top: 12px; padding: 14px 16px; border-radius: var(--radius-sm);
  background: var(--surface-2); border: 1px solid var(--line);
  font-size: .89rem; color: var(--ink-soft); line-height: 1.68;
}
.reveal-body[hidden] { display: none; }
.reveal-body p { margin: 0 0 10px; }
.reveal-body p:last-child { margin-bottom: 0; }

/* the predict step, before the calculator exists on the page */
.predict {
  background: color-mix(in srgb, var(--primary) 7%, var(--surface));
  border: 1px solid var(--primary-100); border-radius: var(--radius);
  padding: 22px 24px;
}
.predict h3 { font-size: 1.05rem; font-weight: 700; margin: 0 0 8px; }
.predict p { font-size: .93rem; color: var(--ink-soft); line-height: 1.68; margin: 0 0 14px; }

/* Two sided ledger where one side is deliberately blank */
.ledger-gap {
  display: grid; grid-template-columns: 1fr 1fr; gap: 12px; margin-top: 18px;
}
.ledger-gap .side {
  border: 1px solid var(--line); border-radius: var(--radius-sm);
  padding: 12px 14px; background: var(--surface-2);
}
.ledger-gap .side .v {
  font-size: 1.15rem; font-weight: 800; font-variant-numeric: tabular-nums;
  margin-top: 3px; color: var(--ink);
}
.ledger-gap .side.empty {
  border-style: dashed;
  background: repeating-linear-gradient(
    135deg, transparent, transparent 7px,
    color-mix(in srgb, var(--ink-faint) 8%, transparent) 7px,
    color-mix(in srgb, var(--ink-faint) 8%, transparent) 14px);
}
.ledger-gap .side.empty .v { color: var(--ink-faint); font-size: 1.02rem; }
.ledger-gap .hint-line { font-size: .74rem; color: var(--ink-faint); margin-top: 4px; line-height: 1.4; }
@media (max-width: 520px) { .ledger-gap { grid-template-columns: 1fr; } }

/* ranking exercise */
.rank-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(220px, 1fr)); gap: 14px; margin-bottom: 16px; }
.rank-card {
  border: 1px solid var(--line); border-radius: var(--radius-sm);
  background: var(--surface-2); padding: 16px 18px;
}
.rank-card h3, .rank-card h4 { font-size: .95rem; font-weight: 700; margin: 0 0 6px; }
.rank-card p { font-size: .86rem; color: var(--ink-soft); line-height: 1.6; margin: 0 0 10px; }
.rank-card .cost {
  font-size: 1.15rem; font-weight: 800; font-variant-numeric: tabular-nums;
  color: var(--ink);
}
.rank-card .cost-lbl { font-size: .7rem; font-weight: 800; text-transform: uppercase; letter-spacing: .05em; color: var(--ink-faint); }

/* ============================================================
   Compounding chart
   ============================================================ */
/* CLS: the holder is an empty div until the script injects the SVG, so the
   page painted at zero height and then jumped by the full chart height.
   The reservation applies only while the holder is empty, so once the real
   SVG lands its own height governs the box. */
div.cx-chart:empty { aspect-ratio: 640 / 380; }
.cx-chart { width: 100%; }
.cx-chart svg { display: block; width: 100%; height: auto; }

.cx-contrib { fill: color-mix(in srgb, var(--ink-faint) 40%, transparent); }
.cx-growth  { fill: color-mix(in srgb, var(--money) 42%, transparent); }
.cx-line    { fill: none; stroke: var(--money-text); stroke-width: 2; }
.cx-grid    { stroke: var(--line-soft); stroke-width: 1; }
.cx-axis-line { stroke: var(--line); stroke-width: 1.5; }
.cx-axis    { fill: var(--ink-faint); font-size: 11px; font-family: var(--font); }
.cx-axis-title { fill: var(--ink-faint); font-size: 12px; font-weight: 700; font-family: var(--font); }
.cx-marker  { stroke: var(--primary); stroke-width: 2; stroke-dasharray: 5 4; }
.cx-marker-label { fill: var(--primary-600); font-size: 11px; font-weight: 700; font-family: var(--font); }

.cx-legend { display: flex; gap: 18px; flex-wrap: wrap; margin-top: 10px; font-size: .84rem; color: var(--ink-soft); }
.cx-legend span { display: inline-flex; align-items: center; gap: 7px; }
.cx-legend .sw { width: 13px; height: 13px; border-radius: 3px; flex: none; }
.cx-legend .sw.c { background: color-mix(in srgb, var(--ink-faint) 40%, transparent); }
.cx-legend .sw.g { background: color-mix(in srgb, var(--money) 42%, transparent); }

/* Risk paths */
.rk-steady   { fill: none; stroke: var(--ink-faint); stroke-width: 2; stroke-dasharray: 6 4; }
.rk-volatile { fill: none; stroke: var(--primary); stroke-width: 2.2; }
.rk-order-a  { fill: none; stroke: var(--primary); stroke-width: 2.2; }
.rk-order-b  { fill: none; stroke: var(--amber-strong); stroke-width: 2.2; stroke-dasharray: 5 4; }
.rk-mixed    { fill: none; stroke: var(--money-text); stroke-width: 2.6; }
.rk-legend .sw.mixed { background: var(--money-text); }

.rk-legend .sw.steady   { background: var(--ink-faint); }
.rk-legend .sw.volatile { background: var(--primary); }
.rk-legend .sw.a        { background: var(--primary); }
.rk-legend .sw.b        { background: var(--amber-strong); }

.seed-row { display: flex; gap: 10px; align-items: flex-end; flex-wrap: wrap; }
.seed-row .field { margin: 0; flex: 1 1 130px; }
.seed-note { font-size: .78rem; color: var(--ink-faint); margin-top: 10px; line-height: 1.5; }
.seed-note b { color: var(--ink-soft); font-family: var(--mono); }

/* Two-person comparison */
.hs-pair { display: grid; grid-template-columns: 1fr 1fr; gap: 14px; }
@media (max-width: 560px) { .hs-pair { grid-template-columns: 1fr; } }
.hs-side {
  border: 1px solid var(--line); border-radius: var(--radius-sm);
  padding: 16px 18px; background: var(--surface-2);
}
.hs-side.lead { border-color: color-mix(in srgb, var(--money) 55%, transparent);
  background: color-mix(in srgb, var(--money) 9%, transparent); }
.hs-side .k { font-size: .68rem; font-weight: 800; text-transform: uppercase; letter-spacing: .05em; color: var(--ink-faint); }
.hs-side .v { font-size: 1.35rem; font-weight: 800; font-variant-numeric: tabular-nums; margin: 4px 0 2px; }
.hs-side.lead .v { color: var(--money-text); }
.hs-side .s { font-size: .78rem; color: var(--ink-faint); }

/* Wage table and its provenance */
.wage-table { width: 100%; border-collapse: collapse; font-size: .89rem; }
.wage-table th {
  font-size: .68rem; font-weight: 800; text-transform: uppercase; letter-spacing: .05em;
  color: var(--ink-faint); padding: 0 10px 8px 0; border-bottom: 1px solid var(--line);
  text-align: right; white-space: nowrap;
}
.wage-table th:first-child { text-align: left; }
.wage-table td {
  padding: 9px 10px 9px 0; border-bottom: 1px solid var(--line-soft);
  text-align: right; font-variant-numeric: tabular-nums; white-space: nowrap;
}
.wage-table td.wage-label { text-align: left; font-weight: 600; white-space: normal; }
.wage-table tr.wage-total td { font-weight: 800; border-top: 1px solid var(--line); border-bottom: 0; }

/* Data vintage badge, same idea as the tax-year badge on the finance site:
   the age of the numbers travels with the numbers. */
.data-note {
  display: inline-flex; align-items: center; gap: 8px; font-size: .78rem; font-weight: 600;
  color: var(--ink-soft); background: var(--surface-2); border: 1px solid var(--line);
  padding: 7px 12px; border-radius: 8px; margin-bottom: 14px;
}
.data-note .dot {
  width: 8px; height: 8px; border-radius: 50%; background: var(--amber-strong); flex: none;
}
.provenance {
  font-size: .82rem; color: var(--ink-faint); line-height: 1.6;
  border-top: 1px solid var(--line-soft); margin-top: 14px; padding-top: 12px;
  /* Provenance lines carry full source URLs, and a URL has nothing in it a
     browser is willing to break at. Measured 2026-08-06: module-7-part-2
     pushed the whole page 22px sideways at 375px on the strength of one
     bls.gov path. Nothing else on that page overflowed. Anywhere rather than
     break-word because the offending token is one long unbroken run and
     break-word declines to split it until the line is already too wide. */
  overflow-wrap: anywhere;
}
.provenance b { color: var(--ink-soft); }
/* Same reason, and this is the class the citation on module-7 Part 2 actually
   sits in. The first attempt at this fix put the rule only on .provenance,
   measured again, and found the page still 22px wide: source lines live in
   both classes and only one of them had been looked at. */
.note-small { overflow-wrap: anywhere; }

/* A pair of readouts either side of a comparison */
.prem-pair { display: grid; grid-template-columns: 1fr 1fr; gap: 12px; margin-top: 4px; }
@media (max-width: 520px) { .prem-pair { grid-template-columns: 1fr; } }

/* Checkbox row */
.check-row { display: flex; gap: 10px; align-items: flex-start; margin-top: 4px; min-height: 44px; }
.check-row input { margin-top: 3px; width: 18px; height: 18px; flex: none; accent-color: var(--primary); }
.check-row label { font-size: .88rem; color: var(--ink-soft); line-height: 1.55; font-weight: 500; }

/* ============================================================
   Module 5: the life simulation
   ============================================================ */
.sim-screen[hidden] { display: none; }

/* Status bar: where you are, at a glance, every turn */
.sim-status {
  display: grid; grid-template-columns: repeat(auto-fit, minmax(128px, 1fr));
  gap: 1px; background: var(--line); border: 1px solid var(--line);
  border-radius: var(--radius); overflow: hidden; margin-bottom: 20px;
}
.sim-status > div { background: var(--surface); padding: 12px 14px; }
.sim-status .k {
  font-size: .64rem; font-weight: 800; text-transform: uppercase;
  letter-spacing: .05em; color: var(--ink-faint);
}
.sim-status .v {
  font-size: 1.05rem; font-weight: 800; font-variant-numeric: tabular-nums;
  margin-top: 3px; letter-spacing: -.01em;
}
.sim-status .s { font-size: .72rem; color: var(--ink-faint); margin-top: 1px; }
/* THREE TIERS, AND COLOR HAS TO BE EARNED.

   The status bar had seven numbers in identical black, so nothing on it
   told a student where to look. The fix is not to color all seven: a
   number that is simply a fact, like what you earn, stays plain, because
   coloring everything is the same as coloring nothing.

   Color means one thing here, which is whether this number is working for
   you or against you. Green for a position that is doing its job, amber for
   one that wants attention, red for one that is actively costing you. That
   is why a mortgage and a credit card are no longer the same color: the
   code comment above the debt list has always said they are not the same
   object, and the screen was drawing them identically anyway. */
.sim-status .v.warn { color: var(--danger); }

/* Who the card is about, under its title. A caption rather than a sentence,
   because the card's own writing already reads as prose and this is the
   answer to "who is Ainsley again" after forty years of a life. */
.sim-card .who-caption {
  margin: -2px 0 8px;
  font-size: .84rem;
  color: var(--ink-soft);
  font-style: italic;
}

/* A second line under a budget line's label, for the detail that makes the
   figure actionable: what kind of match an employer gives, and so on. Quiet
   enough that the column of amounts still reads as a column. */
.budget-line .line-note {
  display: block;
  font-size: .78rem;
  color: var(--ink-faint);
  line-height: 1.3;
  margin-top: 1px;
}

/* ---- the life this browser is already holding ----
   Sits above the button that starts a different one, and is boxed so it
   reads as a thing that already exists rather than as a third option in a
   row of buttons. */
.sim-continue {
  margin: 16px 0 4px;
  padding: 14px 16px;
  border: 1px solid var(--line);
  border-left: 3px solid var(--primary);
  border-radius: var(--radius-sm);
  background: var(--surface);
}
.sim-continue .k { margin-bottom: 4px; }
.sim-continue p { margin: 0; }

/* Said out loud only when writing to this browser actually failed. */
.note-small.warn { color: var(--danger); }

/* ---- the interface gate's frame ----
   The simulation under test, kept visible rather than hidden, because a
   frame with display:none has no layout and every check on that page is a
   check of layout. Scaled down to sit on the page without dominating it;
   the frame's own viewport is whatever the driver sets, and transform does
   not change that. */
.i-stage-wrap {
  max-width: var(--maxw);
  margin: 40px auto 60px;
  padding: 0 24px;
}
.i-stage-wrap .k { margin-bottom: 10px; }
#i-frame {
  width: 1366px; height: 768px;
  border: 1px solid var(--line);
  border-radius: var(--radius-sm);
  background: var(--bg);
  transform: scale(.5);
  transform-origin: top left;
  /* Half scale, so the box on the page is half the frame's real size and
     the rest of the page is not pushed sideways by a 1366px child. */
  margin-bottom: -50%;
}
.vsummary.ok { color: var(--good); }
.vsummary.bad { color: var(--danger); }
.sim-status .v.caution { color: var(--amber-strong); }
.sim-status .v.good { color: var(--money-text); }

/* Age progress */
.sim-progress { display: flex; align-items: center; gap: 12px; margin-bottom: 6px; flex-wrap: wrap; }
.sim-progress .age { font-size: 1.6rem; font-weight: 800; letter-spacing: -.02em; }
.sim-progress .phase { font-size: .8rem; color: var(--ink-faint); font-weight: 600; }
/* `overflow: hidden` dropped and `position: relative` added, so the horizon
   ticks can sit on the rail. The fill bar keeps its own radius, so nothing
   spills past the rounded ends without the clip. */
.sim-track {
  height: 6px; border-radius: 4px; background: var(--line-soft);
  margin-bottom: 22px; position: relative;
}
.sim-track > i#t-track { display: block; height: 100%; background: var(--primary); border-radius: 4px; transition: width .4s; }

/* ---- WHAT IS STILL AHEAD ----
   A fixed date the model enforces, marked on the rail so the run has a
   shape to play toward instead of seventy identical years. Only ever drawn
   for thresholds not yet reached: a tick for something already passed reads
   as a thing still to come, which is worse than no tick. */
.sim-track > i.tick {
  position: absolute; top: -3px; width: 2px; height: 12px;
  background: var(--ink-faint);
  border-radius: 1px;
  opacity: .55;
  transform: translateX(-1px);
}
.sim-progress .horizon {
  font-size: .78rem; color: var(--ink-faint); font-weight: 600;
  margin-left: auto;
}
@media (max-width: 560px) {
  /* The flex row wraps on a phone, and pushed right it lands alone on its
     own line looking like a heading. Let it sit under the age instead. */
  .sim-progress .horizon { margin-left: 0; flex-basis: 100%; }
}

/* Wellbeing band readout */
.band-card {
  border: 1px solid var(--line); border-radius: var(--radius);
  padding: 16px 18px; background: var(--surface-2); margin-bottom: 20px;
}
.band-card .top { display: flex; align-items: baseline; gap: 10px; flex-wrap: wrap; }
.band-card .name { font-size: 1.05rem; font-weight: 800; }
.band-card .num { font-size: .82rem; color: var(--ink-faint); font-variant-numeric: tabular-nums; }
.band-card p { font-size: .88rem; color: var(--ink-soft); line-height: 1.6; margin: 6px 0 0; }
.band-meter { height: 8px; border-radius: 5px; background: var(--line-soft); overflow: hidden; margin-top: 10px; }
.band-meter > i { display: block; height: 100%; border-radius: 5px; transition: width .5s; }

/* Cards, revealed one at a time */
.sim-cards { display: flex; flex-direction: column; gap: 12px; }
.sim-card {
  border: 1px solid var(--line); border-radius: var(--radius);
  background: var(--surface); padding: 16px 18px;
  box-shadow: var(--shadow-sm);
  /* Fill mode deliberately left at the default `none` rather than `both`.

     `both` holds the from-keyframe before the animation runs and the
     to-keyframe after, which means an animation that never advances pins
     opacity at 0 and the element is there, laid out, and invisible. With no
     fill mode the element's own style applies whenever the animation is not
     actively running, so the worst a stalled animation can do is skip the
     entrance. See the note on .sim-modal, where the same pattern could hide
     a modal dialog and leave the page inert behind it. */
  animation: cardIn .34s cubic-bezier(.2,.7,.3,1);
}
@keyframes cardIn {
  from { opacity: 0; transform: translateY(10px) scale(.985); }
  to   { opacity: 1; transform: none; }
}
@media (prefers-reduced-motion: reduce) {
  .sim-card { animation: none; }
  .sim-track > i, .band-meter > i { transition: none; }
}
.sim-card .kind {
  font-size: .64rem; font-weight: 800; text-transform: uppercase;
  letter-spacing: .06em; color: var(--ink-faint);
}
.sim-card h3, .sim-card h4 { font-size: 1rem; font-weight: 700; margin: 4px 0 6px; }
.sim-card p { font-size: .89rem; color: var(--ink-soft); line-height: 1.6; margin: 0; }
.sim-card .cost {
  font-size: 1.05rem; font-weight: 800; font-variant-numeric: tabular-nums;
  margin-top: 10px;
}
.sim-card .cost.out { color: var(--danger); }
.sim-card .cost.in { color: var(--money-text); }
.sim-card.happened { border-left: 3px solid var(--ink-faint); }
.sim-card.offer { border-left: 3px solid var(--primary); }
.sim-card.career { border-left: 3px solid var(--amber-strong); }

/* The marker that replaces rarity. Meaning, not luck. */
.once-marker {
  display: inline-flex; align-items: center; gap: 6px;
  font-size: .68rem; font-weight: 800; text-transform: uppercase; letter-spacing: .05em;
  color: var(--amber-strong); background: color-mix(in srgb, var(--amber-strong) 14%, transparent);
  padding: 3px 9px; border-radius: 999px; margin-top: 10px;
}
.card-actions { display: flex; gap: 9px; margin-top: 12px; flex-wrap: wrap; }
.card-actions button { min-height: 44px; }

/* ---- TWO REAL ANSWERS, WEIGHTED THE SAME ----

   REPORTED 2026-08-05, of the enlistment card: a blue button beside a white
   one reads as the recommended one, whatever the words say.

   On a card where both buttons are named, neither answer is the default and
   the color must not imply one. They are both the neutral treatment and the
   difference is made on hover and on focus instead, which is a signal about
   where the pointer is rather than about which choice is correct. */
.card-actions.even button:hover {
  border-color: var(--primary);
  background: var(--surface-2);
  color: var(--ink);
}
.card-actions.even button:focus-visible {
  border-color: var(--primary);
}
.btn-take {
  border: 1px solid var(--primary); background: var(--primary); color: var(--primary-fg);
  font-family: var(--font); font-weight: 600; font-size: .9rem;
  padding: 10px 18px; border-radius: var(--radius-sm); cursor: pointer;
}
.btn-take:disabled { opacity: .45; cursor: not-allowed; }
/* Armed. Used by "Stop working", which selects rather than acting, so it
   needs a state a player can see before they live the year. Inverted to ink
   rather than tinted, because unlike a preference toggle this one is worth
   being the loudest thing in its box. */
.btn-take[aria-pressed="true"] {
  background: var(--ink); border-color: var(--ink); color: var(--surface);
}
.btn-take[aria-pressed="true"]:hover { background: var(--ink); }
.btn-pass {
  border: 1px solid var(--line); background: var(--surface); color: var(--ink-soft);
  font-family: var(--font); font-weight: 600; font-size: .9rem;
  padding: 10px 18px; border-radius: var(--radius-sm); cursor: pointer;
}
.btn-take:hover { background: var(--primary-600); }
.btn-pass:hover { border-color: var(--primary); color: var(--ink); }
/* The take button on a two-way card is drawn as a pass, so this needs the
   same disabled treatment its blue counterpart already had. Without it a
   card nobody can afford looked answerable. */
.btn-pass:disabled { opacity: .45; cursor: not-allowed; }
.btn-pass:disabled:hover { border-color: var(--line); color: var(--ink-soft); }
.card-chosen {
  font-size: .82rem; font-weight: 700; margin-top: 10px;
}
.card-chosen.took { color: var(--money-text); }
.card-chosen.passed { color: var(--ink-faint); }

/* Standing choices */
.choice-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(210px, 1fr)); gap: 14px; }
.choice-block { border: 1px solid var(--line); border-radius: var(--radius-sm); padding: 14px 16px; background: var(--surface); }
.choice-block .k {
  font-size: .64rem; font-weight: 800; text-transform: uppercase;
  letter-spacing: .05em; color: var(--ink-faint); margin-bottom: 7px;
}
.choice-block select { width: 100%; }
.choice-block .cost { font-size: .78rem; color: var(--ink-faint); margin-top: 6px; font-variant-numeric: tabular-nums; }
.choice-block .blurb { font-size: .78rem; color: var(--ink-soft); line-height: 1.5; margin-top: 6px; }

/* Budget readout */
.budget-line {
  display: flex; justify-content: space-between; gap: 12px;
  padding: 9px 0; border-bottom: 1px solid var(--line-soft); font-size: .9rem;
}
.budget-line:last-child { border-bottom: 0; }
.budget-line .amt { font-variant-numeric: tabular-nums; font-weight: 700; }
.budget-line.total { border-top: 2px solid var(--line); margin-top: 4px; padding-top: 11px; font-weight: 800; }
.budget-line.neg .amt { color: var(--danger); }
.budget-line.pos .amt { color: var(--money-text); }

/* End of life record */
.record-cols { display: grid; grid-template-columns: 1fr 1fr; gap: 18px; }
@media (max-width: 720px) { .record-cols { grid-template-columns: 1fr; } }
.record-list { list-style: none; padding: 0; margin: 0; }
.record-list li {
  padding: 9px 0; border-bottom: 1px solid var(--line-soft);
  font-size: .88rem; color: var(--ink-soft); display: flex; gap: 10px;
}
.record-list li .age {
  font-variant-numeric: tabular-nums; color: var(--ink-faint);
  font-weight: 700; flex: none; width: 30px;
}
.record-list.missed li { color: var(--ink-faint); }
.record-empty { font-size: .86rem; color: var(--ink-faint); font-style: normal; padding: 10px 0; }

.life-chart { width: 100%; }
div.life-chart:empty { aspect-ratio: 640 / 380; }
.life-chart svg { display: block; width: 100%; height: auto; }
.lc-well { fill: none; stroke: var(--primary); stroke-width: 2.4; }
.lc-retire { stroke: var(--money-text); stroke-width: 2; stroke-dasharray: 5 4; }
.lc-retire-label { fill: var(--money-text); font-size: 11px; font-weight: 700; font-family: var(--font); }
.lc-bandline { stroke: var(--line-soft); stroke-width: 1; }
.lc-bandlabel { fill: var(--ink-faint); font-size: 10px; font-family: var(--font); }

/* Export block */
.export-box {
  background: var(--surface); border: 1px solid var(--line);
  border-radius: var(--radius); box-shadow: var(--shadow-sm);
  padding: 22px 24px; margin-top: 20px;
}
.export-box h2, .export-box h3 { font-size: 1.05rem; font-weight: 700; margin: 0 0 8px; }
.export-box > p { font-size: .91rem; color: var(--ink-soft); line-height: 1.65; margin: 0 0 16px; }
.export-actions { display: flex; gap: 10px; flex-wrap: wrap; align-items: center; margin-top: 14px; }
.export-actions .btn-check { flex: none; }
.btn-quiet {
  border: 1px solid var(--line); background: var(--surface); color: var(--ink-soft);
  font-family: var(--font); font-weight: 600; font-size: .9rem;
  padding: 11px 18px; border-radius: var(--radius-sm); cursor: pointer;
  min-height: 44px; transition: border-color .15s, color .15s;
}
.btn-quiet:hover { border-color: var(--primary); color: var(--ink); }
.btn-quiet.danger:hover { border-color: var(--danger); color: var(--danger); }
.export-status { font-size: .84rem; color: var(--money-text); margin-top: 12px; min-height: 1.2em; font-weight: 600; }
.export-privacy {
  font-size: .8rem; color: var(--ink-faint); line-height: 1.55;
  border-top: 1px solid var(--line-soft); margin-top: 16px; padding-top: 14px;
}

/* Course hub cards */
.hub-grid {
  display: grid; grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
  gap: 16px; margin-top: 26px;
}
.course-card {
  display: block; background: var(--surface); border: 1px solid var(--line);
  border-radius: var(--radius); box-shadow: var(--shadow-sm);
  padding: 20px 22px; transition: border-color .15s, transform .15s, box-shadow .15s;
}
a.course-card:hover { border-color: var(--primary); transform: translateY(-2px); box-shadow: var(--shadow-md); }
.course-card .n {
  font-size: .68rem; font-weight: 800; text-transform: uppercase;
  letter-spacing: .05em; color: var(--ink-faint);
}
.course-card h3 { font-size: 1.05rem; font-weight: 700; margin: 5px 0 7px; color: var(--ink); }
.course-card p { font-size: .88rem; color: var(--ink-soft); line-height: 1.6; margin: 0; }
.course-card .status {
  display: inline-block; margin-top: 12px; font-size: .7rem; font-weight: 800;
  text-transform: uppercase; letter-spacing: .05em; padding: 3px 9px; border-radius: 999px;
}
.course-card .status.live { background: var(--ok-bg); color: var(--ok-text); }
.course-card .status.soon { background: var(--line-soft); color: var(--ink-faint); }
.course-card .status.part { background: var(--primary-100); color: var(--primary-600); }
.course-card.pending { opacity: .78; }

/* Verification harness */
.vt { width: 100%; border-collapse: collapse; font-size: .86rem; }
.vt th {
  font-size: .68rem; font-weight: 800; text-transform: uppercase; letter-spacing: .05em;
  color: var(--ink-faint); text-align: left; padding: 0 12px 8px 0; border-bottom: 1px solid var(--line);
}
.vt td { padding: 9px 12px 9px 0; border-bottom: 1px solid var(--line-soft); vertical-align: top; }
.vt td.num { font-variant-numeric: tabular-nums; font-family: var(--mono); font-size: .8rem; white-space: nowrap; }
.vt .pass { color: var(--ok-text); font-weight: 800; }
.vt .fail { color: var(--bad-text); font-weight: 800; }
.vsummary {
  font-size: 1.05rem; font-weight: 700; padding: 16px 18px; border-radius: var(--radius-sm);
  margin-bottom: 20px; border: 1px solid var(--line); background: var(--surface-2);
}
.vsummary.ok { background: var(--ok-bg); border-color: var(--ok-border); color: var(--ok-text-strong); }
.vsummary.bad { background: var(--bad-bg); border-color: var(--bad-border); color: var(--bad-text-strong); }

/* Responsive */
@media (max-width: 768px) {
  .nav { gap: 16px; }
  .module-section h2 { font-size: 1.25rem; }
  .hero { padding: 36px 24px 20px; }
}

/* Selectable cards, used for the opening choices instead of dropdowns */
.pick-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(230px, 1fr)); gap: 12px; margin-top: 14px; }
.pick {
  text-align: left; cursor: pointer; font-family: var(--font);
  border: 1px solid var(--line); border-radius: var(--radius);
  background: var(--surface); padding: 15px 16px; min-height: 44px;
  transition: border-color .15s, transform .12s, box-shadow .15s;
}
.pick:hover { border-color: var(--primary); transform: translateY(-1px); box-shadow: var(--shadow-md); }
.pick[aria-pressed="true"] { border-color: var(--primary); border-width: 2px; padding: 14px 15px;
  background: color-mix(in srgb, var(--primary) 7%, var(--surface)); }
.pick .t { font-size: .98rem; font-weight: 700; color: var(--ink); display: block; }
.pick .d { font-size: .82rem; color: var(--ink-soft); line-height: 1.55; margin-top: 5px; display: block; }
.pick .m { font-size: .72rem; font-weight: 800; text-transform: uppercase; letter-spacing: .05em;
  color: var(--ink-faint); margin-top: 8px; display: block; }
.pick:disabled { opacity: .5; cursor: not-allowed; }
.pick:disabled:hover { border-color: var(--line); transform: none; box-shadow: none; }

/* ============================================================
   Card modal

   The card is the interruption, so it gets the whole screen's
   attention rather than a slot in the page flow. Inside it, the
   card keeps its normal .sim-card styling: it is the same card,
   put somewhere you cannot scroll past.

   No close button and no Escape. Every card in the queue needs a
   decision, and an overlay you can dismiss without deciding would
   put the run into a state the model has no answer for.
   ============================================================ */
/* ---- A MODAL THAT IS TALLER THAN THE SCREEN MUST SCROLL ----

   REPORTED twice, on 2026-08-03, as the run locking up after finishing a
   course: "can no longer click or interact with anything". Both reports
   came right after a graduation, and the reason is that the job hand is by
   some way the tallest dialog here, seven options with a description each.

   `overflow: visible` meant the dialog box clipped to its max-height while
   the content carried on past it, with no scrollbar to reach the rest.
   Measured on a 1366x768 laptop: the box caps at 569px, the content is
   724px, and the last option sits below the viewport with no way to get to
   it. An open dialog makes the rest of the page inert, so what a player
   sees is a screen that has stopped responding.

   It did not reproduce at 820px, which is why two reports went unexplained.
   The lesson is that a dialog has to be tested at the shortest viewport it
   will meet, not the one the developer happens to have. */
.sim-modal {
  border: none; padding: 0; background: transparent;
  max-width: 560px; width: calc(100% - 32px);
  color: var(--ink);
  overflow-y: auto;
  overscroll-behavior: contain;
}
.sim-modal::backdrop {
  background: rgba(10, 37, 64, .55);
  backdrop-filter: blur(2px);
}
:root[data-theme="dark"] .sim-modal::backdrop { background: rgba(0, 0, 0, .66); }

/* ---- a modal must never be able to open invisibly ----

   An open <dialog> makes the rest of the page inert. If the dialog is also
   transparent, the player sees the screen they were on, nothing responds to
   the mouse, and there is no way out: Escape is deliberately cancelled on
   both modals here, because every card and the graduation job hand need an
   answer. That is indistinguishable from the application hanging.

   `animation-fill-mode: both` created exactly that risk. It holds the
   from-keyframe, opacity 0, whenever the animation is not running, so any
   condition that stops the animation advancing leaves an invisible modal
   over an inert page. Dropping the fill mode means the dialog's own style
   applies unless the animation is actively running, and a stalled animation
   costs the entrance rather than the run.

   Dropping the fill mode is not enough on its own. A running animation
   applies its own from-keyframe whatever the fill mode says, so anything
   that stalls the timeline still shows opacity 0. The only way for a modal
   to be structurally incapable of opening invisibly is for nothing to
   animate its opacity, so modalIn moves transform alone. The fade lives on
   the backdrop, which can safely be missed.

   Found while chasing a report of the game locking up after graduating,
   which is when the job hand dialog opens. That report is NOT confirmed
   fixed by this: the invisible dialog reproduced only under a frozen
   document timeline in a headless pane, which is a property of the test rig
   and not of any browser a student will use. This removes the failure mode.
   The report itself still needs a retest in a real browser. */
.sim-modal[open] {
  opacity: 1;
  animation: modalIn .26s cubic-bezier(.2,.7,.3,1);
}
@keyframes modalIn {
  from { transform: translateY(14px) scale(.97); }
  to   { transform: none; }
}
.sim-modal[open]::backdrop {
  animation: backdropIn .26s ease both;
}
@keyframes backdropIn {
  from { opacity: 0; }
  to   { opacity: 1; }
}
@media (prefers-reduced-motion: reduce) {
  .sim-modal[open], .sim-modal[open]::backdrop { animation: none; }
}

/* The card inside the modal fills it and drops the entrance animation,
   since the modal itself is already animating in. */
.sim-modal .sim-card {
  margin: 0;
  box-shadow: var(--shadow-lg);
  animation: none;
}
.sim-modal .note-small { color: var(--cta-fg-soft); text-align: center; }

/* Buttons go full width and stack on a narrow screen, because a modal on a
   phone is the one place a two-across button row gets cramped. */
.sim-modal .card-actions { display: flex; gap: 10px; flex-wrap: wrap; }
.sim-modal .card-actions button { flex: 1 1 auto; min-height: 44px; }

@media (prefers-reduced-motion: reduce) {
  .sim-modal[open] { animation: none; }
}

/* What a card does, listed under the blurb. Deliberately plain: these are
   consequences, not features, and a badge row would sell them. */
.card-effects {
  list-style: none; margin: 12px 0 0; padding: 0;
  display: flex; flex-direction: column; gap: 5px;
}
.card-effects li {
  font-size: .84rem; color: var(--ink-soft);
  padding-left: 16px; position: relative; line-height: 1.4;
}
.card-effects li::before {
  content: ""; position: absolute; left: 0; top: .52em;
  width: 6px; height: 6px; border-radius: 50%;
  background: var(--ink-faint);
}

/* ============================================================
   The tree playback

   Only opacity and transform move. The tree is fully drawn before
   any of this runs, so a browser that ignores the animation shows a
   finished tree rather than an empty box.
   ============================================================ */
.tree-step {
  opacity: 0;
  transform: translateX(-6px);
  transition: opacity .38s ease, transform .38s cubic-bezier(.2,.7,.3,1);
}
.tree-step.on { opacity: 1; transform: none; }

.tree-mark {
  margin-top: 18px; text-align: center;
  font-size: 1.35rem; font-weight: 800; letter-spacing: -.02em;
  color: var(--ink);
  opacity: 0; transform: translateY(6px);
  transition: opacity .9s ease, transform .9s cubic-bezier(.2,.7,.3,1);
}
.tree-mark.on { opacity: 1; transform: none; }

/* No playback at all for readers who have asked for less motion: the
   stylesheet shows everything and the script skips the timers. */
@media (prefers-reduced-motion: reduce) {
  .tree-step { opacity: 1; transform: none; transition: none; }
  .tree-mark { transition: none; }
}

/* Standing choices on the turn screen. Denser than the setup grid, because
   there are four groups of them on one screen rather than one group with the
   page to itself. */
.pick-grid.tight { grid-template-columns: repeat(auto-fit, minmax(190px, 1fr)); gap: 9px; margin-top: 10px; }
.pick-grid.tight .pick { padding: 11px 12px; }
.pick-grid.tight .pick[aria-pressed="true"] { padding: 10px 11px; }
.pick-grid.tight .pick .t { font-size: .9rem; }
.pick-grid.tight .pick .d { font-size: .78rem; }
.pick-grid.tight .pick .m { font-size: .74rem; }

/* Out of reach, and saying why. Not hidden: the reason it is unavailable is
   the thing being taught. */
.pick[disabled] { opacity: .55; cursor: not-allowed; }
.pick[disabled]:hover { border-color: var(--line); transform: none; box-shadow: none; }

/* ---- RULED OUT, NOT JUST DIMMED ----

   REPORTED 2026-08-04: an option a life cannot take "should have a visible
   x or cross-out over it to be sure students don't get confused. Maybe a
   small footnote."

   Dimming alone is ambiguous: at a glance it reads as "not chosen" as
   easily as "not available", and the two mean opposite things on a screen
   full of choices. The title gets a line through it, which says ruled out
   in a way nothing else on the card does, and the reason line underneath is
   the footnote. Both together, because a line with no explanation is just
   as confusing in the other direction.

   Only the title, not the whole card: striking the reason line through
   would make the one piece of text that explains the lock the hardest thing
   on the card to read. */
.pick[disabled] .t {
  text-decoration: line-through;
  text-decoration-thickness: 1.5px;
  text-decoration-color: var(--ink-faint);
}
.pick[disabled] .m {
  opacity: 1;
  font-style: italic;
  color: var(--ink-soft);
}

/* ---- NOT CHOSEN, AS OPPOSED TO NOT AVAILABLE ----

   REPORTED 2026-08-05, against Things You Could Do: once the slots are full
   the unpicked options should dim.

   Deliberately a lighter touch than `[disabled]`, and deliberately not the
   same treatment: a disabled option cannot be taken and says why, a dimmed
   one is one click from being taken and swaps for what is there. It comes
   back to full strength on hover and on focus, because the moment you are
   pointing at it, it is a live choice again. */
.pick.dimmed { opacity: .62; }
.pick.dimmed:hover,
.pick.dimmed:focus-visible { opacity: 1; }
@media (prefers-reduced-motion: no-preference) {
  .pick.dimmed { transition: opacity .14s; }
}

/* The credit version of an offer, shown beside the cash version so the
   comparison is on the card rather than in the player's head. */
.credit-alt {
  margin-top: 14px; padding-top: 12px;
  border-top: 1px dashed var(--line);
}
.credit-alt .k {
  font-size: .64rem; font-weight: 800; text-transform: uppercase;
  letter-spacing: .06em; color: var(--ink-faint);
}
.credit-alt .card-effects { margin-top: 6px; }

/* ============================================================
   Turn screen tabs

   Three panels instead of one long column. The advance and retire
   controls live outside them so they are reachable from any tab.
   ============================================================ */
.sim-tabs {
  display: flex; gap: 4px; flex-wrap: wrap;
  border-bottom: 1px solid var(--line);
  margin: 26px 0 0;
}
.sim-tab {
  appearance: none; cursor: pointer; font-family: var(--font);
  font-size: .92rem; font-weight: 600;
  color: var(--ink-faint); background: transparent;
  border: 1px solid transparent; border-bottom: none;
  border-radius: var(--radius-sm) var(--radius-sm) 0 0;
  padding: 11px 18px; min-height: 44px;
  position: relative; bottom: -1px;
  display: inline-flex; align-items: center; gap: 8px;
  transition: color .15s, background .15s, border-color .15s;
}
.sim-tab:hover { color: var(--ink); background: var(--surface-2); }
.sim-tab.on {
  color: var(--ink); background: var(--surface);
  border-color: var(--line); border-bottom: 1px solid var(--surface);
  font-weight: 700;
}

/* A tab can hide a consequence, which is the risk with tabs. This is how
   the money tab says so without the player having to open it. */
.tab-flag {
  display: inline-grid; place-items: center;
  width: 18px; height: 18px; border-radius: 50%;
  background: var(--badge-warn-bg); color: var(--badge-warn-fg);
  font-size: .68rem; font-weight: 800; line-height: 1;
}

@media (max-width: 520px) {
  .sim-tab { flex: 1 1 auto; justify-content: center; padding: 11px 10px; font-size: .86rem; }
}

/* The forks along the path, revealed as the line reaches them. */
.fork { opacity: 0; transition: opacity .45s ease; }
.fork.on { opacity: 1; }
@media (prefers-reduced-motion: reduce) {
  .fork { opacity: 1; transition: none; }
  .path-line { transition: none !important; }
}

/* A thing that happened more than once, counted rather than repeated. */
.record-list .times {
  display: inline-block; margin-left: 6px;
  font-size: .72rem; font-weight: 700;
  color: var(--ink-faint);
}

/* ============================================================
   Your run against an ordinary one

   A band with a marker, deliberately not a rank. The band is where
   the middle half of other runs landed; the dot is where this life
   sat. Nothing here converts to a position or a percentage.
   ============================================================ */
.cmp-row { margin-bottom: 26px; }
.cmp-row:last-child { margin-bottom: 0; }

.cmp-label {
  font-size: .92rem; font-weight: 600; color: var(--ink);
  display: flex; align-items: baseline; gap: 10px; flex-wrap: wrap;
  margin-bottom: 12px;
}
.cmp-where {
  font-size: .74rem; font-weight: 700; text-transform: uppercase;
  letter-spacing: .05em; color: var(--ink-faint);
}

.cmp-track {
  position: relative; height: 10px;
  background: var(--line-soft); border-radius: 999px;
  margin: 22px 0 8px;
}
.cmp-band {
  position: absolute; top: 0; height: 100%;
  background: var(--primary-100); border-radius: 999px;
}
.cmp-median {
  position: absolute; top: -4px; width: 2px; height: 18px;
  background: var(--primary); opacity: .55;
}
.cmp-you {
  position: absolute; top: 50%; transform: translate(-50%, -50%);
  width: 15px; height: 15px; border-radius: 50%;
  background: var(--money); border: 2.5px solid var(--surface);
  box-shadow: var(--shadow-sm);
}
.cmp-you.out { background: var(--amber-strong); }
.cmp-you span {
  position: absolute; bottom: 20px; left: 50%; transform: translateX(-50%);
  font-size: .76rem; font-weight: 800; color: var(--ink);
  white-space: nowrap;
}

.cmp-scale {
  display: flex; justify-content: space-between;
  font-size: .72rem; color: var(--ink-faint);
}
.cmp-none {
  font-size: .86rem; color: var(--ink-faint);
  padding: 10px 0;
}

/* ============================================================
   Glossary and standards alignment
   ============================================================ */

.g-head { margin: 30px 0 10px; font-size: 1.05rem; }
.g-head:first-child { margin-top: 0; }
.g-dl { margin: 0; }
.g-dl dt {
  font-weight: 700; margin-top: 18px; color: var(--ink);
}
.g-dl dd {
  margin: 5px 0 0; color: var(--ink-soft); line-height: 1.6;
}
.g-where {
  display: inline-block; margin-left: 4px; font-size: .82rem;
  white-space: nowrap;
}
.g-note {
  display: block; margin-top: 6px; font-size: .84rem;
  color: var(--ink-faint); border-left: 2px solid var(--line);
  padding-left: 10px;
}

/* The table has nowrap headers and comes out 584px wide, which is wider than
   a phone. Wrapping it in .worked-wrap was not enough on its own: #st-grid is
   the element the table is built into, and it expands to its content inside
   the wrapper, so the scroll container never saw anything to scroll. The rule
   goes on the element that actually holds the table. Measured at 375px: this
   was a 225px overflow, the worst on the site. */
/* `position: relative` is doing the real work here and it is not obvious.

   The table's cells each carry a `.visually-hidden` span naming the topic for
   a screen reader. Those are `position: absolute`, so their containing block
   was the page rather than this box, and an absolutely positioned element
   whose containing block sits outside a scroll container is not clipped by
   it. Every one of them was laid out at its cell's coordinate, out to 581px,
   and each was 1px wide and invisible, so nothing on the page looked wrong
   while the page scrolled sideways.

   That is why `overflow-x: auto` alone did nothing: the table was already
   being clipped correctly. `body` measured a correct 356px the whole time
   and only `documentElement` was 581, which is the signature of content
   escaping the normal flow. Making this box the containing block brings the
   spans inside it, and they stay in the accessibility tree because visual
   clipping is not what screen readers read. */
#st-grid { overflow-x: auto; position: relative; }
.st-table { border-collapse: collapse; width: 100%; font-size: .9rem; }
.st-table th, .st-table td {
  border-bottom: 1px solid var(--line); padding: 10px 8px; text-align: left;
  vertical-align: top;
}
.st-table thead th { font-size: .8rem; color: var(--ink-soft); white-space: nowrap; }
.st-table thead th abbr { text-decoration: none; border-bottom: 1px dotted var(--line); }
.st-th { display: block; font-weight: 500; font-size: .72rem; color: var(--ink-faint); }
.st-table tbody th { font-weight: 700; }
.st-sub { display: block; font-weight: 500; font-size: .8rem; color: var(--ink-faint); }
.st-table td { text-align: center; font-size: 1.1rem; }
.lvl-full { color: var(--primary); }
.lvl-part { color: var(--ink-soft); }
.lvl-none { color: var(--ink-faint); }

.st-part { margin-bottom: 30px; }
.st-part h3 { margin: 0 0 6px; font-size: 1rem; }
.st-part p { margin: 0 0 10px; }

/* Windfall options. These are buttons carrying two lines, like .pick, but
   they live in .card-actions where the button styles are single-line. */
.card-actions.windfall { flex-wrap: wrap; }
.card-actions.windfall button {
  display: flex; flex-direction: column; align-items: flex-start;
  gap: 3px; text-align: left; line-height: 1.35;
}
.card-actions.windfall button .t { font-weight: 700; }
.card-actions.windfall button .m {
  font-size: .8rem; font-weight: 500; opacity: .85;
}

/* The end-run row, kept away from the button pressed every turn. */
.end-run-row {
  display: flex; align-items: center; gap: 12px; flex-wrap: wrap;
  padding-top: 16px; border-top: 1px solid var(--line-soft);
}
.end-run-row .note-small { margin: 0; }

/* ------------------------------------------------------------
   Milestones

   The largest events in a financial life used to be one line in a
   list of fifteen. These sit above that list and are deliberately
   loud. Kept rare: if everything is a milestone then nothing is.
   ------------------------------------------------------------ */
.milestone {
  border: 1px solid var(--money);
  border-left: 4px solid var(--money);
  border-radius: 10px;
  padding: 14px 16px;
  margin-bottom: 12px;
  /* A solid surface, not a translucent tint. A tint over an unknown
     background makes the text contrast depend on what happens to be
     behind it, which is exactly the thing the accessibility gate cannot
     measure and a reader cannot rely on. */
  background: var(--surface-2);
}
.milestone .mt {
  font-weight: 800; font-size: 1rem; color: var(--ink); margin-bottom: 4px;
}
.milestone .mb {
  font-size: .88rem; color: var(--ink-soft); line-height: 1.55;
}
/* BIG HAS TO LOOK BIG.

   This differed from an ordinary milestone by four pixels of padding and a
   fifth of a rem of type, which is why the banner was reported as still not
   feeling like anything. Paying off a house after thirty years and buying a
   coat should not be four pixels apart.

   It stayed a banner rather than becoming a modal, deliberately, on the
   reasoning that a decision interrupts and a notification does not.

   REVISED 2026-08-03. That reasoning did not survive playtesting: "I didn't
   even realize when my house was paid off." The distinction was wrong,
   because some news changes what you should do next, and a mortgage ending
   frees a payment that has gone out for thirty years. A player who does not
   notice carries on as though it were still going out.

   So a small number of milestones now carry `mark` and are shown in a modal
   before the year continues. Everything else is still a banner, and the
   weight, accent bar and short movement below are what those get. */
.milestone.big {
  padding: 20px 22px 20px 26px;
  border-left-width: 6px;
  box-shadow: 0 1px 2px rgba(10, 37, 64, .06), 0 8px 24px rgba(10, 37, 64, .07);
  /* The confetti layer is position: absolute; inset: 0, so it needs a
     positioned parent or it falls the length of the page. Added 2026-08-05
     when the paper was extended from the modal to the banners: until then no
     milestone had ever had anything absolutely positioned inside it. */
  position: relative;
}
/* The banner's own text has to stay over the paper. `.confetti` is
   pointer-events: none already, so this is purely about paint order. */
.milestone .mt, .milestone .mb, .milestone .me { position: relative; z-index: 1; }
.milestone.big .mt { font-size: 1.32rem; letter-spacing: -.01em; margin-bottom: 6px; }
.milestone.big .mb { font-size: .95rem; }

@keyframes milestone-land {
  from { opacity: 0; transform: translateY(-6px) scale(.985); }
  to   { opacity: 1; transform: none; }
}
.milestone.big { animation: milestone-land .38s cubic-bezier(.2, .7, .3, 1) both; }

/* NOT EVERY BIG MOMENT IS A GOOD ONE.

   Every milestone was bordered in the money color, which was fine while
   they were all mortgages paid off and courses finished. Long-term care
   joined them and a $160,160 bill Medicare does not touch arrived in a
   green celebratory banner. Grave ones are amber. */
.milestone.grave { border-color: var(--amber-strong); border-left-color: var(--amber-strong); }

@media (prefers-reduced-motion: reduce) {
  .milestone.big { animation: none; }
}

/* The card inside the acknowledgement modal, which carries the same two
   moods the banners do. Amber when the news is bad, so that a $160,160 care
   bill is not announced in celebration, and the money accent when it is the
   kind of thing worth stopping to enjoy.

   No opacity in the animation, for the reason set out above .sim-modal: a
   modal makes the page inert, so anything that can leave one invisible can
   leave the run looking hung. */
.sim-modal .sim-card.cheer {
  border-left: 6px solid var(--ok-text);
}
.sim-modal .sim-card.grave {
  border-left: 6px solid var(--amber-strong);
}
.sim-modal .sim-card.cheer .kind { color: var(--ok-text); }
.sim-modal .sim-card.grave .kind { color: var(--amber-strong); }
#t-mark-title { font-size: 1.32rem; letter-spacing: -.01em; margin: 6px 0 0; }
#t-mark-body { font-size: .95rem; line-height: 1.6; color: var(--ink-soft); }

/* ---- CONFETTI, CONTAINED IN THE CARD ----

   REPORTED 2026-08-04: "confetti, contained in the card, for celebratory
   events."

   Contained is the requirement and it is also the safeguard: the layer is
   inset inside the card and clips its own children, so nothing can escape
   over the page, sit above the button, or add a scrollbar. It never covers
   the card either, because `pointer-events: none` means every piece is
   invisible to the mouse and the only thing there to click is still the
   button.

   Cheer marks only. A long-term care bill arriving under falling paper would
   be the same mistake the green celebratory banner was. */
/* The layer needs a positioned parent, and no `.sim-card` rule set one.
   Scoped to the acknowledgement modal so the ordinary cards are untouched. */
.sim-modal .sim-card { position: relative; }
.confetti {
  position: absolute; inset: 0;
  overflow: hidden;
  pointer-events: none;
  border-radius: inherit;
}
.confetti i {
  position: absolute;
  top: -14px;
  width: 7px; height: 11px;
  border-radius: 1px;
  opacity: 0;
  animation: confetti-fall linear forwards;
}
@keyframes confetti-fall {
  0%   { transform: translate3d(0, -14px, 0) rotate(0deg); opacity: 0; }
  8%   { opacity: .95; }
  85%  { opacity: .95; }
  100% { transform: translate3d(var(--drift, 0), var(--fall, 260px), 0)
                    rotate(var(--spin, 360deg)); opacity: 0; }
}

/* Nothing falls for a reader who asked the page to hold still. The card is
   already marked as a celebration by its accent and its heading, so removing
   this removes decoration and not information. */
@media (prefers-reduced-motion: reduce) {
  .confetti { display: none; }
}



/* The life summary: the account of the person, set larger than the
   surrounding copy because it is the thing worth reading twice. */
.life-summary p {
  font-size: 1.02rem; line-height: 1.7; color: var(--ink);
  margin: 0 0 14px;
}
.life-summary p:last-child { margin-bottom: 0; }
.life-summary p:first-child { font-size: 1.1rem; font-weight: 600; }

/* A line that adds up other lines. Set apart so it reads as a summary of
   what is above it rather than as another cost to be counted again. */
.budget-line.total-line {
  border-top: 1px solid var(--line);
  margin-top: 8px; padding-top: 10px; font-weight: 700;
}
.budget-line.total-line .sub {
  display: block; font-weight: 500; font-size: .8rem; color: var(--ink-faint);
  margin-top: 2px;
}

/* A chapter crossing. The same component as a milestone, in the primary
   color rather than the money color, because it is a change of frame
   rather than a financial event. */
.milestone.chapter {
  border-color: var(--primary);
  border-left-color: var(--primary);
}
.milestone .me {
  font-size: .64rem; font-weight: 800; text-transform: uppercase;
  letter-spacing: .06em; color: var(--primary); margin-bottom: 4px;
}

/* The job on the table, on a card that moves you to another employer. */
.offer-role {
  border: 1px solid var(--line); border-radius: 8px;
  padding: 10px 12px; margin: 12px 0 0; background: var(--surface-2);
}
.offer-role .k {
  font-size: .64rem; font-weight: 800; text-transform: uppercase;
  letter-spacing: .06em; color: var(--ink-faint); margin-bottom: 4px;
}
.offer-role .from { font-size: .82rem; color: var(--ink-faint); margin-top: 4px; }

/* What a life was dealt before it decided anything. */
.dealt-row {
  border-left: 2px solid var(--line);
  padding: 0 0 0 12px; margin: 0 0 12px;
}
.dealt-row:last-child { margin-bottom: 0; }
.dealt-row .k {
  font-size: .68rem; font-weight: 800; text-transform: uppercase;
  letter-spacing: .06em; color: var(--ink-faint);
}
.dealt-row .v { font-weight: 700; margin-top: 2px; }
.dealt-row .s {
  font-size: .85rem; color: var(--ink-soft); line-height: 1.55; margin-top: 2px;
}

/* ------------------------------------------------------------
   The people

   Deliberately no meter and no number. A closeness is a name and a
   sentence; anything that looked like a bar would be a score.
   ------------------------------------------------------------ */
.person {
  border-left: 3px solid var(--line);
  padding: 0 0 0 14px; margin: 0 0 18px;
}
.person:last-child { margin-bottom: 0; }
.person .pn { font-weight: 800; font-size: 1.02rem; }
.person .pr {
  font-size: .74rem; font-weight: 700; text-transform: uppercase;
  letter-spacing: .05em; color: var(--ink-faint); margin-top: 2px;
}
.person .pb {
  font-size: .9rem; color: var(--ink-soft); line-height: 1.55; margin-top: 5px;
}
.person .pw {
  font-size: .86rem; color: var(--ink-soft); line-height: 1.55; margin-top: 6px;
  padding-left: 10px; border-left: 2px solid var(--amber-strong);
}
.person.close { border-left-color: var(--money); }
.person.fading { border-left-color: var(--amber-strong); }
.person.gone { border-left-color: var(--danger); }
.person.gone .pn { color: var(--ink-faint); }

/* ============================================================
   THE LIFE TAB: A RAIL THAT IS ALSO THE BUDGET

   Seven standing choices lived here as seven stacked panels,
   about five times the text of any other tab, and a player had
   to scroll past four thousand characters to find the one they
   wanted. Reported as jumbled, and it was.

   Collapsing them into seven dropdowns is the phone answer. This
   is built for a school laptop, so it spends the horizontal room
   the page already had and hides nothing: every current setting
   stays on screen while exactly one set of options is open.

   WHAT MAKES IT THIS PRODUCT'S LAYOUT RATHER THAN A SETTINGS
   PANE. The rail carries what each choice COSTS, and totals it at
   the bottom. So the navigation is simultaneously the standing
   cost breakdown, which is the argument the whole module is
   making: the four or five things you rarely think about are your
   budget. A student looking for the housing options cannot help
   reading what housing costs on the way there.
   ============================================================ */
.life-shell { display: grid; grid-template-columns: minmax(230px, 300px) 1fr; gap: 18px; align-items: start; }

.life-rail { display: flex; flex-direction: column; gap: 2px; position: sticky; top: 12px; }

.life-rail-item {
  display: grid; gap: 1px; width: 100%; text-align: left; cursor: pointer;
  padding: 9px 11px; border: 1px solid transparent; border-radius: var(--radius-sm);
  background: transparent; font: inherit; color: inherit;
  border-left: 3px solid transparent;
}
.life-rail-item:hover { background: var(--surface-2); border-color: var(--line); }
.life-rail-item[aria-selected="true"] {
  background: var(--surface); border-color: var(--line);
  border-left-color: var(--primary);
}
.life-rail-item .rail-k { font-size: .72rem; letter-spacing: .04em; text-transform: uppercase; color: var(--ink-faint); }
.life-rail-item .rail-v { font-size: .88rem; color: var(--ink); line-height: 1.35; }
.life-rail-item .rail-m { font-size: .76rem; color: var(--ink-soft); font-variant-numeric: tabular-nums; }

/* Something changed here this turn, or something new is on offer. The rail
   is the only place a player can see that without opening all seven. */
.life-rail-item .rail-flag {
  justify-self: start; margin-top: 3px; font-size: .68rem; letter-spacing: .05em;
  text-transform: uppercase; padding: 1px 6px; border-radius: 999px;
  background: var(--primary-100); color: var(--primary-600);
}

.life-rail-total {
  margin-top: 10px; padding: 10px 11px; border-top: 1px solid var(--line);
  display: grid; gap: 2px;
}
.life-rail-total .t { font-size: .74rem; color: var(--ink-soft); line-height: 1.4; }
.life-rail-total .v { font-size: 1.02rem; color: var(--ink); font-variant-numeric: tabular-nums; }

.life-detail { min-width: 0; }
.life-detail .choice-block { border: none; padding: 0; background: transparent; }
.life-detail .choice-block .k { font-size: 1rem; margin-bottom: 2px; }

/* Narrow windows and the split-screen a Chromebook actually gets used in.
   The rail stops being a column and becomes a row of chips above the
   options, which keeps every current setting visible without a sidebar. */
@media (max-width: 860px) {
  .life-shell { grid-template-columns: 1fr; gap: 12px; }
  .life-rail { position: static; flex-direction: row; flex-wrap: wrap; gap: 6px; }
  .life-rail-item { width: auto; border-left-width: 1px; border-color: var(--line); }
  .life-rail-item[aria-selected="true"] { border-color: var(--primary); background: var(--primary-100); }
  .life-rail-item .rail-m { display: none; }
  .life-rail-total { width: 100%; margin-top: 4px; }
}
/* ============================================================
   SEVENTY YEARS, ONE FACT AT A TIME

   The end screen is a wall of worthwhile things, and a student
   who has just finished a life wants to know what happened before
   they want a report. This goes in front of it: single facts,
   stepped through, on a card big enough that the number fills it.

   Built for a school laptop, so the card is sized for a number
   read across a room rather than for a thumb.

   DELIBERATELY NOT A SCOREBOARD. No ranks, no percentiles, no
   badges. The end screen spends a whole section refusing to tell
   a student they beat 68% of players and this must not put that
   back through the side door, so the type is large and the
   claims are small.
   ============================================================ */
.wrapped { max-width: 720px; }

.wrapped-card {
  border: 1px solid var(--line);
  border-radius: var(--radius);
  background: var(--surface);
  padding: 40px 36px;
  min-height: 250px;
  display: flex; flex-direction: column; justify-content: center;
}
.wr-eyebrow {
  font-size: .72rem; font-weight: 800; text-transform: uppercase;
  letter-spacing: .08em; color: var(--ink-faint); margin-bottom: 14px;
}
.wr-big {
  font-size: 2.9rem; font-weight: 800; line-height: 1.08;
  letter-spacing: -.025em; color: var(--ink);
  font-variant-numeric: tabular-nums; margin-bottom: 14px;
}
.wr-line { font-size: 1rem; line-height: 1.6; color: var(--ink-soft); }

/* One short movement per card, so stepping forward feels like stepping
   forward. Anything longer would be a transition to sit through. */
@keyframes wrapped-in {
  from { opacity: 0; transform: translateY(8px); }
  to   { opacity: 1; transform: none; }
}
.wrapped-card.is-in { animation: wrapped-in .32s cubic-bezier(.2,.7,.3,1) both; }

.wrapped-foot {
  display: flex; align-items: center; justify-content: space-between;
  gap: 16px; margin-top: 14px; flex-wrap: wrap;
}
.wrapped-dots { display: flex; gap: 6px; }
.wrapped-dots i {
  width: 7px; height: 7px; border-radius: 999px;
  background: var(--line); display: block;
}
.wrapped-dots i.on { background: var(--primary); }
.wrapped-nav { display: flex; gap: 8px; }

@media (max-width: 700px) {
  .wrapped-card { padding: 28px 22px; min-height: 0; }
  .wr-big { font-size: 2.1rem; }
}
@media (prefers-reduced-motion: reduce) {
  .wrapped-card.is-in { animation: none; }
}
/* A heading present for the document outline and for a screen reader, and
   not drawn. The wrapped section needs one so the landmark is labeled;
   showing it would put a title above a card whose point is that the
   number is the first thing you read. */
.sr-only {
  position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px;
  overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; border: 0;
}
/* Which dollars the money screen is printing in. A pair of buttons rather
   than a switch, because a switch has an implied "off" and neither of these
   is the wrong one: today's money and the money of the year are both true
   and they teach different halves of the same fact. */
.money-view { margin-bottom: 14px; }
.money-view-btns { display: inline-flex; gap: 0; border: 1px solid var(--line); border-radius: var(--radius-sm); overflow: hidden; }
.money-view-btns button { border: none; border-radius: 0; margin: 0; padding: 7px 14px; font-size: .82rem; background: var(--surface); }
.money-view-btns button + button { border-left: 1px solid var(--line); }
.money-view-btns button[aria-pressed="true"] { background: var(--primary-100); color: var(--primary-600); font-weight: 700; }
/* The same year told twice, side by side. The left column is what a price
   tag would have said at the time and the right is what it was worth in
   money the reader understands, and the point of putting them next to each
   other is that the last row is identical in shape to the first: everything
   multiplied by the same number, so nothing actually changed. */
.money-compare { width: 100%; border-collapse: collapse; margin-top: 12px; font-variant-numeric: tabular-nums; }
.money-compare caption { caption-side: bottom; text-align: left; font-size: .74rem; color: var(--ink-faint); padding-top: 8px; line-height: 1.5; }
.money-compare th, .money-compare td { padding: 7px 10px; border-bottom: 1px solid var(--line); text-align: right; font-size: .86rem; }
.money-compare thead th { font-size: .7rem; text-transform: uppercase; letter-spacing: .05em; color: var(--ink-faint); font-weight: 800; }
.money-compare th[scope="row"], .money-compare thead th:first-child { text-align: left; font-weight: 600; color: var(--ink-soft); }
.money-compare tbody th[scope="row"] { color: var(--ink); }
.money-compare .mc-point td, .money-compare .mc-point th { border-top: 2px solid var(--line); border-bottom: none; font-weight: 800; color: var(--ink); }
/* ============================================================
   PAPER OBJECTS

   The things a life accumulates, drawn as cut paper.

   Built 2026-08-03 alongside the almanac retheme, on the note that
   if the course is going to be paper then the objects in it should
   be paper too: flat shapes, no gradients, no perspective, a hard
   offset shadow so each one reads as a cutout standing slightly off
   the page. Layered construction paper rather than illustration.

   ALL CSS AND ONE ELEMENT. No images, no SVG files, no build step.
   Each object is a single <i class="po po-house"> and everything is
   drawn with ::before, ::after and a few layered backgrounds. That
   keeps them themeable, since they read from the same tokens as the
   rest of the page and so change with light and dark on their own,
   and it keeps them free: nothing to download, nothing to cache,
   nothing that can 404.

   They are decoration. Every one is aria-hidden at the markup, and
   nothing in the simulation ever depends on a player seeing one.
   ============================================================ */

.po {
  --po-paper: var(--surface);
  --po-edge: var(--ink);
  display: inline-block;
  position: relative;
  width: 84px;
  height: 68px;
  vertical-align: middle;
  /* The tilt is what makes it a cutout rather than an icon. Small and
     alternating, so a row of them looks placed by hand. */
  transform: rotate(-2deg);
  transition: transform .18s cubic-bezier(.2,.7,.3,1);
}
.po:nth-child(even) { transform: rotate(1.6deg); }
.po:hover { transform: rotate(0deg) translateY(-2px); }

/* Position and content only. The shadow is applied by hand to the pieces
   that are meant to be standing off the page, because giving it to every
   layer made a window look like it was hovering in front of a wall. */
.po::before,
.po::after,
.po > i { position: absolute; content: ""; }

/* ---- PAINT ORDER, WHICH IS NOT WHAT IT LOOKS LIKE ----

   ::before is generated as the first child and ::after as the LAST, so with
   auto z-index the order is ::before, real children, ::after. Every detail
   element was therefore painting UNDERNEATH the piece it was meant to sit
   on: the notes covered their own denomination panel, and the car's cabin
   covered its glass and headlight.

   Details sit above by default and opt back down where a piece is really
   meant to be behind, like a chimney disappearing under a roof. */
.po > i { z-index: 2; }

/* The hard offset shadow. Paper standing off paper, no blur, because a
   blurred shadow is a lighting effect and these are cut shapes. */
.po-drop { box-shadow: 2px 3px 0 rgba(34,28,22,.18); }
:root[data-theme="dark"] .po-drop { box-shadow: 2px 3px 0 rgba(0,0,0,.5); }

/* ---- HOW MUCH DETAIL, AND WHY ----

   The first pass was four shapes that were all a rounded rectangle with
   something on top, so they read as one object in four colors rather than
   as four things. Distinctiveness in a cutout comes from SILHOUETTE first
   and internal detail second, so each of these now has an outline you could
   identify with the color removed, and enough interior parts to be worth
   looking at twice.

   Detail is cut paper too. Nothing is a gradient, nothing is soft, and every
   interior piece is either its own scrap with an outline or a flat block of
   color laid on one. That is the rule that keeps a window from looking like
   a screen and a wheel from looking like a button. */

/* ---- HOUSE: body, pitched roof, chimney, panelled window, door ---- */
.po-house::before {          /* the body */
  left: 12%; bottom: 0; width: 76%; height: 58%;
  background: var(--po-paper);
  border: 2px solid var(--po-edge);
  border-radius: 2px;
  box-shadow: 2px 3px 0 rgba(34,28,22,.18);
}
.po-house::after {           /* the roof, cut slightly off square so it reads as hand cut */
  left: 2%; top: 2%; width: 96%; height: 46%;
  background: var(--po-roof, var(--primary));
  clip-path: polygon(50% 0, 100% 92%, 97% 100%, 3% 100%, 0 92%);
}

/* ---- EVERY PLACE HAS ITS OWN ROOF ----

   REPORTED 2026-08-05: "can the roof color for the house models be randomly
   generated each time you move into a new place? Offers a change even if you
   upgrade from an apartment to a rented house."

   That last clause is the argument. Moving is one of the largest decisions in
   the model and the drawing barely acknowledged it: the same blue roof
   followed a life from a shared room at nineteen to a house it owned at
   fifty, so the one object that should say "this is somewhere else" said
   "this is the same place, bigger".

   The same mechanism the cars use, and for the same reason: paint is a class
   on the object, the colors are real colors rather than tokens, and nothing
   in the simulation is ever colored BY them. Muted for the light theme and
   lifted for the dark one, because a dark roof on a dark page is a hole. */
.po-roof-slate   { --po-roof: #4a5a6b; }
.po-roof-clay    { --po-roof: #a8583f; }
.po-roof-moss    { --po-roof: #5f7355; }
.po-roof-charcoal{ --po-roof: #45423e; }
.po-roof-rust    { --po-roof: #8c5230; }
.po-roof-blue    { --po-roof: #3b6ea5; }
.po-roof-sand    { --po-roof: #b09468; }
.po-roof-plum    { --po-roof: #6b4a5c; }

:root[data-theme="dark"] .po-roof-slate    { --po-roof: #6d8095; }
:root[data-theme="dark"] .po-roof-clay     { --po-roof: #c8735a; }
:root[data-theme="dark"] .po-roof-moss     { --po-roof: #85a077; }
:root[data-theme="dark"] .po-roof-charcoal { --po-roof: #6a655f; }
:root[data-theme="dark"] .po-roof-rust     { --po-roof: #b06f47; }
:root[data-theme="dark"] .po-roof-blue     { --po-roof: #6a9fd4; }
:root[data-theme="dark"] .po-roof-sand     { --po-roof: #cdb185; }
:root[data-theme="dark"] .po-roof-plum     { --po-roof: #946980; }
/* ---- THE ROOF HAD NO EDGE ----

   The stated rule for this whole system is that every interior piece is its
   own scrap with an outline. Every other shape here obeys it: the wall, the
   door, both windows, the chimney, the car body, the cabin, the glass. The
   roof did not, because a clip-path cannot take a border, so it was the one
   large shape on the object sitting as a bare block of color. Next to five
   outlined pieces it reads as unfinished, which is most of what "the house
   needs polish" was pointing at.

   Two stacked shapes: this one is the same polygon two percent larger in
   ink, and it is a child, so it paints BEFORE ::after and the extra shows
   all the way round as an outline. Cheaper than redrawing the roof as five
   bordered pieces and it holds at every size, since both are percentages of
   the same box. */
.po-house > i:nth-of-type(9) {
  left: 0; top: 0; width: 100%; height: 50%;
  background: var(--po-edge);
  clip-path: polygon(50% 0, 100% 92%, 97% 100%, 3% 100%, 0 92%);
  border: 0;
  z-index: 0;
}
/* ---- THE CHIMNEY HAS TO REACH THE ROOF ON BOTH EDGES ----

   REPORTED 2026-08-04: "some chimneys don't fully connect to the houses so
   they look strange."

   It is a box on a slope. The roof falls away to the right, so the surface
   under the chimney's downhill edge is lower than under its uphill edge,
   and the box has to be tall enough to meet the LOWER of the two. Measured
   at 128x104: the roof sits at y 20.5 under the left edge and y 32.3 under
   the right, and the chimney stopped at y 31.8. Buried by 11% on one side
   and floating by half a percent on the other, which is the 1px notch that
   was being seen. It gets worse as the object gets bigger, because the gap
   is a fraction of the height.

   42% now, so the downhill edge is a good 10% into the roof. Everything
   below the roofline is covered by it anyway: the roof is ::after and
   paints last, so there is no cost to burying more. */
.po-house > i:nth-of-type(1) {   /* chimney, which the roof cuts across */
  right: 16%; top: 0; width: 13%; height: 42%;
  background: var(--surface-2);
  border: 2px solid var(--po-edge);
  border-radius: 1px 1px 0 0;
  /* Below the roof, which is ::after and paints last. z-index: -1 would put
     it behind the page itself, since .po sets no background of its own. */
  z-index: 0;
}
/* A cap, overhanging both sides. Without it the chimney is a plain tab of
   paper with a straight top, which is the one shape on the house that reads
   as unfinished rather than as cut. */
.po-house > i:nth-of-type(1)::after {
  content: "";
  position: absolute;
  left: -22%; right: -22%; top: -2px; height: 18%;
  background: var(--surface-2);
  border: 2px solid var(--po-edge);
  border-radius: 1px;
}
/* ---- THE FACADE HAS TWO FLOORS ----

   REPORTED 2026-08-03 as elements being misaligned and the house looking
   broken. Measured, there were two faults and they compounded. The second
   window, commented "upstairs, under the roof", sat at y 54 to 76% against
   a first window at 52 to 76%: the same floor, 0.9px apart. And it ran
   x 50 to 70% across a door at 56 to 76%, so it was drawn straight through
   the door, overlapping it by 18px across and 12px down.

   Laid out as a facade now. Downstairs is a window and a door, upstairs is
   two smaller windows tucked under the roof, and nothing overlaps anything
   it is not meant to. */
.po-house > i:nth-of-type(2) {   /* ground floor window, mullion drawn by its own borders */
  left: 20%; bottom: 6%; width: 22%; height: 21%;
  background: var(--amber);
  border: 2px solid var(--po-edge);
  border-radius: 1px;
  background-image:
    linear-gradient(var(--po-edge), var(--po-edge)),
    linear-gradient(var(--po-edge), var(--po-edge));
  background-size: 100% 2px, 2px 100%;
  background-position: center, center;
  background-repeat: no-repeat;
}
.po-house > i:nth-of-type(3) {   /* door, with a handle */
  right: 24%; bottom: 0; width: 20%; height: 34%;
  background: var(--surface-2);
  border: 2px solid var(--po-edge);
  border-radius: 2px 2px 0 0;
  box-shadow: inset -5px 0 0 -3px var(--po-edge);
}

:root[data-theme="dark"] .po-house::before { box-shadow: 2px 3px 0 rgba(0,0,0,.5); }

/* ---- CAR: a proper profile, with a bonnet, glass, wheels and a light ---- */
.po-car::before {            /* the body, longer at the front than the back */
  left: 0; bottom: 16%; width: 100%; height: 32%;
  background: var(--primary);
  border: 2px solid var(--po-edge);
  border-radius: 5px 10px 4px 4px;
  box-shadow: 2px 3px 0 rgba(34,28,22,.18);
}
/* The cabin is paper, not body color. Painted in the same blue the whole
   car read as one blob, and the point of a cutout is that you can name the
   parts. Two tones is what makes it a vehicle rather than a shape. */
/* ---- WHICH WAY THE CAR FACES ----

   REPORTED 2026-08-03 as elements being misaligned and the car looking
   broken. Measured, this was the reason: the headlight sits at x 91 to 99%
   and the bumper at 86 to 100%, so the front is on the right, and the cabin
   and the glass both cut their TOP LEFT corner, so the windscreen raked
   toward the back. The car had a windscreen at one end and headlights at
   the other, which the eye reads as wrong long before it can say why.

   The bonnet is the long end: the cabin runs 20 to 64%, leaving 36% of
   length ahead of it and 20% behind. So the front is the right, and the
   rake belongs on the right. */
/* ---- THE RAKE IS A RADIUS, NOT A CLIP ----

   REPORTED 2026-08-04: "some assets are missing part of their black
   outlines. Is this stylistic, or an accident?" An accident, and this was
   the clearest case of it.

   A clip-path clips the BORDER along with everything else. This rule asked
   for a 2px ink border and then cut a diagonal through it, so the browser
   drew the outline and immediately removed it along the one edge the
   polygon invented. The car had a complete outline everywhere except the
   windscreen, which is the edge the eye goes to first.

   An elliptical corner radius gives the same swept front and the border
   follows it all the way round, because nothing is being cut. It is also
   closer to a real car than the straight diagonal was. The roof does need
   the two-layer trick, because a pitched roof is a shape no radius can
   make, but a windscreen is not. */
.po-car::after {             /* the cabin, swept at the front like a windscreen */
  left: 20%; bottom: 44%; width: 44%; height: 28%;
  background: var(--po-paper);
  border: 2px solid var(--po-edge);
  border-radius: 5px 58% 0 0 / 5px 96% 0 0;
}
.po-car > i:nth-of-type(1),
.po-car > i:nth-of-type(2) { /* wheels, as rings with a hub */
  bottom: 2%; width: 22%; height: 0; padding-bottom: 22%;
  background: var(--po-paper);
  border: 2px solid var(--po-edge);
  border-radius: 50%;
  box-shadow: 2px 2px 0 rgba(34,28,22,.18),
              inset 0 0 0 3px var(--po-paper),
              inset 0 0 0 6px var(--po-edge);
}
.po-car > i:nth-of-type(1) { left: 13%; }
.po-car > i:nth-of-type(2) { right: 13%; }
.po-car > i:nth-of-type(3) {  /* the glass, sitting inside the paper cabin */
  left: 25%; bottom: 49%; width: 34%; height: 18%;
  background: var(--primary-100);
  border: 2px solid var(--po-edge);
  /* Swept the same way the cabin is, and by the same means. It used to be
     clipped, which took its outline off the raked edge, and before that it
     raked the other way, which is how a windscreen ended up sloping into
     the roof. */
  border-radius: 3px 54% 0 0 / 3px 94% 0 0;
}
.po-car > i:nth-of-type(4) {  /* headlight */
  right: 1%; bottom: 34%; width: 8%; height: 10%;
  background: var(--amber);
  border: 2px solid var(--po-edge);
  border-radius: 2px;
}

:root[data-theme="dark"] .po-car::before { box-shadow: 2px 3px 0 rgba(0,0,0,.5); }
:root[data-theme="dark"] .po-car > i:nth-of-type(1),
:root[data-theme="dark"] .po-car > i:nth-of-type(2) {
  box-shadow: 2px 2px 0 rgba(0,0,0,.5),
              inset 0 0 0 3px var(--po-paper),
              inset 0 0 0 6px var(--po-edge);
}

/* ---- TRANSIT: a pass, punched, with a stripe and a route printed on it ---- */
/* ---- TRANSIT: A BUS ----

   This used to be a travel card: a notched rectangle with a magnetic
   stripe, a punch hole and two printed lines. It was a decent drawing of a
   ticket and the wrong object, for a reason that only shows up in place.
   The vehicle slot sits on the same ground line as a house and the people
   in your life, and a ticket at that scale is a scale error: a thing you
   hold, standing where a thing you travel in should be. Next to a house it
   read as a stray receipt.

   REQUESTED 2026-08-04: "'public transport only' should have a little CSS
   bus and/or bicycle, or maybe even just a bus ticket." The ticket already
   existed and was not landing, which is the answer to which of the three to
   build. A bus also parallels the car it replaces, so the two states of
   that slot are the same kind of thing.

   Same construction as the car, so they sit together: paper body, ink
   outlines, wheels as rings, and the front on the right. */
.po-transit::before {        /* the body, tall and square-shouldered */
  left: 1%; bottom: 16%; width: 98%; height: 58%;
  background: var(--po-body, var(--primary));
  border: 2px solid var(--po-edge);
  border-radius: 7px 9px 3px 3px;
  box-shadow: 2px 3px 0 rgba(34,28,22,.18);
}
/* The door is ::after so it paints last and stays crisp over the body.
   Between the wheels, which is where a bus's center door is and, more
   practically, the one span of the side that nothing else is using. */
.po-transit::after {
  left: 41%; bottom: 18%; width: 17%; height: 44%;
  background: var(--po-paper);
  border: 2px solid var(--po-edge);
  border-radius: 2px 2px 1px 1px;
  /* The split down the middle, drawn by the background rather than by
     another element, the same way the house windows draw their mullion. */
  background-image: linear-gradient(var(--po-edge), var(--po-edge));
  background-size: 2px 100%;
  background-position: center;
  background-repeat: no-repeat;
}
.po-transit > i:nth-of-type(1),
.po-transit > i:nth-of-type(2) {  /* wheels, as on the car */
  bottom: 2%; width: 20%; height: 0; padding-bottom: 20%;
  background: var(--po-paper);
  border: 2px solid var(--po-edge);
  border-radius: 50%;
  box-shadow: 2px 2px 0 rgba(34,28,22,.18),
              inset 0 0 0 3px var(--po-paper),
              inset 0 0 0 5px var(--po-edge);
}
.po-transit > i:nth-of-type(1) { left: 13%; }
.po-transit > i:nth-of-type(2) { right: 13%; }
/* Two window bands, one either side of the door. A single band would have
   to run behind the door, and a bus is the one vehicle here whose windows
   are the thing you actually recognize it by. */
.po-transit > i:nth-of-type(3),
.po-transit > i:nth-of-type(4) {
  bottom: 44%; height: 22%;
  background: var(--primary-100);
  border: 2px solid var(--po-edge);
  border-radius: 2px;
  /* Pillars between the panes, printed the same way the door's split is. */
  background-image:
    linear-gradient(var(--po-edge), var(--po-edge)),
    linear-gradient(var(--po-edge), var(--po-edge));
  background-size: 2px 100%, 2px 100%;
  background-position: 33% 0, 67% 0;
  background-repeat: no-repeat;
}
.po-transit > i:nth-of-type(3) { left: 6%; width: 32%; }
.po-transit > i:nth-of-type(4) { left: 62%; width: 32%; }
.po-transit > i:nth-of-type(5) {  /* the route blind, over the windscreen */
  left: 64%; bottom: 67%; width: 28%; height: 6%;
  background: var(--amber);
  border: 2px solid var(--po-edge);
  border-radius: 1px;
}
.po-transit > i:nth-of-type(6) {  /* headlight */
  right: 3%; bottom: 21%; width: 7%; height: 8%;
  background: var(--amber);
  border: 2px solid var(--po-edge);
  border-radius: 2px;
}
:root[data-theme="dark"] .po-transit > i:nth-of-type(1),
:root[data-theme="dark"] .po-transit > i:nth-of-type(2) {
  box-shadow: 2px 2px 0 rgba(0,0,0,.5),
              inset 0 0 0 3px var(--po-paper),
              inset 0 0 0 5px var(--po-edge);
}

:root[data-theme="dark"] .po-transit::before { box-shadow: 2px 3px 0 rgba(0,0,0,.5); }

/* ---- MONEY: three notes fanned, banded, with a coin resting on top ---- */
.po-money::before {          /* the note at the back */
  left: 4%; top: 30%; width: 74%; height: 40%;
  background: var(--money-100);
  border: 2px solid var(--po-edge);
  border-radius: 2px;
  transform: rotate(-7deg);
  box-shadow: 2px 3px 0 rgba(34,28,22,.14);
}
.po-money::after {           /* the note in front */
  left: 12%; top: 24%; width: 74%; height: 40%;
  background: var(--po-paper);
  border: 2px solid var(--po-edge);
  border-radius: 2px;
  box-shadow: 2px 3px 0 rgba(34,28,22,.18);
}
.po-money > i:nth-of-type(1) {  /* the denomination panel on the front note */
  left: 20%; top: 32%; width: 26%; height: 22%;
  background: var(--money);
  border-radius: 1px;
}
.po-money > i:nth-of-type(2) {  /* a printed line beside it */
  left: 52%; top: 39%; width: 26%; height: 6%;
  background: var(--ink-faint);
  border-radius: 1px;
}
.po-money > i:nth-of-type(3) {  /* the coin, overlapping the corner */
  right: 2%; bottom: 8%; width: 30%; height: 0; padding-bottom: 30%;
  background: var(--amber);
  border: 2px solid var(--po-edge);
  border-radius: 50%;
  box-shadow: 2px 2px 0 rgba(34,28,22,.18),
              inset 0 0 0 3px var(--amber),
              inset 0 0 0 5px var(--po-edge);
}

:root[data-theme="dark"] .po-money::before { box-shadow: 2px 3px 0 rgba(0,0,0,.4); }
:root[data-theme="dark"] .po-money::after { box-shadow: 2px 3px 0 rgba(0,0,0,.5); }
:root[data-theme="dark"] .po-money > i:nth-of-type(3) {
  box-shadow: 2px 2px 0 rgba(0,0,0,.5),
              inset 0 0 0 3px var(--amber),
              inset 0 0 0 5px var(--po-edge);
}

/* Sizes, because the same object serves a summary strip and a page
   heading. */
.po.sm { width: 46px; height: 38px; }
.po.lg { width: 128px; height: 104px; }

@media (prefers-reduced-motion: reduce) {
  .po, .po:hover { transition: none; }
}

/* ============================================================
   FURNITURE, redrawn for the almanac
   2026-08-03

   The tokens changed underneath these, so nothing looked broken,
   but nothing had been redrawn either: the shapes were still the
   ones the old design language wanted. A translucent blurred header
   bar, cards that lift and float on hover, pill-shaped status
   badges and buttons that rise off the page when you point at them
   are all the same idiom, and it is not this one.

   Printed things do not levitate. What replaces the lift is weight:
   a rule that thickens, an edge that darkens, a background that
   fills. Movement is kept to a couple of pixels and only where it
   helps you feel a control respond.
   ============================================================ */

/* ---- the header is a masthead ----
   Solid rather than a translucent blur. The blur was a phone idiom
   borrowed for a desktop page, it cost a compositing layer on every
   scroll, and on warm paper it turned the whole bar slightly gray. A
   masthead sits on a double rule, the way a printed one does. */
.site-header {
  background: var(--bg);
  backdrop-filter: none;
  border-bottom: 1px solid var(--line);
  box-shadow: 0 2px 0 var(--line-soft);
}

/* The mark stops being a rounded app tile. A square with a serif letter and
   a hairline reads as a printer's colophon, which is the right ancestor. */
.brand { letter-spacing: 0; }
.brand .mark, .brand-mark {
  border-radius: 2px;
  font-family: var(--font-display);
  font-weight: 700;
  box-shadow: 2px 2px 0 var(--line);
}
:root[data-theme="dark"] .brand .mark,
:root[data-theme="dark"] .brand-mark { box-shadow: 2px 2px 0 var(--surface-2); }

/* ---- buttons press instead of lifting ----
   The primary button had `transition: all` and rose on hover with a shadow
   underneath it. A printed control does the opposite: it darkens, and when
   you push it, it moves down. */
.btn-primary {
  border-radius: var(--radius-sm);
  box-shadow: 2px 2px 0 var(--primary-600);
  transition: background-color .14s, box-shadow .1s, transform .1s;
}
.btn-primary:hover {
  background: var(--primary-600);
  transform: none;
  box-shadow: 2px 2px 0 var(--link-hover);
}
.btn-primary:active {
  transform: translate(2px, 2px);
  box-shadow: 0 0 0 var(--link-hover);
}

.btn-quiet {
  border-radius: var(--radius-sm);
  transition: border-color .14s, color .14s, background-color .14s;
}
.btn-quiet:hover { background: var(--surface-2); border-color: var(--ink-faint); }
.btn-quiet[aria-pressed="true"] {
  background: var(--ink); color: var(--surface); border-color: var(--ink);
}

/* ---- cards sit on the page rather than above it ---- */
.course-card {
  border-radius: var(--radius);
  box-shadow: none;
  transition: border-color .14s, background-color .14s;
}
a.course-card:hover {
  border-color: var(--ink);
  transform: none;
  box-shadow: none;
  background: var(--surface-2);
}
/* The number becomes the almanac's marginal figure: set in the display face,
   large and quiet, the way a chapter number sits in a printed book. */
.course-card .n {
  font-family: var(--font-display);
  font-size: 1.05rem;
  font-weight: 700;
  letter-spacing: 0;
  text-transform: none;
  color: var(--primary);
}
.course-card h3 { font-size: 1.18rem; }

/* Status badges lose the pill. 999px radius is the single most app-like
   shape in the stylesheet, and a printed label is a rectangle. */
.course-card .status {
  border-radius: 2px;
  letter-spacing: .07em;
  padding: 3px 7px;
}

/* ---- panels get a rule at the top rather than a shadow ---- */
.panel, .scenario, .reflection-box {
  box-shadow: none;
}

/* ---- tables read as a ledger ----
   Which they already nearly did. Tabular figures everywhere they are
   numbers, so columns line up the way a printed table does, and a heavier
   rule under the head. */
.vt th { border-bottom: 2px solid var(--ink); letter-spacing: .07em; }
.vt td.num, .budget-line .amt, .cmp-scale, .stat .big {
  font-variant-numeric: tabular-nums;
}

/* ---- the sim's own furniture ----
   Kept light for now. The simulation is getting its own treatment as a
   separate piece of work, and anything done here would be redone there. */
.sim-card { border-radius: var(--radius); }
.milestone.big { border-left-width: 5px; }

/* ---- THE CAP ON THE GRADUATION DIALOG ----
   Set in the corner rather than above the heading, because the card is
   left-aligned and a centered drawing over left-aligned text reads as a
   mistake. The heading and the note reserve room for it so the two never
   share a column, and below 420px it is dropped: at that width the space it
   would take is the space the decision needs. */
.grad-card { position: relative; }
/* Two classes deep on purpose. `.po` sets its own width and height, and the
   phone block that resets them sits further down the file, so a single-class
   rule here loses to it on exactly the screens where the fit is tightest.
   Same trap the keepsake sizes fell into. */
.grad-card .grad-mark {
  position: absolute; right: 16px; top: 14px;
  width: 54px; height: 54px;
}
.grad-card .kind, .grad-card h3, .grad-card .note-small { padding-right: 80px; }
@media (max-width: 420px) {
  .grad-mark { display: none; }
  .grad-card .kind, .grad-card h3, .grad-card .note-small { padding-right: 0; }
}

/* The last pill. Squared for the same reason the status badges were: a
   999px radius is the most recognizable app shape in the stylesheet, and a
   printed standfirst is set on a rule, not in a capsule. */
.eyebrow {
  border-radius: 2px;
  border-left: 3px solid var(--primary);
}

/* Pressed state, walked back for standalone toggles.

   Inverting to solid ink is right for a segmented control, where two buttons
   sit together and the whole job of the state is to say which of them you
   are in. Applied to every .btn-quiet it also caught single toggles like
   "Sound on", which then read as the heaviest element on the screen for a
   preference nobody is deciding about. */
.btn-quiet[aria-pressed="true"] {
  background: var(--surface-2);
  color: var(--ink);
  border-color: var(--ink-faint);
}
/* The segmented ones keep the strong version, because there the state is
   the point. */
.money-view-btns .btn-quiet[aria-pressed="true"] {
  background: var(--ink);
  color: var(--surface);
  border-color: var(--ink);
}

/* ============================================================
   CAR PAINT

   flavor.js already gives every car a color: "a sporty blue 2026
   Smonda Mimic". The record said blue and the picture was brand
   blue whatever the sentence claimed, which is the kind of small
   disagreement that makes everything around it feel generated.

   One class per color in the flavor list, setting --po-body. The
   car reads --po-body and falls back to the brand color, so an
   object with no color attached still draws.

   These are paints, not tokens: they carry no meaning, nothing in
   the simulation is ever colored BY them, and they are decoration
   on an aria-hidden element. That is why they can be actual colors
   rather than another set of semantic slots. Muted for the light
   theme and lifted for the dark one, because a black car on a dark
   page is a hole and a white car on paper is an outline.
   ============================================================ */
.po-paint-red        { --po-body: #b23a35; }
.po-paint-blue       { --po-body: #3b6ea5; }
.po-paint-silver     { --po-body: #b9bcc0; }
.po-paint-white      { --po-body: #f2efe8; }
.po-paint-green      { --po-body: #4a7c59; }
.po-paint-gray       { --po-body: #8a8d92; }
.po-paint-black      { --po-body: #33312e; }
.po-paint-dark-blue  { --po-body: #2b4a6f; }
.po-paint-burgundy   { --po-body: #7d2f3f; }
.po-paint-sand       { --po-body: #c9a86a; }
.po-paint-gunmetal   { --po-body: #565c63; }
.po-paint-pale-green { --po-body: #9cbb9a; }

/* ---- THE LUXURY PAINTS ----
   Added with the luxury register on 2026-08-05, and they had to be: the
   drawing takes its color from this list and falls through to the brand blue
   for anything missing, so ten new paint names shipped without ten new
   entries meant every luxury car in the simulation was drawn identically in
   blue while the card underneath called it oxblood. */
.po-paint-oxblood     { --po-body: #6e2b2f; }
.po-paint-champagne   { --po-body: #d9c9a3; }
.po-paint-obsidian    { --po-body: #2c2b30; }
.po-paint-ivory       { --po-body: #efe9da; }
.po-paint-cobalt      { --po-body: #2f5fa8; }
.po-paint-bronze      { --po-body: #9a6b3f; }
.po-paint-moss-green  { --po-body: #5c7150; }
.po-paint-pearl-white { --po-body: #f4f1ea; }
.po-paint-graphite    { --po-body: #4a4d52; }
.po-paint-storm-blue  { --po-body: #46607a; }

:root[data-theme="dark"] .po-paint-red        { --po-body: #d2564f; }
:root[data-theme="dark"] .po-paint-blue       { --po-body: #6a9fd4; }
:root[data-theme="dark"] .po-paint-silver     { --po-body: #cfd2d6; }
:root[data-theme="dark"] .po-paint-white      { --po-body: #ece7dc; }
:root[data-theme="dark"] .po-paint-green      { --po-body: #6da87c; }
:root[data-theme="dark"] .po-paint-gray       { --po-body: #a8abb0; }
:root[data-theme="dark"] .po-paint-black      { --po-body: #4a4642; }
:root[data-theme="dark"] .po-paint-dark-blue  { --po-body: #5580ad; }
:root[data-theme="dark"] .po-paint-burgundy   { --po-body: #a8505f; }
:root[data-theme="dark"] .po-paint-sand       { --po-body: #d8bd85; }
:root[data-theme="dark"] .po-paint-gunmetal   { --po-body: #7c838b; }
:root[data-theme="dark"] .po-paint-pale-green { --po-body: #b3d0b1; }
:root[data-theme="dark"] .po-paint-oxblood     { --po-body: #94413f; }
:root[data-theme="dark"] .po-paint-champagne   { --po-body: #e6d8b6; }
:root[data-theme="dark"] .po-paint-obsidian    { --po-body: #46454b; }
:root[data-theme="dark"] .po-paint-ivory       { --po-body: #f0ebdd; }
:root[data-theme="dark"] .po-paint-cobalt      { --po-body: #5a8ad0; }
:root[data-theme="dark"] .po-paint-bronze      { --po-body: #c08e5c; }
:root[data-theme="dark"] .po-paint-moss-green  { --po-body: #7d9470; }
:root[data-theme="dark"] .po-paint-pearl-white { --po-body: #f6f3ec; }
:root[data-theme="dark"] .po-paint-graphite    { --po-body: #6d7178; }
:root[data-theme="dark"] .po-paint-storm-blue  { --po-body: #6b87a4; }

/* The body takes the paint. Everything else on the car stays paper and ink,
   so a silver car and a burgundy one are the same drawing in a different
   color rather than two different drawings. */
.po-car::before { background: var(--po-body, var(--primary)); }

/* ============================================================
   MORE DETAIL

   Second pass. The rule for what earns a place: it has to survive
   at the default 84px, and it has to be a piece of paper or a
   printed mark rather than shading. Anything finer than about 5% of
   the object turns to mud, so the smallest marks are dropped at .sm
   rather than drawn and lost.
   ============================================================ */

/* ---- house: ridge, a second window, a step, a garden ---- */
/* Upstairs this time: the roof's lower edge is at 52% from the bottom and
   these top out at 51%, so they sit under it with a hair of wall showing. */
.po-house > i:nth-of-type(4),
.po-house > i:nth-of-type(6) {   /* the two upstairs windows */
  bottom: 35%; width: 18%; height: 16%;
  background: var(--amber);
  border: 2px solid var(--po-edge);
  border-radius: 1px;
  background-image: linear-gradient(var(--po-edge), var(--po-edge));
  background-size: 2px 100%;
  background-position: center;
  background-repeat: no-repeat;
}
.po-house > i:nth-of-type(4) { left: 21%; }   /* over the window */
.po-house > i:nth-of-type(6) { left: 57%; }   /* over the door */
.po-house > i:nth-of-type(5) {   /* the step under the door */
  right: 20%; bottom: -3%; width: 28%; height: 7%;
  background: var(--surface-2);
  border: 2px solid var(--po-edge);
  border-radius: 1px;
}
/* A hedge in the gap between the window and the door, which was the one
   blank stretch of the ground floor. */
.po-house > i:nth-of-type(7) {
  left: 44%; bottom: 0; width: 10%; height: 13%;
  background: var(--surface-2);
  border: 2px solid var(--po-edge);
  border-radius: 6px 6px 1px 1px;
}
/* The letterbox, printed rather than cut, on the door. */
.po-house > i:nth-of-type(8) {
  right: 27%; bottom: 17%; width: 9%; height: 3%;
  background: var(--po-edge);
  border-radius: 1px;
  opacity: .65;
}

/* ---- car: mirror, handle, bumper, shut line, rear light ---- */
/* The mirror used to sit at 17 to 24%, which put it on the back corner of a
   cabin that starts at 20%, hanging into the air behind the car. A wing
   mirror belongs at the base of the windscreen, so it goes to the front of
   the cabin and is allowed to stick out past it, which is what a mirror
   does. */
.po-car > i:nth-of-type(5) {     /* wing mirror */
  left: 60%; bottom: 43%; width: 8%; height: 6%;
  background: var(--po-edge);
  border-radius: 1px 2px 2px 1px;
}
.po-car > i:nth-of-type(6) {     /* door handle, a printed dash */
  left: 38%; bottom: 36%; width: 13%; height: 4%;
  background: var(--po-edge);
  border-radius: 1px;
  opacity: .75;
}
/* Sat at 18 to 26% from the bottom against a body whose sill is at 16%, so
   it floated two percent clear of the car it is bolted to. It now runs from
   just under the sill upward, which is where a bumper is. */
.po-car > i:nth-of-type(7) {     /* bumper */
  right: 0; bottom: 14%; width: 14%; height: 9%;
  background: var(--po-paper);
  border: 2px solid var(--po-edge);
  border-radius: 2px;
}
/* ---- the two marks that make it read as a car with doors ---- */
.po-car > i:nth-of-type(9) {     /* the shut line between the doors */
  left: 36%; bottom: 18%; width: 2px; height: 26%;
  background: var(--po-edge);
  opacity: .45;
}
.po-car > i:nth-of-type(10) {    /* rear light, so the back is not blank */
  left: 1%; bottom: 33%; width: 6%; height: 9%;
  background: var(--po-edge);
  border-radius: 2px;
  opacity: .55;
}
/* ---- WHEEL ARCHES ----

   The body was a rectangle with two rings under it, and the rings simply
   disappeared behind a straight edge. An arch is the line that makes a car
   body read as a car body: the fender curving over the tire.

   Drawn as the top half of an ellipse, centered on each wheel. The wheels
   sit at x 13 to 35% and 65 to 87%, so these are two percent wider on each
   side, and they run up from the wheel's own centerline at 84.5%. They are
   the last parts, so they paint over the wheels, which is the right way
   round: the fender is in front of the tire. */
.po-car > i:nth-of-type(11),
.po-car > i:nth-of-type(12) {
  bottom: 15.5%; width: 28%; height: 17.5%;
  background: none;
  border: 2px solid var(--po-edge);
  border-bottom: 0;
  border-radius: 50% 50% 0 0 / 100% 100% 0 0;
}
.po-car > i:nth-of-type(11) { left: 10%; }
.po-car > i:nth-of-type(12) { right: 10%; }

/* The transit chevron lived here, a mark on the old travel card. It went
   with the card when that became a bus on 2026-08-04, and it had to be
   deleted rather than left: this section comes after the object's own
   rules, so it still won on order and was quietly rotating the bus's front
   window band 45 degrees. Measured as x 32 to 50 where x 62 to 94 was
   declared, which is what caught it. */

/* ---- money: a corner figure and a second band ---- */
.po-money > i:nth-of-type(4) {
  right: 32%; top: 30%; width: 9%; height: 9%;
  border: 2px solid var(--po-edge);
  border-radius: 50%;
  background: none;
}
.po-money > i:nth-of-type(5) {
  left: 52%; top: 50%; width: 18%; height: 5%;
  background: var(--ink-faint);
  border-radius: 1px;
  opacity: .7;
}

/* The finest marks come off at small sizes rather than being drawn as two
   muddy pixels. Silhouette survives, decoration does not, which is the right
   way round. */
/* Everything from the fourth scrap on, so adding a mark to an object does
   not silently start drawing mud at 46px. */
.po.sm > i:nth-of-type(n+4) { display: none; }

/* Exceptions, and every one is silhouette rather than decoration. The roof
   outline, the wheel arches and the bus's second window band are the shape
   of the thing, not a mark on it, and dropping them at 46px leaves a bare
   block of color, a pair of loose rings, and a bus with windows down one
   side only.

   THEY HAVE TO BE LISTED AFTER THE RULE ABOVE. Specificity is identical,
   two classes and a positional pseudo-class either way, so source order is
   the only thing separating them. The bus exemption was first written
   inside the object's own block, earlier in the file, and lost silently:
   measured at 46px it drew its rear windows and not its front ones. */
.po-house.sm > i:nth-of-type(9),
.po-car.sm > i:nth-of-type(11),
.po-car.sm > i:nth-of-type(12),
.po-transit.sm > i:nth-of-type(4) { display: block; }

/* ============================================================
   CONDITION

   flavor.js already writes a condition into every car: a beater
   "with a wing mirror held on with tape", a new one "with the
   plastic still on the seats". The drawing knew the color and not
   the state, so a nine year old hatchback and a car off the
   forecourt were the same picture.

   .po-worn and .po-fresh are the two ends. Worn is the interesting
   one and the reason this exists: a replaced door in the wrong
   paint is the single most recognizable mark of a cheap car, it is
   exactly what the beater tier is about, and it costs one element.
   ============================================================ */

/* ---- worn: a mismatched door, tape on the mirror, a tired stance ---- */
.po-car.po-worn > i:nth-of-type(8) {   /* the replaced door, in somebody else's color */
  left: 30%; bottom: 18%; width: 26%; height: 28%;
  background: var(--po-mismatch, #b7a98c);
  border: 2px solid var(--po-edge);
  border-radius: 2px 2px 3px 3px;
}
:root[data-theme="dark"] .po-car.po-worn > i:nth-of-type(8) {
  background: var(--po-mismatch, #9c8f74);
}
/* Tape holding the mirror on, which is a real line from the card text. */
.po-car.po-worn > i:nth-of-type(5) {
  background: var(--po-paper);
  border: 2px solid var(--po-edge);
  width: 9%; height: 9%;
  transform: rotate(-16deg);
}
/* It sits down at one corner. Two degrees is enough to read as tired
   without looking broken. */
.po-car.po-worn { transform: rotate(-2.6deg); }
.po-car.po-worn:hover { transform: rotate(-1deg) translateY(-2px); }

/* A house can be worn too: a boarded window and a patched roof. */
.po-house.po-worn > i:nth-of-type(4) {
  background: var(--surface-2);
  background-image: linear-gradient(var(--po-edge), var(--po-edge));
  background-size: 100% 2px;
  background-position: center;
  background-repeat: no-repeat;
}
.po-house.po-worn::after {
  /* a patch cut out of the roofline */
  clip-path: polygon(50% 0, 100% 92%, 97% 100%, 62% 100%, 60% 88%, 44% 88%, 42% 100%, 3% 100%, 0 92%);
}

/* ---- fresh: a shine, and the plastic still on ----

   REPORTED 2026-08-05: "instead of a dealer plate for a new car can we add a
   little sparkle or shine icon? The dealer plate doesn't land well."

   It did not. A small pale rectangle low on the body reads as a number plate
   only if you already know that is what it is, and at 84px it mostly read as
   a smudge. A shine says the same thing without needing to be identified: it
   is the mark every drawing of a new thing has used forever.

   Four-pointed, made from one element: a tall thin diamond and a wide flat
   one crossing it, which is a star at any size and needs no clip-path, so it
   keeps working where a bordered shape would not. */
/* ON THE CAR, ON THE HOOD, AND WHITE.

   REPORTED 2026-08-05, three times: "shouldn't the car sparkle be on the car
   and white/clearish?", "maybe a light sparkle animation on it", and "the
   shine should be on the hood of the car for best contrast."

   All three right, and the third is the one measurement would not have
   caught. The first version sat at top 12%, which measured out at y 5.6 on a
   49px car whose cabin starts at y 9.2: floating above the roofline
   entirely, off the vehicle. Amber then read as a badge or a light rather
   than a reflection. And white on the windscreen is white on the one panel
   of the car that is already pale, so the shine that was finally in the
   right place was sitting where it showed up least.

   The hood is painted in the car's own color, which is the strongest field
   on the drawing and never light, so a white mark on it reads at every paint
   and at every size. Measured against the parts either side: the cabin ends
   at 68.5% and the headlight starts at 90%, so 72 to 85% is hood. */
.po-car.po-fresh > i:nth-of-type(8),
.po-car.po-lux > i:nth-of-type(8) {   /* the shine, on the hood */
  left: 72%; top: 46%; width: 13%; height: 16%;
  background: none;
  border: 0;
  border-radius: 0;
  box-shadow: none;
  z-index: 3;
}
.po-car.po-fresh > i:nth-of-type(8)::before,
.po-car.po-fresh > i:nth-of-type(8)::after,
.po-car.po-lux > i:nth-of-type(8)::before,
.po-car.po-lux > i:nth-of-type(8)::after {
  content: "";
  position: absolute;
  left: 50%; top: 50%;
  background: rgba(255, 255, 255, .95);
  border-radius: 40%;
}
.po-car.po-fresh > i:nth-of-type(8)::before,
.po-car.po-lux > i:nth-of-type(8)::before {     /* the tall axis */
  width: 26%; height: 100%;
  transform: translate(-50%, -50%);
}
.po-car.po-fresh > i:nth-of-type(8)::after,
.po-car.po-lux > i:nth-of-type(8)::after {      /* the wide one */
  width: 100%; height: 26%;
  transform: translate(-50%, -50%);
}

/* A twinkle rather than a flash: mostly still, with one dip. The shine
   should catch the eye and then stop asking for it, which is the opposite of
   what a steady pulse does on a screen somebody is reading.

   Four seconds first, then 2.2 on request 2026-08-05 ("the sparkle animation
   should be a little faster"). The dip stays the same fraction of the cycle,
   so what changed is the wait between twinkles rather than the twinkle. */
@keyframes carShine {
  0%, 72%, 100% { opacity: .95; transform: scale(1); }
  84%           { opacity: .55; transform: scale(.72); }
}
@media (prefers-reduced-motion: no-preference) {
  .po-car.po-fresh > i:nth-of-type(8),
  .po-car.po-lux > i:nth-of-type(8) {
    animation: carShine 2.2s ease-in-out infinite;
    transform-origin: center;
  }
}
.po-car.po-fresh > i:nth-of-type(3) {   /* glass still filmed over */
  background: var(--surface-2);
  opacity: .9;
}

/* ---- luxury: a different car, not the same car in a nicer color ----

   REPORTED 2026-08-05: "can we make the luxury car model look more luxury?
   It should visually stand out."

   It did not stand out at all, and the reason is worth writing down: the
   tier changed the paint list and nothing else. A $92,000 car and a $24,000
   one were the same drawing, so the object that is supposed to be the
   reward for a decade of earnings looked exactly like the one somebody
   bought at twenty-two.

   Color cannot carry this on its own, because the ordinary register already
   owns twelve colors and the eye reads paper cutouts by silhouette first.
   So the silhouette changes:

     - a longer hood and a lower body, which is the single strongest read
       and the one thing every expensive car actually shares
     - the cabin set back and its roof dropped, so the proportion is
       hood-heavy rather than cabin-heavy
     - larger wheels, which is what makes the body look low rather than
       small
     - a chrome beltline down the flank, in the one color nothing else on
       the drawing uses

   The beltline takes the place of the door handle dash. That is a trade
   rather than a loss: a four percent mark at 84px was barely a mark, and
   the beltline is where the handles sit on a real one anyway. */
.po-car.po-lux { --po-chrome: #c9a94e; }
:root[data-theme="dark"] .po-car.po-lux { --po-chrome: #dcc173; }

.po-car.po-lux::before {         /* longer, lower, and a deeper shadow under it */
  bottom: 13%; height: 29%;
  border-radius: 5px 16px 3px 3px;
  box-shadow: 2px 4px 0 rgba(34,28,22,.26);
}
:root[data-theme="dark"] .po-car.po-lux::before { box-shadow: 2px 4px 0 rgba(0,0,0,.55); }

.po-car.po-lux::after {          /* cabin set back, roof dropped, rake laid down */
  left: 15%; bottom: 38%; width: 41%; height: 23%;
  border-radius: 4px 74% 0 0 / 4px 96% 0 0;
}
.po-car.po-lux > i:nth-of-type(3) {   /* the glass follows the cabin */
  left: 19%; bottom: 42%; width: 32%; height: 15%;
  border-radius: 3px 70% 0 0 / 3px 94% 0 0;
}

/* Bigger wheels, and the arches move with them or the fenders cut across
   the tires. Centers are held: wheel 24% and 76%, arch 24% and 76%. */
.po-car.po-lux > i:nth-of-type(1),
.po-car.po-lux > i:nth-of-type(2) { bottom: 0; width: 24%; padding-bottom: 24%; }
.po-car.po-lux > i:nth-of-type(1) { left: 12%; }
.po-car.po-lux > i:nth-of-type(2) { right: 12%; }
.po-car.po-lux > i:nth-of-type(11),
.po-car.po-lux > i:nth-of-type(12) { bottom: 12.5%; width: 30%; height: 18.5%; }
.po-car.po-lux > i:nth-of-type(11) { left: 9%; }
.po-car.po-lux > i:nth-of-type(12) { right: 9%; }

/* The beltline stops at the base of the windscreen, which is where one
   stops. Drawn at 55% wide it ran to x 64.1 against a cabin ending at 61.2,
   so it carried three percent out onto the hood and collected with the
   mirror into one gold smudge at large sizes. */
.po-car.po-lux > i:nth-of-type(6) {   /* the chrome beltline, along the flank */
  left: 9%; bottom: 30%; width: 52%; height: 4%;
  background: var(--po-chrome);
  border-radius: 2px;
  opacity: 1;
}
.po-car.po-lux > i:nth-of-type(5) {   /* the mirror, in the same chrome */
  left: 55%; bottom: 39%; width: 7%; height: 4.5%;
  background: var(--po-chrome);
}
/* THE SHINE DOES NOT MOVE WITH THE BODY, AND THAT IS DELIBERATE.

   Measured, the lux car puts it at y 44.7 against a hood whose top edge is
   y 47.5, so the top point of the star hangs about 1.4px clear of the panel
   at the default size. That measurement is why it was pushed down to 53%,
   and pushing it down was wrong: JUDGED 2026-08-05, watching both, "the
   luxury car sparkle position was good."

   It is good because the shine is a four-pointed star, not a block. Only the
   thin vertical arm reaches y 44.7; the mass of it sits on the hood, and a
   point overhanging the edge is what a highlight coming off a curved panel
   actually looks like. The measurement was right about the geometry and
   wrong about what the geometry meant, which is the case for looking as well
   as measuring rather than instead of it. */
.po-car.po-lux > i:nth-of-type(9) { bottom: 15%; height: 24%; }  /* shut line, to the new sill */
.po-car.po-lux > i:nth-of-type(10) { bottom: 30%; }              /* rear light, to the new beltline */
.po-car.po-lux > i:nth-of-type(4) { bottom: 31%; }               /* headlight, to the lower body */
.po-car.po-lux > i:nth-of-type(7) { bottom: 11%; }               /* bumper, to the new sill */

.po.sm > i:nth-of-type(8) { display: none; }

/* ============================================================
   THE STAGE

   The simulation's own world, built 2026-08-03.

   A single assembled scene rather than a row of icons: a ground
   line with the life standing on it, and a shelf underneath for the
   things it gathered. Fixed cast during play, so a new car replaces
   the old one and the scene stays readable at every age. The
   everything-at-once panorama belongs to the end screen, where
   there is room to scroll and a reason to look.

   THE GROUND MOVES WITH THE LIFE. Six chapters, six tones, from a
   pale morning at eighteen to dusk in the last one. It is the one
   thing on screen that says how far through you are without using a
   number, and it is toggleable, because a stage that changes color
   is a distraction to some readers and the point to others. Off
   falls back to a single neutral tabletop.

   The tones are deliberately low chroma and mid value. The cutouts
   carry their own color and a 2px ink outline, and a saturated
   ground would fight both.
   ============================================================ */

.stage {
  --stage: var(--surface-2);
  --stage-ground: var(--line);
  position: relative;
  background: var(--stage);
  border: 1px solid var(--line);
  border-radius: var(--radius);
  padding: 20px 22px 0;
  overflow: hidden;
  transition: background-color .6s ease;
}

/* The ground the cutouts stand on. A band rather than a line, so the
   objects have somewhere to sit rather than floating on a color. */
.stage-floor {
  position: relative;
  display: flex;
  align-items: flex-end;
  gap: 26px;
  min-height: 104px;
  /* Headroom for the hover names. The house is the tallest object on the
     row and its label sits 23px above it, against 21px of clearance to the
     stage edge, so without this the one name most worth reading, the
     address, was the one clipped. `#t-stage` is overflow: hidden, so there
     is no second chance. */
  padding-top: 10px;
  padding-bottom: 14px;
  border-bottom: 3px solid var(--stage-ground);
}
/* Slots keep their width whether or not they hold anything, so gaining a car
   does not shove the house sideways. */
.stage-slot {
  display: flex; align-items: flex-end; gap: 10px;
  min-width: 92px;
}
.stage-slot.people { flex: 1; gap: 8px; }
.stage-slot.empty { opacity: .35; }

/* ---- THE CAR WAS FALLING OFF THE END ON A PHONE ----

   FOUND 2026-08-04 while checking the hover names did not clip. At 375px
   the stage is 277px wide inside and the floor needs 328px, so the vehicle
   slot ran from x 307 to 399 against a stage ending at 327. `#t-stage` is
   overflow: hidden, so 72px of the car, most of it, was simply not there.
   The scene is supposed to say where you live, who is with you and what you
   drive, and on a phone it silently said two of those three.

   Objects scale down and the people take their own line. Everything inside
   a `.po` is positioned in percentages, so shrinking the box shrinks the
   whole drawing and nothing has to be redrawn. The people wrap within their
   line, which is the one slot whose size is not known in advance. */
@media (max-width: 620px) {
  .po { width: 58px; height: 47px; }
  .po.lg { width: 82px; height: 67px; }
  /* The phone size for keepsakes is NOT declared here. A media query adds no
     specificity, and the 40px base sits further down the file in the
     keepsakes section, so a 34px written here loses to it on every screen and
     had never once applied. It is declared next to that base instead. */

  .stage-floor { gap: 14px; flex-wrap: wrap; row-gap: 24px; }
  .stage-slot { min-width: 58px; gap: 6px; }
  /* Ordered last so the home and the vehicle share the first line: those
     two are fixed width and always fit, and the cast is the part that
     grows. */
  .stage-slot.people { order: 3; flex: 1 0 100%; flex-wrap: wrap; row-gap: 24px; }
}

/* ---- a name over the top, on hover ----
   The objects say what KIND of thing something is, by shape and color, and
   never which one. This is the answer to that and nothing more: decoration
   on top of decoration, so it is hover only and the Life and People tabs
   remain where the same facts are actually listed. Positioned above rather
   than beside, because the row is tight and a label to the side would sit on
   the next object along. */
.stage-named { position: relative; display: inline-flex; align-items: flex-end; }
.stage-name {
  position: absolute;
  bottom: calc(100% + 6px);
  left: 50%;
  transform: translateX(-50%) translateY(3px);
  padding: 2px 7px;
  border: 1px solid var(--rule);
  border-radius: 5px;
  background: var(--card);
  color: var(--ink);
  font-size: 12px;
  line-height: 1.35;
  white-space: nowrap;
  pointer-events: none;   /* never sits between the pointer and the object */
  opacity: 0;
  transition: opacity .12s ease, transform .12s ease;
  z-index: 4;
}
.stage-named:hover .stage-name {
  opacity: 1;
  transform: translateX(-50%) translateY(0);
}
/* The house and the car are at the ends of the row, and their names are the
   long ones: an address, or a year, make and model. Left to itself a label
   centerd on the house would run off the left edge of the stage, which clips
   it. These two anchor to their own edge instead. */
.stage-slot.home .stage-name { left: 0; transform: translateX(0) translateY(3px); }
.stage-slot.home .stage-named:hover .stage-name { transform: translateX(0); }
.stage-slot.vehicle .stage-name { left: auto; right: 0; transform: translateX(0) translateY(3px); }
.stage-slot.vehicle .stage-named:hover .stage-name { transform: translateX(0); }

@media (prefers-reduced-motion: reduce) {
  .stage-name,
  .stage-named:hover .stage-name { transition: none; transform: translateX(-50%); }
  .stage-slot.home .stage-name,
  .stage-slot.home .stage-named:hover .stage-name,
  .stage-slot.vehicle .stage-name,
  .stage-slot.vehicle .stage-named:hover .stage-name { transform: translateX(0); }
}

/* ---- the shelf ----
   Where accumulation shows, since the cast above is fixed. Small, in a row,
   and it scrolls sideways rather than wrapping into a second line that would
   push the scene around every time something is added. */
.stage-shelf {
  display: flex; align-items: center; gap: 12px;
  padding: 10px 2px 14px;
  overflow-x: auto;
  scrollbar-width: thin;
}
.stage-shelf:empty { display: none; }

/* ---- THE DRAWING ON ITS OWN LINE, THE CAPTION UNDER IT ----

   REPORTED 2026-08-04: "the ticket and trip keepsakes overlap their
   captions."

   They did, and the cause was that the cell had no layout at all. A `.po` is
   inline-block and the caption was inline, so both sat in the same line box:
   a caption short enough to fit sat beside the drawing, and one long enough
   to wrap put its first line beside the drawing and its second underneath
   it, through the middle of the object. Measured on a 375px screen before
   the fix, "A night out" ran its caption 26px into the ticket.

   That is also why only two were reported. Nothing here is wrong with the
   ticket or the trip; they are the ones whose captions happened to wrap.

   The trays on the identity page had already solved this in a page-level
   style block, so the app's own shelf was the one place still without it. */
.stage-shelf .obj-cell {
  display: flex; flex-direction: column; align-items: center;
  flex: 0 0 auto; gap: 6px; text-align: center;
}
.stage-shelf .cap {
  display: block;
  font-size: .62rem; text-transform: uppercase; letter-spacing: .07em;
  color: var(--ink-faint); font-weight: 700;
}
.stage-more {
  font-size: .72rem; color: var(--ink-faint); white-space: nowrap; font-weight: 600;
}

/* ---- the tones ----
   Named by chapter rather than by number, so the mapping is readable and a
   chapter renamed in the catalog is findable here. */
.stage[data-chapter="starting"]    { --stage: #e6edf3; --stage-ground: #c8d5df; }
.stage[data-chapter="footing"]     { --stage: #e6eee7; --stage-ground: #c6d8c9; }
.stage[data-chapter="building"]    { --stage: #f0ecdf; --stage-ground: #d8d0bb; }
.stage[data-chapter="established"] { --stage: #f2e7d2; --stage-ground: #dcc9a6; }
.stage[data-chapter="turning"]     { --stage: #eedcd0; --stage-ground: #d9bda9; }
.stage[data-chapter="after"]       { --stage: #e3dce4; --stage-ground: #c6bcc9; }

:root[data-theme="dark"] .stage[data-chapter="starting"]    { --stage: #1a2530; --stage-ground: #2b3a47; }
:root[data-theme="dark"] .stage[data-chapter="footing"]     { --stage: #182621; --stage-ground: #2a3b33; }
:root[data-theme="dark"] .stage[data-chapter="building"]    { --stage: #262117; --stage-ground: #3a3226; }
:root[data-theme="dark"] .stage[data-chapter="established"] { --stage: #2b2417; --stage-ground: #423827; }
:root[data-theme="dark"] .stage[data-chapter="turning"]     { --stage: #2b1f1b; --stage-ground: #40302a; }
:root[data-theme="dark"] .stage[data-chapter="after"]       { --stage: #221c26; --stage-ground: #362d3a; }

/* Toggled off: one tabletop for the whole life. */
.stage.plain,
.stage.plain[data-chapter] { --stage: var(--surface-2); --stage-ground: var(--line); }

/* ---- arriving ----
   Movement means news. Nothing idles, so when a piece of paper drops onto
   the stage it is because the life gained something, and it happens once. */
@keyframes po-arrive {
  0%   { opacity: 0; transform: translateY(-22px) rotate(-8deg); }
  60%  { opacity: 1; transform: translateY(3px) rotate(2deg); }
  100% { opacity: 1; transform: translateY(0) rotate(-2deg); }
}
.po.arriving { animation: po-arrive .5s cubic-bezier(.3,1.2,.5,1) 1; }
@media (prefers-reduced-motion: reduce) {
  .po.arriving { animation: none; }
  .stage { transition: none; }
}

/* ============================================================
   THE REST OF THE CAST

   Everything the scene needs that the first four objects did not
   cover. Same rules throughout: flat cut shapes, an ink outline, a
   hard offset shadow on the silhouette only, and detail that is
   either its own scrap or a printed mark.
   ============================================================ */

/* ---- PEOPLE ----
   A head and a body, and a token that says who. Height carries the
   first read (an adult or a child) and the token carries the second,
   which means a scene is legible before you look closely and
   specific once you do.

   Tokens are shapes rather than colors alone, because a scene with
   a partner and three children in four colors asks a reader to
   remember a key. A square is a parent, a triangle a sibling, a
   circle a friend, a ring a partner. Children carry none: being
   small IS the token. */
.po-person { width: 34px; height: 68px; }
.po-person::before {                 /* head */
  left: 50%; top: 0; width: 22px; height: 22px; margin-left: -11px;
  background: var(--po-body, var(--surface));
  border: 2px solid var(--po-edge);
  border-radius: 50%;
  box-shadow: 2px 3px 0 rgba(34,28,22,.18);
}
.po-person::after {                  /* body */
  left: 50%; bottom: 0; width: 30px; height: 42px; margin-left: -15px;
  background: var(--po-body, var(--surface));
  border: 2px solid var(--po-edge);
  border-radius: 8px 8px 2px 2px;
  box-shadow: 2px 3px 0 rgba(34,28,22,.18);
}
:root[data-theme="dark"] .po-person::before,
:root[data-theme="dark"] .po-person::after { box-shadow: 2px 3px 0 rgba(0,0,0,.5); }

.po-person > i:nth-of-type(1) {      /* the token, worn on the chest */
  left: 50%; bottom: 20px; width: 11px; height: 11px; margin-left: -5.5px;
  border: 2px solid var(--po-edge);
  background: var(--po-token, var(--surface));
}
.po-role-parent  > i:nth-of-type(1) { border-radius: 1px; }
.po-role-friend  > i:nth-of-type(1) { border-radius: 50%; }
.po-role-sibling > i:nth-of-type(1) {
  border: 0; background: none;
  border-left: 6px solid transparent; border-right: 6px solid transparent;
  border-bottom: 10px solid var(--po-edge);
  height: 0; width: 0;
}
.po-role-partner > i:nth-of-type(1) {
  border-radius: 50%;
  box-shadow: inset 0 0 0 3px var(--po-body, var(--surface));
}
.po-person.child { width: 24px; height: 46px; }
.po-person.child::before { width: 16px; height: 16px; margin-left: -8px; }
.po-person.child::after { width: 21px; height: 28px; margin-left: -10.5px; }
.po-person.child > i:nth-of-type(1) { display: none; }

/* Paper colors for people, so a household is not one color repeated. */
.po-skin-1 { --po-body: #e8d9c3; }
.po-skin-2 { --po-body: #cfd8e2; }
.po-skin-3 { --po-body: #d9d3e2; }
.po-skin-4 { --po-body: #d5e2d3; }
.po-skin-5 { --po-body: #ecd6d2; }
:root[data-theme="dark"] .po-skin-1 { --po-body: #6b5c48; }
:root[data-theme="dark"] .po-skin-2 { --po-body: #4c5a68; }
:root[data-theme="dark"] .po-skin-3 { --po-body: #575068; }
:root[data-theme="dark"] .po-skin-4 { --po-body: #4f6050; }
:root[data-theme="dark"] .po-skin-5 { --po-body: #6b4f4c; }

/* ---- HOMES OTHER THAN A HOUSE ----
   Renting is most of a life in this simulation and it was the one standing
   choice with nothing to show for it. A block is a different silhouette from
   a house, which is the point: you can tell what you are looking at
   with the color removed. */
.po-flat::before {                   /* the block */
  left: 14%; bottom: 0; width: 72%; height: 92%;
  background: var(--po-paper);
  border: 2px solid var(--po-edge);
  border-radius: 2px 2px 0 0;
  box-shadow: 2px 3px 0 rgba(34,28,22,.18);
}
.po-flat::after {                    /* a parapet, so the top is not just a cut */
  left: 8%; top: 0; width: 84%; height: 9%;
  background: var(--primary);
  border: 2px solid var(--po-edge);
  border-radius: 2px;
}
/* Windows, drawn as a printed grid rather than as six more elements. The one
   lit window is the flat that is yours. */
.po-flat > i:nth-of-type(1) {
  left: 22%; top: 22%; width: 56%; height: 52%;
  background:
    repeating-linear-gradient(to right,
      var(--po-edge) 0 2px, transparent 2px 33.33%),
    repeating-linear-gradient(to bottom,
      var(--po-edge) 0 2px, transparent 2px 33.33%);
  border: 2px solid var(--po-edge);
  background-color: var(--surface-2);
}
/* ---- THE LIT WINDOW HAS TO SIT IN A CELL ----

   REPORTED 2026-08-04: "the yellow lit window is out of the lines and it
   looks weird."

   It was, and by a very small amount, which is why it read as wrong rather
   than as different. Measured at 128x104: the grid's cell is 18.63 by 17.38
   percent and the amber patch was 15.97 by 15.03, starting 0.38% left and
   0.33% above the cell edge. So it lay over the printed line on its top and
   left and left a gap of paper on its bottom and right. Nothing about that
   is visible as a measurement; it just looks like a mistake.

   THE ARITHMETIC HAS TO MATCH THE GRID'S, and the grid's is not the round
   number it looks like. Those lines are a repeating gradient on the window
   element, which has a 2px border, so they repeat across its PADDING box:
   the cell is (56% - 4px) / 3, not 56% / 3, and the first line starts 2px
   in from the element's edge rather than at it. Written out in calc so the
   two cannot drift, and so it lands exactly at every size rather than at
   the one it happened to be eyeballed at. */
.po-flat > i:nth-of-type(2) {        /* the lit one, cell 2 across and 2 down */
  left: calc(22% + (56% - 4px) / 3 + 4px);
  top: calc(22% + (52% - 4px) / 3 + 4px);
  width: calc((56% - 4px) / 3 - 2px);
  height: calc((52% - 4px) / 3 - 2px);
  background: var(--amber);
}

/* ---- AND THE DOOR HAS TO LOOK LIKE A WAY IN ----

   REPORTED in the same breath: "the door is also very small, maybe it's an
   apartment building?" It is: this object stands for every rented tier, a
   room in a shared house up to a rented house, and a block is the honest
   generic for renting. The door was 16% by 16%, which on a three story
   block is a cat flap, and small enough that the eye read the whole shape
   as a filing cabinet rather than a building.

   Wider, taller, split down the middle like a pair of entrance doors, with
   a canopy over it and a step under it. The canopy is what actually says
   "entrance" at this size: a lintel reads as architecture where another
   rectangle reads as another window. */
.po-flat > i:nth-of-type(3) {        /* the entrance */
  left: 38%; bottom: 0; width: 24%; height: 26%;
  background: var(--surface-2);
  border: 2px solid var(--po-edge);
  border-radius: 3px 3px 0 0;
  background-image: linear-gradient(var(--po-edge), var(--po-edge));
  background-size: 2px 100%;
  background-position: center;
  background-repeat: no-repeat;
}
.po-flat > i:nth-of-type(4) {        /* the canopy over it */
  left: 33%; bottom: 26%; width: 34%; height: 5%;
  background: var(--primary);
  border: 2px solid var(--po-edge);
  border-radius: 2px;
}
.po-flat > i:nth-of-type(5) {        /* the step */
  left: 34%; bottom: -3%; width: 32%; height: 5%;
  background: var(--surface-2);
  border: 2px solid var(--po-edge);
  border-radius: 1px;
}

/* ============================================================
   A ROOM IN A SHARED HOUSE

   Added 2026-08-04. Every rented tier used to draw the same
   apartment block, which was a defensible generic until the block
   was reworked to read specifically as a block, with a proper
   entrance and a canopy. After that, "a room in a shared house"
   was being shown a building it is not.

   THE IDEA IS THAT THE HOUSE IS BIGGER THAN YOUR PART OF IT. So it
   is domestic, a pitched roof and a front door rather than three
   stories of grid, and exactly one window is lit. The lit one is
   yours; the others belong to whoever else is in. A row of buzzers
   by the door is the second thing that says shared, and it is the
   detail that a single-occupancy house would not have.

   Same construction as po-house so the two sit together, including
   the roof's outline trick: a child two percent larger in ink,
   painted before ::after, which is the only way to put an edge on
   a clip-path.
   ============================================================ */
.po-room::before {                   /* the house */
  left: 12%; bottom: 0; width: 76%; height: 60%;
  background: var(--po-paper);
  border: 2px solid var(--po-edge);
  border-radius: 2px;
  box-shadow: 2px 3px 0 rgba(34,28,22,.18);
}
.po-room::after {                    /* the roof, cut a little off square */
  left: 4%; top: 8%; width: 92%; height: 36%;
  background: var(--primary);
  clip-path: polygon(50% 0, 100% 88%, 97% 100%, 3% 100%, 0 88%);
}
.po-room > i:nth-of-type(1) {        /* the roof's ink edge, under the roof */
  left: 2%; top: 6%; width: 96%; height: 40%;
  background: var(--po-edge);
  clip-path: polygon(50% 0, 100% 88%, 97% 100%, 3% 100%, 0 88%);
  z-index: 0;
}
/* Yours. The one lit window is the point of the object, so it is the
   second part rather than the fifth: everything from the fourth on comes off
   at the small size, and a shared house with nothing lit is just a house. */
.po-room > i:nth-of-type(2) {
  left: 58%; bottom: 34%; width: 21%; height: 17%;
  background: var(--amber);
  border: 2px solid var(--po-edge);
  border-radius: 1px;
  background-image: linear-gradient(var(--po-edge), var(--po-edge));
  background-size: 2px 100%;
  background-position: center;
  background-repeat: no-repeat;
}
.po-room > i:nth-of-type(3) {        /* the shared front door */
  left: 54%; bottom: 0; width: 24%; height: 29%;
  background: var(--surface-2);
  border: 2px solid var(--po-edge);
  border-radius: 3px 3px 0 0;
  box-shadow: inset -5px 0 0 -3px var(--po-edge);
}
/* Somebody else's, and dark. Drawn in the wall's own paper with an outline
   rather than in a darker fill: an unlit window is a window, not a hole. */
.po-room > i:nth-of-type(4) {
  left: 21%; bottom: 34%; width: 21%; height: 17%;
  background: var(--surface-2);
  border: 2px solid var(--po-edge);
  border-radius: 1px;
  background-image: linear-gradient(var(--po-edge), var(--po-edge));
  background-size: 2px 100%;
  background-position: center;
  background-repeat: no-repeat;
}
.po-room > i:nth-of-type(5) {        /* and one downstairs, also not yours */
  left: 21%; bottom: 8%; width: 21%; height: 17%;
  background: var(--surface-2);
  border: 2px solid var(--po-edge);
  border-radius: 1px;
  background-image: linear-gradient(var(--po-edge), var(--po-edge));
  background-size: 2px 100%;
  background-position: center;
  background-repeat: no-repeat;
}
.po-room > i:nth-of-type(6) {        /* the buzzers: four of them, one door */
  left: 46%; bottom: 12%; width: 6%; height: 14%;
  background: var(--po-paper);
  border: 2px solid var(--po-edge);
  border-radius: 1px;
  background-image:
    linear-gradient(var(--po-edge), var(--po-edge)),
    linear-gradient(var(--po-edge), var(--po-edge)),
    linear-gradient(var(--po-edge), var(--po-edge));
  background-size: 100% 2px, 100% 2px, 100% 2px;
  background-position: 0 25%, 0 50%, 0 75%;
  background-repeat: no-repeat;
}
.po-room > i:nth-of-type(7) {        /* the step */
  left: 52%; bottom: -3%; width: 28%; height: 6%;
  background: var(--surface-2);
  border: 2px solid var(--po-edge);
  border-radius: 1px;
}
:root[data-theme="dark"] .po-room::before { box-shadow: 2px 3px 0 rgba(0,0,0,.5); }

/* ---- KEEPSAKES ----
   Things you did, kept small, because they live on a shelf. Each is one
   silhouette and one mark: enough to recognize, not enough to compete with
   the cast standing above them. */
.po-keep { width: 40px; height: 40px; }
/* Declared here rather than in the mobile block further up, because that
   block comes first in the file and a media query adds no specificity: the
   40px above would win at every width. */
@media (max-width: 620px) { .po-keep { width: 34px; height: 34px; } }

.po-ring::before {                   /* a wedding */
  left: 12%; top: 22%; width: 76%; height: 0; padding-bottom: 76%;
  background: none;
  border: 4px solid var(--amber);
  border-radius: 50%;
  box-shadow: 2px 3px 0 rgba(34,28,22,.18);
}
.po-ring::after {                    /* the stone */
  left: 38%; top: 2%; width: 24%; height: 24%;
  background: var(--primary-100);
  border: 2px solid var(--po-edge);
  transform: rotate(45deg);
}

.po-trip::before {                   /* a suitcase */
  left: 6%; top: 26%; width: 88%; height: 62%;
  background: var(--primary);
  border: 2px solid var(--po-edge);
  border-radius: 3px;
  box-shadow: 2px 3px 0 rgba(34,28,22,.18);
}
.po-trip::after {                    /* the handle */
  left: 34%; top: 12%; width: 32%; height: 18%;
  border: 2px solid var(--po-edge);
  border-bottom: 0;
  border-radius: 4px 4px 0 0;
  background: none;
}
.po-trip > i:nth-of-type(1) {        /* a strap */
  left: 6%; top: 48%; width: 88%; height: 8%;
  background: var(--po-paper);
}

.po-cap::before {                    /* a mortarboard */
  left: 0; top: 30%; width: 100%; height: 26%;
  background: var(--ink);
  /* NO BORDER, and that is deliberate rather than an oversight. Every edge
     of a diamond is one the clip-path invents, so a border here is drawn
     and then removed in its entirety: it was declared for years and never
     painted a pixel. The shape is solid ink, so it needs no outline to read
     against the paper. Written down because a dead declaration looks like a
     working one. */
  clip-path: polygon(50% 0, 100% 50%, 50% 100%, 0 50%);
  box-shadow: none;
}
.po-cap::after {                     /* the tassel */
  right: 14%; top: 44%; width: 4px; height: 36%;
  background: var(--amber);
  border-radius: 2px;
}
.po-cap > i:nth-of-type(1) {         /* the cap band under it */
  left: 26%; top: 50%; width: 48%; height: 20%;
  background: var(--ink);
  border-radius: 0 0 6px 6px;
}

/* ---- THINGS YOU OWN THAT ARE NOT A HOUSE OR A CAR ----

   REPORTED 2026-08-04: "some Things You Could Do should become objects on the
   stage. Like the boat."

   Both are drawn the way everything else on the stage is: outlined shapes in
   percentages, so one box size change scales the whole drawing. Where a
   border survives it is kept, and the two shapes that need a clip-path to be
   readable (a triangular sail, a pitched roof) go without one and use solid
   color instead, which is the rule the mortarboard set and the badge learned
   the hard way. */
/* ---- AND EVERY BOAT ITS OWN HULL ----
   REPORTED 2026-08-05: "same as boats, they should have different colors
   available." Same mechanism as the roofs and the cars: a class carrying a
   real color, drawn from the life so it is stable across a save. */
.po-hull-white  { --po-hull: #e8e3d8; }
.po-hull-navy   { --po-hull: #2b4a6f; }
.po-hull-red    { --po-hull: #a5453d; }
.po-hull-teal   { --po-hull: #3d7d77; }
.po-hull-yellow { --po-hull: #c9a63f; }
.po-hull-green  { --po-hull: #4a7c59; }

:root[data-theme="dark"] .po-hull-white  { --po-hull: #f0ece3; }
:root[data-theme="dark"] .po-hull-navy   { --po-hull: #5580ad; }
:root[data-theme="dark"] .po-hull-red    { --po-hull: #c86a61; }
:root[data-theme="dark"] .po-hull-teal   { --po-hull: #5ea39c; }
:root[data-theme="dark"] .po-hull-yellow { --po-hull: #dcbc5c; }
:root[data-theme="dark"] .po-hull-green  { --po-hull: #6da87c; }

.po-boat::before {                   /* the hull */
  left: 6%; top: 58%; width: 88%; height: 26%;
  background: var(--po-hull, var(--primary));
  border: 2px solid var(--po-edge);
  /* Curved underneath rather than clipped, so the outline survives. */
  border-radius: 4px 4px 46% 46% / 4px 4px 90% 90%;
  box-shadow: 2px 3px 0 rgba(34,28,22,.18);
}
.po-boat::after {                    /* the sail */
  left: 50%; top: 12%; width: 34%; height: 44%;
  background: var(--amber);
  clip-path: polygon(0 0, 100% 100%, 0 100%);
}
.po-boat > i:nth-of-type(1) {        /* the mast */
  left: 46%; top: 10%; width: 5%; height: 50%;
  background: var(--po-edge);
  border-radius: 1px;
}

/* ---- THE RV ----
   A box on wheels with a stripe and a window. Longer than it is tall, which
   is most of what separates it from the cabin at a glance. */
.po-rv::before {                     /* the body */
  left: 4%; top: 26%; width: 84%; height: 44%;
  background: var(--po-paper);
  border: 2px solid var(--po-edge);
  border-radius: 4px 8px 3px 3px;
  box-shadow: 2px 3px 0 rgba(34,28,22,.18);
}
.po-rv::after {                      /* the stripe along the side */
  left: 8%; top: 46%; width: 76%; height: 9%;
  background: var(--po-hull, var(--primary));
}
.po-rv > i:nth-of-type(1) {          /* the window */
  left: 58%; top: 32%; width: 24%; height: 14%;
  background: var(--surface-2);
  border: 2px solid var(--po-edge);
  border-radius: 2px;
}
.po-rv > i:nth-of-type(2) {          /* front wheel */
  left: 16%; top: 64%; width: 16%; height: 0; padding-bottom: 16%;
  background: var(--po-edge);
  border-radius: 50%;
}
.po-rv > i:nth-of-type(3) {          /* back wheel */
  left: 60%; top: 64%; width: 16%; height: 0; padding-bottom: 16%;
  background: var(--po-edge);
  border-radius: 50%;
}

/* ---- THE WORKSHOP ----
   A shed with a wide door and a bench light. Squarer than the cabin and with
   a flatter roof, so the two do not read as the same building. */
.po-workshop::before {               /* the body */
  left: 10%; top: 34%; width: 80%; height: 54%;
  background: var(--po-paper);
  border: 2px solid var(--po-edge);
  border-radius: 2px;
  box-shadow: 2px 3px 0 rgba(34,28,22,.18);
}
.po-workshop::after {                /* a shallow roof, wider than the walls */
  left: 2%; top: 18%; width: 96%; height: 20%;
  background: var(--po-hull, var(--primary));
  clip-path: polygon(50% 0, 100% 100%, 0 100%);
}
.po-workshop > i:nth-of-type(1) {    /* the wide door */
  left: 24%; top: 52%; width: 40%; height: 36%;
  background: var(--po-edge);
  border-radius: 2px 2px 0 0;
}
.po-workshop > i:nth-of-type(2) {    /* the lit window beside it */
  left: 70%; top: 50%; width: 15%; height: 15%;
  background: var(--amber);
  border: 2px solid var(--po-edge);
}

.po-cabin::before {                  /* the body */
  left: 16%; top: 44%; width: 68%; height: 44%;
  background: var(--po-paper);
  border: 2px solid var(--po-edge);
  border-radius: 2px;
  box-shadow: 2px 3px 0 rgba(34,28,22,.18);
}
.po-cabin::after {                   /* the roof, steeper than a house's */
  left: 6%; top: 12%; width: 88%; height: 36%;
  background: var(--amber-strong);
  clip-path: polygon(50% 0, 100% 100%, 0 100%);
}
.po-cabin > i:nth-of-type(1) {       /* the door */
  left: 42%; top: 62%; width: 18%; height: 26%;
  background: var(--po-edge);
  border-radius: 2px 2px 0 0;
}
.po-cabin > i:nth-of-type(2) {       /* one lit window */
  left: 22%; top: 54%; width: 15%; height: 15%;
  background: var(--amber);
  border: 2px solid var(--po-edge);
}

/* ============================================================
   THE ANIMAL

   REPORTED 2026-08-05: "need a dog icon/css asset. Maybe a few
   different dog types?"

   One body, drawn small and standing on the same ground line the
   people do, with the head and the tail as the parts that say which
   animal it is. Three dogs and a cat, and the difference between
   them is the ear, the tail and the height rather than four separate
   drawings, because four drawings would drift apart the first time
   one was edited.

   Sized against the people rather than the objects: a dog beside a
   figure has to read as an animal at that figure's feet, and a dog
   drawn at house scale reads as a horse.
   ============================================================ */
.po-pet { width: 40px; height: 34px; }

.po-pet::before {                    /* the body */
  left: 14%; top: 40%; width: 62%; height: 34%;
  background: var(--po-body, var(--amber-strong));
  border: 2px solid var(--po-edge);
  border-radius: 6px 8px 5px 5px;
  box-shadow: 2px 3px 0 rgba(34,28,22,.18);
}
.po-pet::after {                     /* the head */
  left: 62%; top: 20%; width: 30%; height: 30%;
  background: var(--po-body, var(--amber-strong));
  border: 2px solid var(--po-edge);
  border-radius: 6px 7px 4px 4px;
}
.po-pet > i:nth-of-type(1) {         /* the front leg */
  left: 24%; top: 70%; width: 9%; height: 26%;
  background: var(--po-body, var(--amber-strong));
  border: 2px solid var(--po-edge);
  border-top: 0;
  border-radius: 0 0 2px 2px;
}
.po-pet > i:nth-of-type(2) {         /* the back leg */
  left: 58%; top: 70%; width: 9%; height: 26%;
  background: var(--po-body, var(--amber-strong));
  border: 2px solid var(--po-edge);
  border-top: 0;
  border-radius: 0 0 2px 2px;
}
.po-pet > i:nth-of-type(3) {         /* the ear */
  left: 66%; top: 12%; width: 12%; height: 16%;
  background: var(--po-edge);
  border-radius: 3px 3px 0 0;
}
/* ---- THE TAIL HAS TO GROW OUT OF THE BODY ----

   REPORTED 2026-08-05: "their tails are disconnected."

   The boxes were overlapping the whole time, by four to eight pixels on
   every variant, so this was never a position problem. It is the outline:
   children paint above `::before`, so a tail with a border all the way round
   drew its own edge straight across the body and read as a separate shape
   resting on top of the dog.

   The legs already had the answer and had had it since they were drawn: drop
   the border on the side that meets the body, and square that corner. Same
   treatment here, which is why the tail now reads as part of the animal
   rather than as a lozenge parked behind it. */
.po-pet > i:nth-of-type(4) {         /* the tail */
  /* Pushed further in on 2026-08-05: "the pet tails are still barely
     connected. A little closer." The border fix stopped them reading as
     separate shapes and left them meeting the body at a corner, which at
     40px is two or three pixels of contact. These now run a clear tenth of
     the box into the body, so the join is under the body rather than at the
     edge of it. */
  left: 6%; top: 38%; width: 19%; height: 11%;
  background: var(--po-body, var(--amber-strong));
  border: 2px solid var(--po-edge);
  border-right: 0;
  border-radius: 4px 0 0 4px;
}

/* ---- the three dogs ----
   A tall one, a long low one and a small one. Only the proportions move. */
.po-pet.pet-look-0 { --po-body: #a9743d; }
.po-pet.pet-look-1 {                 /* long and low */
  --po-body: #6f5a44;
  width: 46px; height: 30px;
}
.po-pet.pet-look-1::before { left: 10%; width: 70%; height: 30%; top: 44%; }
/* The long body sits lower, so the tail has to come down to meet it. */
.po-pet.pet-look-1 > i:nth-of-type(4) { left: 4%; top: 43%; width: 19%; height: 11%; }
.po-pet.pet-look-2 {                 /* small, with a curled tail */
  --po-body: #c9a86a;
  width: 34px; height: 30px;
}
.po-pet.pet-look-2 > i:nth-of-type(4) {   /* a short curl, still joined at the body */
  left: 4%; top: 34%; width: 18%; height: 15%;
  border-right: 0;
  border-radius: 50% 0 0 50%;
}

/* ---- the cat ----
   Pointed ear, upright tail, and it sits lower. Same body underneath. */
.po-pet.pet-cat { --po-body: #57534e; }
.po-pet.pet-cat > i:nth-of-type(3) { /* a pointed ear rather than a folded one */
  left: 66%; top: 8%; width: 12%; height: 18%;
  background: var(--po-edge);
  border-radius: 0;
  clip-path: polygon(50% 0, 100% 100%, 0 100%);
}
.po-pet.pet-cat > i:nth-of-type(4) { /* the tail, up rather than out */
  left: 14%; top: 10%; width: 10%; height: 42%;
  /* Joined at the bottom instead of the side, because this one rises out of
     the back rather than trailing off it. */
  border-right: 2px solid var(--po-edge);
  border-bottom: 0;
  border-radius: 4px 4px 0 0;
}

/* ============================================================
   THE SATISFACTION LOOP

   Asked for 2026-08-05: "we need to maximize the satisfaction
   loop, the build up and pay off of things. This sim should not
   only be educational, but also fun."

   The audit found the ingredients all present and none of them
   connected: eight sounds defined with three of them called once
   each in the whole file, confetti that only ever fired on the end
   screen, and no count-up or tick machinery anywhere. Pressing the
   button replaced six numbers between one frame and the next, in
   silence.

   Two rules held while adding this.

   NO NEW COLOR VOCABULARY. A figure moving the right way tints with
   the same green that already means "this is doing its job" and the
   wrong way with the same red that already means "this is costing
   you". Inventing a celebration palette would have given the screen
   two green.

   NOTHING MOVES FOR ANYBODY WHO ASKED IT NOT TO. Every rule here is
   inside prefers-reduced-motion: no-preference, and the count-up
   checks the same query in script before it starts.
   ============================================================ */

/* A figure that just changed, held long enough to find and short enough
   not to become part of the design. */
@keyframes bumpGood {
  0%   { background: color-mix(in srgb, var(--money) 26%, transparent); }
  100% { background: transparent; }
}
@keyframes bumpBad {
  0%   { background: color-mix(in srgb, var(--danger) 22%, transparent); }
  100% { background: transparent; }
}
@media (prefers-reduced-motion: no-preference) {
  .sim-status .v.bump-good, .hero-stat .v.bump-good {
    animation: bumpGood 1.3s ease-out;
    border-radius: 4px;
  }
  .sim-status .v.bump-bad, .hero-stat .v.bump-bad {
    animation: bumpBad 1.3s ease-out;
    border-radius: 4px;
  }
}
/* Tabular figures, so a number counting from 4,000 to 12,000 does not shove
   the rest of the cell sideways on every frame. The status cells already ask
   for this; the hero has to as well or the whole rail jitters. */
.sim-status .v, .hero-stat .v { font-variant-numeric: tabular-nums; }

/* A sealed estimate. Not disabled: a disabled input is skipped by the Tab
   key and read as broken, and the number in it is still worth reading. */
.answer-row input[readonly] {
  background: var(--surface-2);
  color: var(--ink-soft);
  cursor: default;
}
.btn-redo { margin-left: 8px; font-size: .82rem; }

/* The result lines a class page could not read, echoed back so a teacher can
   match one to whoever sent it. Monospace, because what is wrong with them is
   usually a character. */
.bad-lines { list-style: none; margin: 10px 0 0; padding: 0; }
.bad-lines li {
  padding: 7px 0; border-top: 1px solid var(--line);
  display: flex; flex-wrap: wrap; align-items: baseline; gap: 4px 12px;
}
.bad-lines code {
  font-family: var(--mono); font-size: .76rem;
  word-break: break-all; flex: 1 1 22ch; color: var(--ink);
}
.bad-lines .why { font-size: .74rem; color: var(--ink-faint); font-style: italic; }

/* ---- the decade, on a chapter turn ----
   Same construction as the status strip, so a figure means the same thing
   here as it does on the turn screen. Four cells, because it interrupts a
   turn and has to be readable in the time somebody will give an
   interruption. */
.mark-recap {
  display: grid; grid-template-columns: repeat(auto-fit, minmax(120px, 1fr));
  gap: 1px; background: var(--line);
  border: 1px solid var(--line); border-radius: var(--radius-sm);
  overflow: hidden; margin: 14px 0 0;
}
.mark-recap > div { background: var(--surface); padding: 10px 12px; }
/* Which years these figures are, across the top. Without it the card shows a
   figure called Invested while the page behind it shows a different one, and
   nothing says one of them is history. */
.mark-recap .recap-when {
  grid-column: 1 / -1;
  font-size: .68rem; font-weight: 800; text-transform: uppercase;
  letter-spacing: .06em; color: var(--ink-faint);
  padding: 8px 12px 6px;
}
.mark-recap .k {
  font-size: .62rem; font-weight: 800; text-transform: uppercase;
  letter-spacing: .05em; color: var(--ink-faint);
}
.mark-recap .v {
  font-size: 1.02rem; font-weight: 800; margin-top: 2px;
  font-variant-numeric: tabular-nums; letter-spacing: -.01em;
}
.mark-recap .s { font-size: .72rem; color: var(--ink-faint); margin-top: 1px; }

/* ---- the one number the course is about ----

   A band across the top of the readouts rather than another cell in the
   strip. Laid out as a row at any usable width: the figure and its sentence
   on the left, the run's shape on the right. Stacked only where a row would
   crush both, which is under about 560px.

   It was built for a 330px sidebar and the sidebar was reverted. Reworked
   for the full-width column rather than left at sidebar proportions, where
   a 1,184px-wide card holding one number in the top left corner would have
   looked like a mistake. */
.hero-stat {
  border: 1px solid var(--line);
  border-left: 3px solid var(--money);
  border-radius: var(--radius);
  background: var(--surface);
  padding: 14px 18px 14px;
  margin-bottom: 20px;
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 8px 28px;
}
/* The label and the figure are one block; the sentence sits under them and
   the line sits beside the lot. */
.hero-stat .k {
  font-size: .64rem; font-weight: 800; text-transform: uppercase;
  letter-spacing: .05em; color: var(--ink-faint);
  flex: 1 0 100%;
}
.hero-stat .v {
  font-size: 1.8rem; font-weight: 800; letter-spacing: -.02em;
  line-height: 1.05; flex: 0 0 auto;
}
.hero-stat .v.good { color: var(--money-text); }
.hero-stat .s {
  font-size: .86rem; color: var(--ink-soft); line-height: 1.45;
  flex: 1 1 12ch;
}
.hero-stat .s .up { color: var(--money-text); font-weight: 700; }
.hero-stat .s .down { color: var(--danger); font-weight: 700; }

/* The run so far, as one line. No axes and no labels: it says "this is the
   shape", and a chart with furniture on a status band would be neither. */
.hero-stat .spark {
  display: block; height: 30px; margin: 0;
  flex: 0 1 220px;
  overflow: visible;
}
@media (max-width: 560px) {
  .hero-stat { display: block; }
  .hero-stat .v { margin-top: 2px; }
  .hero-stat .s { margin-top: 4px; }
  .hero-stat .spark { width: 100%; margin-top: 10px; }
}
.hero-stat .spark path {
  fill: none;
  stroke: var(--money);
  stroke-width: 1.6;
  stroke-linejoin: round;
  stroke-linecap: round;
  /* Scaled by preserveAspectRatio="none", which would stretch the stroke
     with it and give a line that is fat on one axis and thin on the other. */
  vector-effect: non-scaling-stroke;
}

/* ---- THE TWO-COLUMN TURN SCREEN, TRIED AND REVERTED ----

   Built 2026-08-05 and taken out again on request the next day: "I think I
   like the old layout better."

   Worth recording what it did, because the measurements stand even though
   the layout did not. At 1280x720 the original stacks 594px of readout above
   the tabs, so the panel a player interacts with starts at y 691 of a 720px
   viewport and the advance button sits at y 911, below the fold on every tab
   and as far down as y 1,459 on the money tab. The grid moved the readouts
   into a 330px rail and put the button at y 201 on all four tabs.

   It measured better and read worse, which is a real result and not a
   contradiction: one column down the middle of the page is the idiom the
   rest of this course is set in, and a dashboard rail is not.

   The satisfaction work built alongside it stayed. The count-up, the tints,
   the promoted Invested figure and the milestone confetti were never
   consequences of the columns, and all of them work in one column exactly as
   they did in two.

   ---- THE SETUP SCREEN'S TWO COLUMNS WENT THE SAME WAY ----

   Built the same day for the same reason and taken out on
   2026-08-06: "the setup screen still needs work yes."

   The measurement that produced it stands too. That screen is
   2,968px of page in a 720px viewport, four and a bit screens of
   scrolling to make six choices, in a 1,160px container that never
   uses its width. Moving the summary into a sidebar did not even
   fix that: the first version came out LONGER, 3,308px, because
   taking 384px off the choices dropped every card grid from four
   across to three. Tuned back, it landed at 2,969px, one pixel off
   where it started.

   Which is the useful finding, and it survives the revert: the
   scrolling on that screen is the length of the card text, not the
   layout. Shortening the cards is the job. Rearranging the columns
   was never going to be.

   What is kept from that pass is everything that was not layout:
   the state picker no longer shares the heading "Where you live"
   with the housing picker four sections above it, and the lede
   counts six choices instead of five.
   ============================================================ */
.aside-eyebrow {
  font-size: .64rem; font-weight: 800; text-transform: uppercase;
  letter-spacing: .05em; color: var(--ink-faint);
}
#setup-summary { font-size: 1.02rem; line-height: 1.6; margin: 6px 0 0; color: var(--ink); }
#setup-cost { margin: 8px 0 0; }
/* Sticky at every width, which is what the original inline style did. */
#setup-summary-box {
  border-left: 3px solid var(--primary);
  margin-bottom: 24px; position: sticky; top: 74px; z-index: 5;
}
/* The shortcuts, said once beside the button they drive. Dropped on touch
   screens, where there is no keyboard to press and the line is just noise. */
.keys-hint { margin-left: 14px; color: var(--ink-faint); }
@media (hover: none) { .keys-hint { display: none; } }

.setup-go { margin: 26px 0 50px; }
.setup-go .btn-quiet { margin-left: 10px; }

/* ---- A BADGE, FOR GIVING SOME OF IT AWAY ----

   REPORTED 2026-08-04: "donating to a good cause as a thing you could do,
   with a badge." A rosette: a disc with two ribbon tails under it, which is
   the shape people read as "given" rather than "bought".

   ---- REBUILT 2026-08-05 ----

   REPORTED: "donation badge needs some help. It may need a full redesign."
   It did, and measuring it found two faults that between them explain why it
   read as an amber circle rather than as an award.

   THE TWO TAILS WERE ON OPPOSITE SIDES OF THE DISC. Paint order here is
   ::before, then the children in order, then ::after LAST. The disc was
   ::before and the left tail was ::after, so the left tail painted on top of
   the disc while the right tail, a child, painted underneath it. One ribbon,
   drawn half in front and half behind the thing it hangs from. Measured: the
   left tail overlapped the disc by 16 by 25 percent and won; the right
   overlapped by 16 by 24 and lost.

   AND THERE WAS ALMOST NO RIBBON TO SEE. The disc ran from 6% to 82% of the
   box and the tails ended at 90%, so eight percent of the height was all the
   ribbon that showed: two small nubs under a very large circle. A rosette is
   read by its tails. Nothing else in the drawing says "awarded".

   So the order is rebuilt around what has to sit on top of what:
     ::before  the left tail      behind
     > i:1     the right tail     behind
     > i:2     the disc           over both tails
     ::after   the center ring    on the disc

   That is the same trick the car uses to put its cabin over its body, and
   the reason the disc is a child rather than ::before: the piece that must
   paint last is the small mark on the face, so everything else has to move
   up one slot.

   NO BORDER ON THE TAILS, and deliberately, the same rule the mortarboard
   follows. Every edge of a notched ribbon is one the clip-path invents, so a
   border here is drawn and then removed in its entirety: the assets page
   caught exactly that on the first version of this shape. Solid color against
   the paper reads on its own. */
.po-badge::before {                  /* the left ribbon tail, behind */
  left: 31%; top: 45%; width: 15%; height: 52%;
  background: var(--primary);
  clip-path: polygon(0 0, 100% 0, 100% 100%, 50% 70%, 0 100%);
  transform: rotate(-11deg);
  transform-origin: 50% 0;
}
.po-badge > i:nth-of-type(1) {       /* the right ribbon tail, behind */
  left: 54%; top: 45%; width: 15%; height: 52%;
  background: var(--primary-600);
  clip-path: polygon(0 0, 100% 0, 100% 100%, 50% 70%, 0 100%);
  transform: rotate(11deg);
  transform-origin: 50% 0;
}
/* The disc, over both tails. Smaller than it was, which is most of the fix:
   the old one filled three quarters of the height and left no room for the
   thing that makes a rosette a rosette. */
.po-badge > i:nth-of-type(2) {
  left: 27%; top: 2%; width: 46%; height: 0; padding-bottom: 46%;
  background: var(--amber);
  border: 2px solid var(--po-edge);
  border-radius: 50%;
  box-shadow: 2px 3px 0 rgba(34,28,22,.18);
}
:root[data-theme="dark"] .po-badge > i:nth-of-type(2) {
  box-shadow: 2px 3px 0 rgba(0,0,0,.5);
}
/* A ring rather than a filled dot. The old center was a solid paper circle
   sitting high on the disc, which read as a hole; a ring reads as a medal,
   and it is centered on the disc this time rather than nine percent above
   its middle. */
.po-badge::after {
  left: 41%; top: 21%; width: 18%; height: 0; padding-bottom: 18%;
  background: none;
  border: 2px solid var(--po-edge);
  border-radius: 50%;
}

.po-ticket::before {                 /* a stub, torn down one side */
  left: 4%; top: 26%; width: 92%; height: 50%;
  background: var(--money-100);
  border: 2px solid var(--po-edge);
  border-radius: 2px;
  /* The torn edge used to be a clip-path, so the whole zigzag had no
     outline while the three straight sides had one: a stub that looked
     chewed rather than torn. A perforation says the same thing, keeps the
     border, and is the more honest picture of a ticket stub. Printed as a
     dashed line, which is what a perforation is. */
  background-image: repeating-linear-gradient(
    to bottom, var(--po-edge) 0 3px, transparent 3px 7px);
  background-size: 2px 100%;
  background-position: 82% 0;
  background-repeat: no-repeat;
  box-shadow: 2px 3px 0 rgba(34,28,22,.18);
}
.po-ticket::after {                  /* printed line */
  left: 14%; top: 44%; width: 46%; height: 6%;
  background: var(--ink-faint);
}

/* ---- WHAT YOU BUILT ---- */
.po-deed::before {                   /* a document */
  left: 10%; top: 8%; width: 80%; height: 84%;
  background: var(--po-paper);
  border: 2px solid var(--po-edge);
  border-radius: 2px;
  box-shadow: 2px 3px 0 rgba(34,28,22,.18);
}
.po-deed::after {                    /* the seal */
  right: 12%; bottom: 12%; width: 34%; height: 0; padding-bottom: 34%;
  background: var(--primary);
  border: 2px solid var(--po-edge);
  border-radius: 50%;
}
.po-deed > i:nth-of-type(1),
.po-deed > i:nth-of-type(2) {
  left: 22%; height: 5%;
  background: var(--ink-faint);
  border-radius: 1px;
}
.po-deed > i:nth-of-type(1) { top: 26%; width: 56%; }
.po-deed > i:nth-of-type(2) { top: 42%; width: 40%; }

:root[data-theme="dark"] .po-flat::before,
:root[data-theme="dark"] .po-trip::before,
:root[data-theme="dark"] .po-ticket::before,
:root[data-theme="dark"] .po-deed::before,
:root[data-theme="dark"] .po-ring::before { box-shadow: 2px 3px 0 rgba(0,0,0,.5); }

/* The two reading preferences that sit under the progress track. Grouped so
   they read as settings for the screen rather than as decisions in the life,
   which is the distinction a player has to be able to make instantly. */
.sim-toggles {
  display: flex; flex-wrap: wrap; gap: 10px; margin-top: 14px;
}
.sim-toggles .btn-quiet {
  font-size: .78rem; padding: 6px 12px; min-height: 36px;
}

/* ============================================================
   THE PANORAMA

   Everything at once, at the end of a run. The counterpart to the
   fixed cast on the stage: readability during play, completeness
   afterwards.

   A single sideways strip rather than a grid, because the ordering
   is the content. Things are laid out in the order they happened,
   so scrolling left to right is walking through the life, and a
   grid would throw that away for the sake of fitting.
   ============================================================ */

#e-panorama { margin-top: 4px; }

.pano-strip {
  display: flex;
  align-items: flex-end;
  gap: 26px;
  overflow-x: auto;
  padding: 18px 4px 20px;
  background: var(--surface-2);
  border: 1px solid var(--line);
  border-radius: var(--radius);
  /* The ground line runs the whole width, under everything, so the strip
     reads as one continuous stretch rather than as a row of tiles. */
  background-image: linear-gradient(var(--line), var(--line));
  background-size: 100% 3px;
  background-position: 0 calc(100% - 46px);
  background-repeat: no-repeat;
  scroll-snap-type: x proximity;
}

.pano-item {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: flex-end;
  flex: 0 0 auto;
  min-width: 96px;
  scroll-snap-align: center;
}
/* The object sits on the ground line and the labels hang below it, so the
   line stays unbroken across the whole strip. */
.pano-item .po { margin-bottom: 10px; }
.pano-label {
  font-size: .74rem; font-weight: 700; color: var(--ink);
  text-align: center; line-height: 1.3; max-width: 128px;
}
.pano-note {
  font-size: .66rem; color: var(--ink-faint); margin-top: 2px;
  font-variant-numeric: tabular-nums;
}
.pano-count { margin: 10px 0 0; }

/* Long lives make long strips. A visible scrollbar is the honest signal
   that there is more, and on a trackpad there is no other one. */
.pano-strip::-webkit-scrollbar { height: 10px; }
.pano-strip::-webkit-scrollbar-track { background: var(--line-soft); border-radius: 5px; }
.pano-strip::-webkit-scrollbar-thumb {
  background: var(--ink-faint); border-radius: 5px; border: 2px solid var(--line-soft);
}

@media (max-width: 700px) {
  .pano-strip { gap: 18px; }
  .pano-item { min-width: 76px; }
}

/* ============================================================
   THE COLLAGE

   The panorama's opposite number. That one is a timeline and
   scrolls sideways; this one is grouped by kind and WRAPS, so the
   whole life is on screen at once with nothing hidden off the
   right-hand edge. Nothing here scrolls, which is the point of it
   existing next to something that does.
   ============================================================ */

/* ---- the showcase ----
   Five things at most, at the size the stage uses, because these are the
   ones that had to be earned. Centered rather than left-aligned: it is a
   shelf being presented, not a list being read. */
.showcase {
  display: flex; flex-wrap: wrap; justify-content: center;
  gap: 28px 34px;
  padding: 22px 16px;
  background: var(--surface-2);
  border: 1px solid var(--line);
  border-radius: var(--radius);
  margin-bottom: 18px;
}
.showcase-item {
  display: flex; flex-direction: column; align-items: center;
  text-align: center; flex: 0 0 auto; max-width: 150px;
}
/* ---- SQUARE, BECAUSE THESE ARE KEEPSAKES ----

   REPORTED 2026-08-05: "the married ring icon seems a bit big and overlaps
   the word Married a lot."

   Measured: at `lg` the box is 128 by 104 and the ring's ink reached 128.1,
   overflowing by 19.7px and landing 9.9px on top of its own label.

   The cause is that the ring is a circle made with `padding-bottom: 76%`,
   and a percentage padding resolves against WIDTH, not height. In the square
   40 by 40 box these are drawn for that is a circle. In a 128 by 104 box it
   is an oval taller than the box that holds it.

   So the showcase sizes them square rather than borrowing the house's
   proportions. One rule here instead of a correction inside each drawing,
   because every object that can appear on this row is a keepsake. */
.showcase .po { width: 84px; height: 84px; }
.showcase-item .po { margin-bottom: 12px; }
.showcase-label {
  font-size: .86rem; font-weight: 800; color: var(--ink); line-height: 1.3;
}
.showcase-note {
  font-size: .72rem; color: var(--ink-soft); margin-top: 3px; line-height: 1.4;
}

/* ---- everything, by kind ---- */
.collage-group + .collage-group { margin-top: 20px; }
.collage-title {
  font-size: .74rem; font-weight: 800; text-transform: uppercase;
  letter-spacing: .07em; color: var(--ink-faint);
  margin: 0 0 10px;
}
.collage-grid {
  display: flex; flex-wrap: wrap; gap: 18px 22px;
  align-items: flex-end;
}
.collage-item {
  display: flex; flex-direction: column; align-items: center;
  text-align: center; flex: 0 0 auto; max-width: 132px;
}
.collage-item .po { margin-bottom: 8px; }
.collage-label {
  font-size: .74rem; font-weight: 700; color: var(--ink); line-height: 1.3;
}
.collage-note {
  font-size: .66rem; color: var(--ink-faint); margin-top: 2px; line-height: 1.4;
}

@media (max-width: 700px) {
  .showcase { gap: 20px 22px; padding: 18px 10px; }
  .collage-grid { gap: 14px 16px; }
  .collage-item { max-width: 104px; }
}

/* ============================================================
   THE SIMULATION'S OWN FRAME

   Applied by a single class on the root while a run is on screen.

   The masthead shrinks rather than disappearing. A full takeover
   would read as more immersive and would leave a student with the
   browser back button and a teacher with nothing to point at, so
   what stays is the smallest possible way out: the wordmark, still
   a link to the course, and nothing else competing with it.

   The width opens up because the sim is the only thing on the site
   that is a workspace rather than a piece of writing. Everything
   else is prose and wants a measure; this has a scene, a status
   strip, four tabs and a budget, and 1160px was making it crowd.
   ============================================================ */

:root.in-run .site-header {
  box-shadow: none;
  border-bottom: 2px solid var(--ink);
}
:root.in-run .site-header .wrap,
:root.in-run .site-header .nav {
  padding-top: 7px;
  padding-bottom: 7px;
  max-width: var(--sim-maxw, 1320px);
}
:root.in-run .brand { font-size: .92rem; }
:root.in-run .brand .mark,
:root.in-run .brand-mark {
  width: 22px; height: 22px; font-size: .78rem; box-shadow: none;
}

/* The course links go. Inside a run they are a way to lose your place, and
   the wordmark already leads back to the course. The theme toggle stays,
   because a run can last an hour and the light can change. */
:root.in-run .site-nav a,
:root.in-run .nav-right a { display: none; }

/* The width the simulation actually wants.

   Written as min() rather than as a breakpoint. A fixed 1320 with a media
   query snapping back to 1160 meant the two commonest school screens, 1280
   and 1366, sat either side of the switch and one of them got nothing. This
   takes whatever is there up to a ceiling, so a 1280 laptop gains its
   seventy spare pixels and a large monitor still stops before the budget
   turns into a stretched-out row of distant columns. */
:root.in-run .module-container,
:root.in-run .sim-screen .wrap,
:root.in-run .sim-screen .hero {
  max-width: min(var(--sim-maxw, 1320px), calc(100vw - 48px));
}

/* And a little air, since the header is thinner. */
:root.in-run .sim-screen .hero { padding-top: 30px; }

/* ---- the market chart ----
   Two panels on one age axis, drawn as a single SVG. It stretches to the
   panel width and keeps a fixed height, because the shape of the line is
   the content and its exact aspect ratio is not. */
.market-panel { margin-top: 18px; }
/* Both market charts, the turn screen's and the end screen's, are the same
   drawing at the same 660x320 viewBox from the same builder. CSS is the only
   thing that differs, and only in how wide each is allowed to get.

   `preserveAspectRatio="none"` and a fixed height used to be on this rule,
   which stretched the drawing to the box and scaled the type by a different
   factor on each axis. Fine while the only text was "0%" and some ages, and
   not once the chart grew two scales and two rotated titles. */
.market-svg {
  display: block;
  width: 100%;
  height: auto;
  max-width: 640px;
  aspect-ratio: 660 / 320;
  margin: 8px 0 4px;
  overflow: visible;
  cursor: crosshair;
}
/* Focusable, so it needs a focus ring like any other control. */
.market-svg:focus-visible {
  outline: 2px solid var(--primary);
  outline-offset: 3px;
  border-radius: 4px;
}
/* The end screen's copy gets more room, being the one with a whole life on
   it and a reason to look. Capped, or an 1,100px container gives it a 540px
   chart; at 860 the box is about 417 tall, which is what it was laid out
   for. A max-HEIGHT was the first attempt and it letterboxed: the box stayed
   1,112 wide while the drawing scaled to the height, leaving 200px of
   nothing down each side. */
.market-svg.life-market { max-width: 860px; }

/* What the pointer or the arrow keys found. A fixed line rather than one
   that appears, so the block below the chart does not jump by its own
   height every time somebody moves the mouse onto it. */
.market-read {
  display: flex; flex-wrap: wrap; align-items: baseline; gap: 8px;
  min-height: 1.5em;
  font-size: .86rem; font-variant-numeric: tabular-nums;
  color: var(--ink-soft);
  padding: 2px 0 4px;
}
.market-read b { color: var(--ink); font-weight: 800; }
.market-read .sep { color: var(--ink-faint); }
.market-read .up { color: var(--money-text); font-weight: 700; }
.market-read .down { color: var(--danger); font-weight: 700; }
.market-read .hint { color: var(--ink-faint); font-style: italic; }

/* The end screen's version of the same chart. Taller, because it carries a
   whole life rather than the years so far and the portfolio line is the part
   worth reading: seventy years of it in 190px is a smudge. */
/* Below about 700px the drawing has to shrink its type along with everything
   else, because the viewBox scales as one piece. The prose summary under the
   chart carries the same facts in words and is the readable path at that
   width; the chart stays as the shape. */
@media (max-width: 700px) { .market-svg { min-height: 200px; } }

.market-key {
  display: flex; flex-wrap: wrap; gap: 16px;
  font-size: .74rem; color: var(--ink-soft); font-weight: 600;
  padding: 4px 0 8px;
}
.market-key span { display: inline-flex; align-items: center; gap: 6px; }
.market-key i { display: inline-block; width: 12px; height: 12px; border-radius: 2px; }
.market-key .k-up { background: var(--money); }
.market-key .k-down { background: var(--danger); }
.market-key .k-line { background: var(--primary); height: 3px; border-radius: 2px; }

/* ---- the budget in five-year turns ----
   A per-year column beside the block total. The block figure keeps the
   right-hand position it has always had, so a player who has learned where
   to look still finds it there, and the per-year figure sits between the
   label and it. */
.budget-line.two,
.budget-head {
  display: grid;
  grid-template-columns: 1fr auto auto;
  gap: 0 18px;
  align-items: baseline;
}
.budget-head {
  font-size: .64rem; text-transform: uppercase; letter-spacing: .07em;
  font-weight: 700; color: var(--ink-faint);
  padding: 0 0 6px; border-bottom: 1px solid var(--line);
  margin-bottom: 4px;
}
.budget-head .amt, .budget-line.two .amt { min-width: 92px; text-align: right; }
/* The one-off marker is quieter than a figure, because it is the absence of
   one rather than a smaller one. */
.budget-line.two .amt.none {
  color: var(--ink-faint); font-weight: 600; font-size: .82rem;
}
@media (max-width: 620px) {
  .budget-head .amt, .budget-line.two .amt { min-width: 72px; }
}

/* ---- what you are choosing about, behind the choice ----
   The current home or the current car, set behind the Life tab panel. It is
   scenery: aria-hidden, non-interactive, and never the only place a fact
   appears. Bottom right so it sits away from the reading, and faint enough
   that the text over it keeps its measured contrast, since the token pairs
   the checker tests are all against the panel background rather than this. */
.life-detail { position: relative; overflow: hidden; }
.life-art {
  position: absolute;
  right: 14px;
  bottom: 10px;
  pointer-events: none;
  opacity: .14;
  transform: scale(1.5);
  transform-origin: bottom right;
  z-index: 0;
}
:root[data-theme="dark"] .life-art { opacity: .18; }
/* The panel's own content sits above it. */
.life-detail > .choice-block { position: relative; z-index: 1; }

@media (prefers-reduced-motion: no-preference) {
  .life-art { transition: opacity .25s ease; }
}
/* Not worth the room on a narrow screen, where the panel is already tight. */
@media (max-width: 760px) { .life-art { display: none; } }

/* ---- somebody arriving, decided but not yet lived ----
   Dashed rather than solid, which is the oldest and clearest way to draw a
   thing that is not there yet, and a tag saying when. It has no closeness
   bar because it has no standing: nothing has happened between you. */
.person.pending {
  border-style: dashed;
  background: var(--surface-2);
}
.person .p-tag {
  display: inline-block;
  margin-left: 8px;
  font-size: .6rem; font-weight: 800;
  text-transform: uppercase; letter-spacing: .07em;
  color: var(--primary-600);
  background: var(--primary-100);
  border-radius: 2px;
  padding: 2px 6px;
  vertical-align: middle;
}

/* ============================================================
   THE TOUR

   A first-run walk through the turn screen.

   NOT BUILT ON <dialog>. An open modal dialog makes the rest of
   the page inert, and this exists to point AT the page: a student
   should be able to read the thing being described while it is
   being described. So the page stays live throughout and the tour
   is a positioned panel with a hole cut over the target.

   The hole is one element and a very large spread shadow, which is
   the cheapest way to dim everything except a rectangle without
   compositing four separate panels around it.
   ============================================================ */
.tour { position: fixed; inset: 0; z-index: 60; pointer-events: none; }

.tour-cut {
  position: absolute;
  border-radius: var(--radius);
  box-shadow: 0 0 0 9999px rgba(34, 28, 22, .55);
  outline: 2px solid var(--primary);
  outline-offset: 2px;
  transition: top .28s cubic-bezier(.2,.7,.3,1), left .28s cubic-bezier(.2,.7,.3,1),
              width .28s cubic-bezier(.2,.7,.3,1), height .28s cubic-bezier(.2,.7,.3,1);
}
:root[data-theme="dark"] .tour-cut { box-shadow: 0 0 0 9999px rgba(0, 0, 0, .68); }

.tour-box {
  position: absolute;
  width: min(360px, calc(100vw - 32px));
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: var(--radius);
  box-shadow: var(--shadow-lg);
  padding: 18px 20px;
  pointer-events: auto;
  transition: top .28s cubic-bezier(.2,.7,.3,1), left .28s cubic-bezier(.2,.7,.3,1);
}
.tour-box h3 { font-size: 1.12rem; margin: 4px 0 0; }
.tour-box p { font-size: .92rem; line-height: 1.6; color: var(--ink-soft); margin: 8px 0 0; }
.tour-step {
  font-size: .64rem; font-weight: 800; text-transform: uppercase;
  letter-spacing: .07em; color: var(--ink-faint);
}
.tour-acts {
  display: flex; gap: 10px; align-items: center; margin-top: 16px;
}
.tour-acts .btn-primary { margin-left: auto; }

@media (prefers-reduced-motion: reduce) {
  .tour-cut, .tour-box { transition: none; }
}

/* ============================================================
   THE MARK

   Replaces the rounded tile with a "C" in it, which was the last
   piece of the borrowed design language still standing and read as
   an app icon on a page that had stopped being an app.

   IT IS A FORK WHERE ONE BRANCH CURVES UP. Two paths from one
   point, which is the name and the subject, and the one that bends
   is the compounding curve every module is building toward. Two
   ideas in three strokes, and it means something rather than
   decorating.

   DRAWN AS A MASK, NOT AN IMAGE. The shape is a data URI used as a
   mask and filled with a token, so it takes --primary and flips
   with the theme on its own. An <img> would need two files and a
   swap, and inline SVG would need editing in all fifteen page
   headers. This is one rule.

   BUILT FOR 16px FIRST, because the favicon is the harsh
   constraint and the header is the easy one. Three strokes, wide
   apart, no closed counters, nothing that fills in. Thick relative
   to the box: 3.4 units in a 24 unit viewBox, which is about two
   pixels of ink at favicon size.
   ============================================================ */
.brand .mark,
.brand-mark {
  /* The letter goes. The element stays, so no page markup changes. */
  color: transparent;
  font-size: 0;
  background: var(--primary);
  border-radius: 0;
  box-shadow: none;
  width: 26px;
  height: 26px;
  -webkit-mask-image: url("data:image/svg+xml,%3Csvg%20xmlns='http://www.w3.org/2000/svg'%20viewBox='0%200%2024%2024'%20fill='none'%20stroke='black'%20stroke-width='3.4'%20stroke-linecap='round'%20stroke-linejoin='round'%3E%3Cpath%20d='M2.6%2022H17'%20stroke-width='1.9'%20opacity='.4'/%3E%3Cpath%20d='M3%2019C11%2019%2014%2015.5%2017%2010C19%206.5%2020%204.5%2021%203'/%3E%3C/svg%3E");
          mask-image: url("data:image/svg+xml,%3Csvg%20xmlns='http://www.w3.org/2000/svg'%20viewBox='0%200%2024%2024'%20fill='none'%20stroke='black'%20stroke-width='3.4'%20stroke-linecap='round'%20stroke-linejoin='round'%3E%3Cpath%20d='M2.6%2022H17'%20stroke-width='1.9'%20opacity='.4'/%3E%3Cpath%20d='M3%2019C11%2019%2014%2015.5%2017%2010C19%206.5%2020%204.5%2021%203'/%3E%3C/svg%3E");
  -webkit-mask-repeat: no-repeat; mask-repeat: no-repeat;
  -webkit-mask-position: center; mask-position: center;
  -webkit-mask-size: contain; mask-size: contain;
}

/* In-run the masthead shrinks, and so does this. */
:root.in-run .brand .mark,
:root.in-run .brand-mark { width: 21px; height: 21px; box-shadow: none; }

/* A browser with no mask support gets a plain blue square rather than an
   invisible one, which is a worse logo and not a broken header. */
@supports not ((-webkit-mask-image: url("")) or (mask-image: url(""))) {
  .brand .mark, .brand-mark {
    color: var(--primary-fg);
    font-size: .95rem;
    border-radius: 2px;
  }
}

/* ============================================================
   FOLDS

   The money tab had grown to 1,405px against an 820px viewport,
   so the one control a player acts on, the allocation slider, was
   below the fold on every school laptop in the room.

   Built on <details>, which is keyboard operable, announced
   correctly, and opens itself when somebody uses find-in-page.
   A div with a click handler gets none of that for free.

   A CLOSED FOLD STILL SHOWS ITS NUMBER. The summary carries the
   total on the right, so folding hides the detail and never the
   figure. A fold that hides the amount as well is just a thing to
   click.
   ============================================================ */
.fold {
  border-top: 1px solid var(--line);
  margin: 4px 0 0;
}
.fold > summary {
  display: flex;
  align-items: baseline;
  gap: 12px;
  /* 44px tall in total. Every other control in this simulation is sized to
     that, and a summary is a control. */
  padding: 11px 0;
  min-height: 44px;
  box-sizing: border-box;
  cursor: pointer;
  list-style: none;
  font-size: .95rem;
}
/* Both spellings. Chromium moved from the -webkit- pseudo to ::marker and
   dropping either one leaves a stray triangle in some browser. */
.fold > summary::-webkit-details-marker { display: none; }
.fold > summary::marker { content: ""; }
/* The marker is drawn rather than inherited, because the native one differs
   between browsers and sits at a different baseline in each. */
.fold > summary::before {
  content: "";
  flex: 0 0 auto;
  width: 7px; height: 7px;
  border-right: 2px solid var(--ink-faint);
  border-bottom: 2px solid var(--ink-faint);
  transform: rotate(-45deg);
  transition: transform .18s ease;
  position: relative;
  top: -2px;
}
.fold[open] > summary::before { transform: rotate(45deg); top: -4px; }
.fold > summary:hover::before { border-color: var(--ink); }
.fold .f-k { flex: 1 1 auto; }
.fold .f-v {
  flex: 0 0 auto;
  font-variant-numeric: tabular-nums;
  color: var(--ink-soft);
}
.fold .f-note { margin: 0 0 8px 19px; }
.fold .f-body { padding: 0 0 8px 19px; }
.fold > summary:focus-visible {
  outline: 2px solid var(--primary);
  outline-offset: 2px;
  border-radius: var(--radius-sm);
}
@media (prefers-reduced-motion: reduce) {
  .fold > summary::before { transition: none; }
}

/* ============================================================
   THE END SCREEN FOLD.

   Same idea as .fold above and deliberately not the same size.
   .fold is an inline disclosure inside a card; this is the seam
   between "what happened to you" and "here is the analysis", and
   it is the most important boundary on the page. It has to read as
   a place the page changes subject, not as one more toggle.

   So: a rule across the full width, a label at heading weight, and
   a second line naming what is inside, because "show me all of it"
   alone does not tell you whether it is worth opening.
   ============================================================ */
.end-more {
  border-top: 2px solid var(--line);
  margin: 34px 0 0;
}
.end-more > summary {
  display: flex;
  flex-direction: column;
  gap: 3px;
  /* Matches every other control in the simulation. */
  padding: 15px 0 15px 26px;
  min-height: 44px;
  box-sizing: border-box;
  cursor: pointer;
  list-style: none;
  position: relative;
}
.end-more > summary::-webkit-details-marker { display: none; }
.end-more > summary::marker { content: ""; }
.end-more > summary::before {
  content: "";
  position: absolute;
  left: 4px;
  top: 22px;
  width: 9px; height: 9px;
  border-right: 2px solid var(--ink-faint);
  border-bottom: 2px solid var(--ink-faint);
  transform: rotate(-45deg);
  transition: transform .18s ease;
}
.end-more[open] > summary::before { transform: rotate(45deg); top: 19px; }
.end-more > summary:hover::before { border-color: var(--ink); }
.end-more > summary:focus-visible {
  outline: 2px solid var(--primary);
  outline-offset: 3px;
  border-radius: var(--radius-sm);
}
.end-more .em-label {
  font-size: 1.05rem;
  font-weight: 650;
  color: var(--ink);
}
.end-more .em-sub {
  font-size: .88rem;
  color: var(--ink-soft);
  line-height: 1.45;
}
/* One label element holding both words, so the summary does not change
   height when it is toggled and the page does not jump under the cursor. */
.end-more .em-shut { display: none; }
.end-more[open] .em-open { display: none; }
.end-more[open] .em-shut { display: inline; }
/* The sub-line stays in both states, and the first draft of this hid it
   once open on the grounds that you can now see what is inside. Measured:
   that made the summary 55px open and 79px shut, so the control moved 24px
   under the cursor that had just clicked it, and a second click landed
   somewhere else. The rule above exists precisely to stop that, and hiding
   the sub-line put it straight back. Redundant text is the cheaper problem,
   and once the fold is open the line reads as a label for what follows. */
.end-more .end-more-body > .module-section:first-child { margin-top: 4px; }

@media (prefers-reduced-motion: reduce) {
  .end-more > summary::before { transition: none; }
}

/* ============================================================
   THE SETUP FOLD.

   Same control as .end-more, one step quieter, and shut rather
   than open by default. The end screen's fold hides analysis of
   something already finished, so open is the safe default there.
   This one hides three pickers that are already set to something
   workable and are changeable on any turn, so shut is the safe
   default: opening it is a choice to fiddle, and a player who
   never opens it has lost nothing.
   ============================================================ */
.setup-later {
  border-top: 1px solid var(--line);
  margin: 10px 0 0;
}
.setup-later > summary {
  display: flex;
  flex-direction: column;
  gap: 2px;
  padding: 13px 0 13px 26px;
  min-height: 44px;
  box-sizing: border-box;
  cursor: pointer;
  list-style: none;
  position: relative;
}
.setup-later > summary::-webkit-details-marker { display: none; }
.setup-later > summary::marker { content: ""; }
.setup-later > summary::before {
  content: "";
  position: absolute;
  left: 4px;
  top: 20px;
  width: 8px; height: 8px;
  border-right: 2px solid var(--ink-faint);
  border-bottom: 2px solid var(--ink-faint);
  transform: rotate(-45deg);
  transition: transform .18s ease;
}
.setup-later[open] > summary::before { transform: rotate(45deg); top: 17px; }
.setup-later > summary:hover::before { border-color: var(--ink); }
.setup-later > summary:focus-visible {
  outline: 2px solid var(--primary);
  outline-offset: 3px;
  border-radius: var(--radius-sm);
}
.setup-later .sl-label {
  font-size: 1rem;
  font-weight: 600;
  color: var(--ink);
}
.setup-later .sl-sub {
  font-size: .85rem;
  color: var(--ink-soft);
}
/* Both lines stay in both states, for the reason written against
   .end-more: hiding one of them moves the control out from under the
   cursor that just clicked it. */
.setup-later .setup-later-body > .module-section:first-child { margin-top: 2px; }

@media (prefers-reduced-motion: reduce) {
  .setup-later > summary::before { transition: none; }
}

/* ============================================================
   THE YEAR'S RECEIPT, on the turn screen.

   Third fold in the project and the smallest, because it sits
   inside a card that already has its own padding and border. The
   summary therefore carries no border of its own and reuses the
   card's existing .kind styling for the label, so a closed panel
   looks like the heading it replaced rather than like a new
   control that has appeared.
   ============================================================ */
.recap-fold { padding-top: 10px; padding-bottom: 10px; }
.recap-fold > summary {
  display: flex;
  align-items: baseline;
  gap: 10px;
  cursor: pointer;
  list-style: none;
  padding: 4px 0 4px 20px;
  min-height: 36px;
  box-sizing: border-box;
  position: relative;
}
.recap-fold > summary::-webkit-details-marker { display: none; }
.recap-fold > summary::marker { content: ""; }
.recap-fold > summary::before {
  content: "";
  position: absolute;
  left: 2px;
  top: 12px;
  width: 7px; height: 7px;
  border-right: 2px solid var(--ink-faint);
  border-bottom: 2px solid var(--ink-faint);
  transform: rotate(-45deg);
  transition: transform .18s ease;
}
.recap-fold[open] > summary::before { transform: rotate(45deg); top: 9px; }
.recap-fold > summary:hover::before { border-color: var(--ink); }
.recap-fold > summary:focus-visible {
  outline: 2px solid var(--primary);
  outline-offset: 2px;
  border-radius: var(--radius-sm);
}
.recap-fold .kind { flex: 0 0 auto; }
/* The count sits at the far end and is the reason a closed panel is
   readable: it says whether there is one line behind it or eleven. */
.recap-fold .rf-hint {
  margin-left: auto;
  font-size: .78rem;
  color: var(--ink-soft);
  font-variant-numeric: tabular-nums;
}
.recap-fold #t-recap-body { margin-top: 8px; }

@media (prefers-reduced-motion: reduce) {
  .recap-fold > summary::before { transition: none; }
}

/* ============================================================
   THE FOUR-POINT SCALE, on the teacher page.

   A number in its own column so a teacher scanning for "which
   band is this" finds it without reading the description first,
   which is what a scale is for when it is being used rather than
   read.
   ============================================================ */
.scale-row {
  display: grid;
  grid-template-columns: 42px 1fr;
  gap: 14px;
  align-items: start;
  padding: 12px 0;
  border-top: 1px solid var(--line-soft);
  line-height: 1.55;
}
.scale-row:first-child { border-top: 0; padding-top: 2px; }
.scale-n {
  font-size: 1.5rem;
  font-weight: 800;
  color: var(--primary);
  text-align: center;
  font-variant-numeric: tabular-nums;
  line-height: 1.2;
}
@media (max-width: 460px) {
  .scale-row { grid-template-columns: 30px 1fr; gap: 10px; }
  .scale-n { font-size: 1.2rem; }
}

/* Print-only content, hidden on screen. The @media print block below
   turns these on. Kept out of the accessibility tree on screen too, so
   a screen reader is not read the printed title block on top of the
   page that already says the same thing. */
.print-only { display: none; }
.answer-print { display: none; }

/* ============================================================
   PRINT

   Ctrl+P into "Save as PDF" is the only document this project can
   produce. There is no build step, no server and no PDF library,
   so the browser's print engine IS the export and everything below
   is its configuration.

   THREE THINGS ARE PRINTABLE, and nothing else is claimed:
     - the end screen, as a student's hand-in
     - the teacher page, as something to have on the desk
     - a turn, as a snapshot of a budget partway through a life

   The module pages are deliberately not done. They are full of
   calculators, sliders and canvas charts, and each one needs its
   own answer about what a printed version even means. Printing one
   will produce readable prose and suppressed widgets, which is
   honest, rather than a worksheet, which would need designing.
   ============================================================ */
@media print {

  /* ---- paper, not screen ----
     Dark mode prints as a solid dark rectangle: unreadable, and on a
     school printer it empties a toner cartridge over a class set. The
     tokens are forced to their light values for the duration. */
  :root, :root[data-theme="dark"] {
    --bg: #ffffff;
    --surface: #ffffff;
    --surface-2: #f4f1ea;
    --card: #ffffff;
    --ink: #16120e;
    --ink-soft: #3d352c;
    --ink-faint: #5d5449;
    --line: #b9b0a2;
    --line-soft: #d8d1c5;
    --primary: #1b4a6b;
    --primary-100: #dbe6ee;
    --amber: #8a6410;
    --danger: #8f2020;
    --good: #1f5d3a;
  }

  html, body {
    background: #fff !important;
    color: #16120e !important;
  }

  @page { margin: 14mm 12mm; }

  /* ---- the chrome goes ----
     Navigation, theme switching and anything that is a control rather
     than content. A printed button is an empty rounded box. */
  .site-header,
  .site-footer,
  .skip-link,
  #theme-toggle,
  .tour,
  button,
  .btn-primary,
  .btn-quiet,
  input[type="file"],
  dialog,
  .sim-tabs,
  .i-stage-wrap {
    display: none !important;
  }

  /* ---- nothing is stuck to the viewport on paper ----
     A sticky header repeats on every sheet or lands in the middle of
     one. Fixed and sticky both become static. */
  .site-header, .sim-frame, .stage-named, .who-name, .stage-name {
    position: static !important;
  }

  /* ---- open what the reader cannot open ----
     A collapsed fold prints collapsed, so the budget breakdown, which
     is the most useful thing on the money tab, would simply be absent.
     Paper has no disclosure triangle to press. */
  details, details.fold { display: block !important; }
  details > summary { list-style: none; font-weight: 600; }
  details:not([open]) > *:not(summary) { display: revert !important; }
  .fold > summary::before { display: none !important; }
  .end-more > summary::before { display: none !important; }
  .setup-later > summary::before { display: none !important; }
  .recap-fold > summary::before { display: none !important; }
  .f-body { display: block !important; height: auto !important; }
  /* The end screen fold prints as a plain heading. The rule above already
     reveals a closed details on paper, so a student who shut the analysis
     still hands in all of it. The two-word toggle label would print as
     "Show me all of it" over the thing itself, so the label goes and
     the sub-line, which names what follows, stays. */
  .end-more { border-top: 1px solid #999 !important; }
  .end-more .em-label { display: none !important; }
  .end-more .em-sub { display: block !important; font-weight: 600; }

  /* ---- things that scroll on screen have to fit on paper ---- */
  .worked-wrap,
  .stage-shelf,
  #e-tree,
  .tree-wrap,
  [style*="overflow"] {
    overflow: visible !important;
    max-height: none !important;
  }
  svg, canvas, img { max-width: 100% !important; height: auto !important; }

  /* ---- do not break a thought across two sheets ---- */
  .card, .panel, .callout, .assumption, .headline-card,
  .life-summary, .record-list li, tr, .obj-cell, .sim-continue {
    break-inside: avoid;
    page-break-inside: avoid;
  }
  h1, h2, h3 { break-after: avoid; page-break-after: avoid; }
  table { break-inside: auto; }
  thead { display: table-header-group; }

  /* Links are content here, not destinations to be typed out. Printing
     every href would double the length of the teacher page for nothing:
     this is a hand-in, not a bibliography. */
  a { color: inherit !important; text-decoration: none !important; }

  /* ---- the printed title block ----
     Screen-hidden, print-only. A teacher collecting twenty eight of
     these needs the name and the life number on the sheet, and the
     screen already shows both in places that are not printed. */
  .print-only { display: block !important; }
  .print-head {
    border-bottom: 2px solid #16120e;
    padding-bottom: 8px;
    margin-bottom: 18px;
  }
  .print-head .ph-title { font-weight: 700; font-size: 12pt; }
  .print-head .ph-meta { font-size: 10pt; color: #3d352c; margin-top: 3px; }

  /* ---- typed answers ----
     A textarea prints only the part of itself that happens to be
     scrolled into view and will not grow to its content, so a long
     answer is cut off mid-sentence. On a hand-in that is the marked
     work. Each one is mirrored into a plain block that prints instead.

     Scoped to the assignment. A blanket rule on every textarea would
     take the teacher page's class-code box with it and leave its label
     sitting over nothing. */
  #e-assignment textarea { display: none !important; }
  #e-assignment .answer-print {
    display: block !important;
    white-space: pre-wrap;
    border: 1px solid #b9b0a2;
    border-radius: 3px;
    padding: 8px 10px;
    min-height: 2.4em;
    font-size: 10.5pt;
  }
  .answer-print:empty::before {
    content: "(left blank)";
    color: #5d5449;
    font-style: italic;
  }

  /* ---- paper spacing, not screen spacing ----
     The screen layout is generous on purpose: it is read one section at a
     time on a bright display. On paper that generosity is whole sheets of
     white, and measured it was the difference between a three and a half
     page hand-in and a two and a half page one. Nothing is dropped here,
     it is only set at the density print actually wants. */
  body { font-size: 10.5pt; line-height: 1.4; }
  .hero { padding: 0 0 8px !important; }
  .hero h1 { font-size: 17pt; margin: 0 0 4px !important; }
  .hero .lede { font-size: 10.5pt; margin: 0 !important; }
  .eyebrow { font-size: 8.5pt; }
  .module-container { max-width: none !important; padding: 0 !important; }
  .module-section { margin: 0 0 12px !important; padding: 0 !important; border: 0 !important; }
  h2 { font-size: 12.5pt; margin: 0 0 5px !important; }
  h3 { font-size: 11pt; margin: 0 0 4px !important; }
  p { margin: 0 0 7px; }
  .card, .panel { padding: 9px 11px !important; margin: 0 0 8px !important; }
  .assumption, .callout { padding: 8px 10px !important; margin: 6px 0 !important; font-size: 9.5pt; }
  .note-small, .blurb { font-size: 9.5pt; }
  .field { margin-bottom: 10px !important; }
  label { font-size: 10pt; }
}

/* ---- THE END SCREEN, as a hand-in ----
   Two to three sheets, chosen so that everything printed is something
   a teacher marks: how it ended, who they were, the path they took,
   how that compares, and what they wrote about it.

   Dropped on purpose: the panorama and the wrapped cards, which are
   both a reading experience rather than a record; the luck breakdown
   and the took-and-missed lists, which are long and are argued about
   in the room rather than marked. All five are still on the screen. */
@media print {
  /* Targeted by aria-labelledby rather than by id, because these
     sections have no ids of their own and the label is what actually
     names each one. Adding ids purely to hang a print rule on would put
     the reason for them in a different file from the rule. */
  #e-panorama-wrap,
  #e-wrapped-wrap,
  #screen-end [aria-labelledby="luck"],
  #screen-end [aria-labelledby="record"],
  #screen-end [aria-labelledby="handin"],
  #screen-end [aria-labelledby="again"] { display: none !important; }

  #screen-end .sim-status { break-inside: avoid; }
  #e-tree { max-width: 100% !important; }
  #e-treemark { display: block !important; opacity: .5; }
}

/* ---- THE TEACHER PAGE, as a desk reference ----
   The class reader is a paste box and three buttons, which are no use on
   paper. What it produces is: a teacher who has read the class and wants
   the summary table beside them is one of the better reasons to print this
   page, so the tool goes and its output stays. */
@media print {
  #tc-tool { display: none !important; }
  #tc-out { display: block !important; }
  #tc-out .card { overflow-x: visible !important; }
}

/* ---- A TURN, as a snapshot ----
   All four tabs at once, because on screen only one panel is shown and
   the other three would print as nothing. The stage prints with it: the
   scene is the fastest statement of where a life had got to. */
@media print {
  #screen-turn .sim-tabs { display: none !important; }
  #panel-year, #panel-money, #panel-life, #panel-people {
    display: block !important;
    break-before: auto;
  }
  #panel-money, #panel-life, #panel-people { margin-top: 18px; }
  .turn-rest { display: revert !important; }
  #t-stage { overflow: visible !important; break-inside: avoid; }
}
/* ============================================================
   THE TURN SCREEN'S VERTICAL BUDGET, 2026-08-06.

   REPORTED: "you still have to scroll up and down a lot... the
   wellbeing bar could be attached to the money figures to save
   space, there doesn't need to be a gap."

   The money strip and the wellbeing card were two separate cards
   with a full gap between them, and they are one reading: what you
   have, and how it feels. Joined into a single block, so the seam
   between them is a rule rather than a margin, and the decisions
   below start higher up the page on every one of thirty turns.

   `.invested` marks the cell promoted out of the strip in July and
   returned to it today. It keeps the emphasis, at the weight of a
   cell rather than a band.
   ============================================================ */
.sim-status + .band-card {
  margin-top: 0;
  border-top-left-radius: 0;
  border-top-right-radius: 0;
  border-top: 1px solid var(--line-soft);
}
.sim-status:has(+ .band-card) {
  margin-bottom: 0;
  border-bottom-left-radius: 0;
  border-bottom-right-radius: 0;
}
.sim-status .v.invested { color: var(--money); font-weight: 700; }
/* ============================================================
   THE PRINTABLE ANSWER KEYS, 2026-08-06.

   Built to be printed rather than read on screen, so the rules
   here are paper rules: one part per sheet, answers findable by
   scanning down a margin, and nothing that only makes sense in
   color.
   ============================================================ */
.tk-status { font-size: .85rem; color: var(--ink-soft); margin: 4px 0 18px; }
.tk-status.bad { color: var(--danger, #b3261e); font-weight: 600; }

.tk-part { margin: 30px 0 0; padding-top: 22px; border-top: 2px solid var(--line); }
.tk-part h2 { margin: 0 0 4px; font-size: 1.25rem; }
.tk-part h2 .tk-sub {
  display: block; font-size: .9rem; font-weight: 400; color: var(--ink-soft);
}
.tk-part h3 {
  margin: 20px 0 8px; font-size: .78rem; text-transform: uppercase;
  letter-spacing: .06em; color: var(--ink-faint);
}
.tk-list { margin: 0; padding-left: 26px; }
.tk-list > li { margin: 0 0 16px; break-inside: avoid; }
.tk-q { font-weight: 650; }
.tk-p { font-size: .88rem; color: var(--ink-soft); margin: 3px 0 5px; }
.tk-opts { margin: 4px 0 6px; padding-left: 18px; font-size: .88rem; }
.tk-opts .on { font-weight: 700; }
.tk-opts .on::after { content: "  <- correct"; color: var(--ink-faint); font-weight: 400; }
.tk-a { font-variant-numeric: tabular-nums; }
.tk-w { font-size: .86rem; color: var(--ink-soft); margin-top: 3px; }
.tk-none { font-size: .9rem; color: var(--ink-soft); }

@media print {
  .no-print { display: none !important; }
  .site-header, .site-footer, .hero .lede { display: none !important; }
  .tk-part { break-before: page; border-top: 0; }
  .tk-part:first-child { break-before: auto; }
}
/* ============================================================
   THE SLIDES, 2026-08-06.

   Built to be projected, so the rules are projection rules: one
   idea per screen, the headline large enough to read from the
   back, and the speaker note hidden until the teacher asks for
   it. Nothing here depends on color to carry meaning, because a
   classroom projector will not reproduce it.
   ============================================================ */
.sl-stage-wrap {
  max-width: 1000px;
  margin: 0 auto;
  padding: 0 20px;
}
.slide {
  min-height: 46vh;
  display: flex;
  flex-direction: column;
  justify-content: center;
  padding: 40px 0;
  border-bottom: 1px solid var(--line-soft);
}
.sl-k {
  font-size: .8rem;
  text-transform: uppercase;
  letter-spacing: .08em;
  color: var(--ink-faint);
  margin-bottom: 14px;
}
.slide h2 {
  font-size: clamp(1.8rem, 5vw, 3.2rem);
  line-height: 1.12;
  margin: 0 0 18px;
  font-variant-numeric: tabular-nums;
}
.sl-b {
  font-size: clamp(1rem, 2.2vw, 1.35rem);
  line-height: 1.5;
  color: var(--ink-soft);
  max-width: 44ch;
  margin: 0;
}
/* The note is the teacher's, and it does not project unless asked for. */
.sl-n {
  display: none;
  margin-top: 26px;
  padding: 14px 16px;
  border-left: 3px solid var(--primary);
  background: var(--surface-2, transparent);
  font-size: .92rem;
  line-height: 1.55;
  color: var(--ink-soft);
  max-width: 62ch;
}
body.notes-on .sl-n { display: block; }

.sl-bar {
  position: sticky;
  bottom: 0;
  display: flex;
  align-items: center;
  gap: 10px;
  flex-wrap: wrap;
  max-width: 1000px;
  margin: 0 auto;
  padding: 12px 20px;
  border-top: 1px solid var(--line);
  background: var(--bg);
}
.sl-count {
  font-size: .85rem;
  color: var(--ink-soft);
  font-variant-numeric: tabular-nums;
  min-width: 6.5em;
  text-align: center;
}
.sl-spacer { flex: 1 1 auto; }

.sl-all { display: none; }

@media print {
  .sl-chrome, .sl-bar, .no-print { display: none !important; }
  .sl-stage-wrap { display: none !important; }
  .sl-all { display: block !important; }
  .sl-all .slide {
    break-after: page;
    min-height: 0;
    border-bottom: 0;
    padding: 0 0 24px;
  }
  .sl-all .slide h2 { font-size: 2rem; }
  /* Printing is the handout, so the notes always come with it. */
  .sl-all .sl-n { display: block !important; }
}