/* ==========================================================================
   provitly.css  .  Provitly UI contract v1.2 (craft round, 2026-10-27)
   --------------------------------------------------------------------------
   THE COPY LAW: this file is copied byte-for-byte into every Provitly page.
   Never edit a copy. Fixes land in ui-contract/provitly.css first, then get
   re-copied out. A page that needs a style this file lacks keeps it in its
   own page.css and asks for it to be added here.

   Identity: "Registrar ink" + "Hallmark gold" (see UI-BOOK.md, section 2).
   Ink carries every action and the words. Gold appears only where the page
   PROVES something: the mark, and the working under an answer.

   MOTION VOCABULARY (the whole app speaks these and nothing else)
     --dur-press  150ms  press feedback
     --dur-quick  150ms  exits, small feedback
     --dur-view   200ms  answer settling, unit picker pill, the one count-up
                         (first answer of a visit only),
                         and the wait before the save prompt rises
     --dur-enter  220ms  save prompt, toast, error note entering
     --dur-sheet  250ms  the one big surface (bottom sheet)
     --ease-out     every entrance and every piece of feedback
     --ease-in-out  something moving while it stays on screen
     --ease-drawer  the bottom sheet only
   Only transform and opacity animate (colours change instantly).
   Exits are faster than entrances. Nothing bounces. Two things never move
   at once. prefers-reduced-motion keeps the fades, drops the travel and
   the count-up (see section 17).
   ========================================================================== */

/* ---------- 1. Font (self-hosted; no CDN at runtime) ---------------------- */
/* Atkinson Hyperlegible Next + Mono (SIL Open Font License), made by the
   Braille Institute so 0/O, 1/l/I and 6/9 cannot be mistaken for each other.
   The right face for a page whose whole job is a number you can trust.
   The files live in ui-contract/fonts/ (variable woff2, weights 200-800,
   licence in fonts/OFL.txt) and are copied beside this file: the url() is
   relative to this css. local() comes first so an installed copy costs no
   download. */
@font-face {
  font-family: "Provitly Sans";
  src: local("Atkinson Hyperlegible Next"),
       url("fonts/AtkinsonHyperlegibleNext-Variable.woff2") format("woff2");
  font-weight: 200 800;
  font-style: normal;
  font-display: swap;
}
@font-face {
  font-family: "Provitly Mono";
  src: local("Atkinson Hyperlegible Mono"),
       url("fonts/AtkinsonHyperlegibleMono-Variable.woff2") format("woff2");
  font-weight: 200 800;
  font-style: normal;
  font-display: swap;
}
/* Fallbacks shaped like the real face, so text does not jump when it swaps
   in. Numbers are measured from the font files (units per em 1000, ascent
   984, descent 316, line gap 0; mean advance 0.504 em for Next, 0.632 em for
   Mono) against Arial (0.502 em) and Courier New (0.600 em):
     size-adjust = real width / fallback width
     ascent/descent-override = real metric / size-adjust */
@font-face {
  font-family: "Provitly Sans Fallback";
  src: local("Arial"), local("ArialMT"), local("Helvetica Neue"), local("Helvetica");
  size-adjust: 100.4%;
  ascent-override: 98%;
  descent-override: 31.5%;
  line-gap-override: 0%;
}
@font-face {
  font-family: "Provitly Mono Fallback";
  src: local("Courier New"), local("CourierNewPSMT"), local("Menlo");
  size-adjust: 105.3%;
  ascent-override: 93.4%;
  descent-override: 30%;
  line-gap-override: 0%;
}

/* ---------- 2. Tokens ------------------------------------------------------ */
:root {
  color-scheme: light dark;

  /* The pair */
  --ink:        #1F416E;   /* Registrar ink: actions, links, focus (9.9:1 on white) */
  --ink-press:  #173357;
  --ink-soft:   #E6EDF6;   /* ink wash: selected states */
  --on-ink:     #FFFFFF;
  --gold:       #A97416;   /* Hallmark gold: marks and proof only, never body text */
  --gold-wash:  #FBF3E2;
  --gold-line:  #E2C68E;
  --on-gold:    #5E3F07;   /* text on gold-wash */

  /* Neutrals, biased toward the ink's blue */
  --paper:      #F5F7FA;   /* page */
  --surface:    #FFFFFF;   /* cards, fields, sheet */
  --sunk:       #EBEFF4;   /* unit picker track */
  --line:       #D6DCE5;
  --line-strong:#9AA6B6;   /* field borders: 3:1 against white */
  --text:       #111A27;
  --muted:      #4D5A6C;
  --faint:      #66727F;   /* fine print only */
  --hint:       #8A95A3;   /* placeholders only: about 3:1 on --surface, so an
                              example reads as a hint, never as a typed value */

  /* Signals */
  --error:      #A8261D;
  --error-wash: #FCEDEB;

  --scrim:      rgb(10 16 26 / 0.44);
  --shadow-1:   0 1px 2px rgb(17 26 39 / 0.06);
  --shadow-sheet: 0 -8px 32px rgb(17 26 39 / 0.16);
  --mark-plate: #1F416E;

  /* Type */
  --font: "Provitly Sans", "Provitly Sans Fallback", system-ui, -apple-system,
          "Segoe UI", Roboto, sans-serif;
  --font-mono: "Provitly Mono", "Provitly Mono Fallback", ui-monospace,
          "SF Mono", "Cascadia Mono", Consolas, monospace;

  /* Type scale (rem; 1rem = 16px). Tracking and leading tighten as size grows.
     The big number is FITTED, not stepped: its size is the row's width
     divided by how wide the number is (see section 8, FIT LAW). These tokens
     are its limits. */
  --t-answer: 3rem;          /* fallback only, for a browser without cqi */
  --t-answer-max: min(13rem, 40svh);  /* never taller than this (landscape) */
  --t-answer-floor: 2.25rem; /* below this the unit drops under the number */
  --answer-unit-room: 3.6rem;/* room kept beside the number for its unit
                                ("tons" at --t-answer-unit plus the gap).
                                A page with a longer main unit sets its own. */
  --t-answer-unit: 1.5rem;
  --t-stat:   1.375rem;    /* secondary numbers under the answer */
  --t-title:  1.625rem;    /* page title (h1) */
  --t-head:   1.125rem;    /* header page name, row names */
  --t-body:   1.0625rem;   /* 17px body */
  --t-field:  1.25rem;     /* 20px inputs: always >= 16px so iOS never zooms */
  --t-small:  0.9375rem;   /* 15px */
  --t-tiny:   0.8125rem;   /* 13px: chips and fine print only */

  /* Spacing (rem, 4px grid) */
  --s-1: 0.25rem; --s-2: 0.5rem;  --s-3: 0.75rem; --s-4: 1rem;
  --s-5: 1.25rem; --s-6: 1.5rem;  --s-8: 2rem;    --s-10: 2.5rem;
  --s-12: 3rem;

  --gutter: 1rem;          /* page side gutter at phone width */
  --page-max: 62rem;
  --col-max: 34rem;
  --tap: 44px;             /* minimum tap target, every control */
  --header-h: 3.5rem;

  --r-field: 10px;
  --r-card: 14px;
  --r-pill: 999px;
  --r-sheet: 20px;

  /* Motion */
  --dur-press: 150ms;
  --dur-quick: 150ms;
  --dur-view:  200ms;
  --dur-enter: 220ms;
  --dur-sheet: 250ms;
  --ease-out:    cubic-bezier(0.23, 1, 0.32, 1);
  --ease-in-out: cubic-bezier(0.77, 0, 0.175, 1);
  --ease-drawer: cubic-bezier(0.32, 0.72, 0, 1);
  --rise: 6px;             /* how far things travel when they enter */

  /* Safe areas */
  --safe-t: env(safe-area-inset-top, 0px);
  --safe-r: env(safe-area-inset-right, 0px);
  --safe-b: env(safe-area-inset-bottom, 0px);
  --safe-l: env(safe-area-inset-left, 0px);
}

/* Dark: the same pair, re-mixed for a screen read at night.
   Kept in two blocks with identical values: one for the system setting,
   one for a page that forces [data-theme="dark"]. Change both or neither. */
@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) {
    --ink: #9DC0F0; --ink-press: #B8D2F5; --ink-soft: #1C2B42; --on-ink: #0B1220;
    --gold: #E2B357; --gold-wash: #241D11; --gold-line: #5C4A22; --on-gold: #F0D399;
    --paper: #0C121B; --surface: #141C28; --sunk: #0E151F;
    --line: #253142; --line-strong: #4A5A70;
    --text: #E9EEF5; --muted: #A6B2C3; --faint: #8592A5; --hint: #5E6B7D;
    --error: #FF9C92; --error-wash: #2E1715;
    --scrim: rgb(0 0 0 / 0.6);
    --shadow-1: none;
    --shadow-sheet: 0 -8px 32px rgb(0 0 0 / 0.5);
    --mark-plate: #1C2B42;
  }
}
:root[data-theme="dark"] {
  color-scheme: dark;
  --ink: #9DC0F0; --ink-press: #B8D2F5; --ink-soft: #1C2B42; --on-ink: #0B1220;
  --gold: #E2B357; --gold-wash: #241D11; --gold-line: #5C4A22; --on-gold: #F0D399;
  --paper: #0C121B; --surface: #141C28; --sunk: #0E151F;
  --line: #253142; --line-strong: #4A5A70;
  --text: #E9EEF5; --muted: #A6B2C3; --faint: #8592A5; --hint: #5E6B7D;
  --error: #FF9C92; --error-wash: #2E1715;
  --scrim: rgb(0 0 0 / 0.6);
  --shadow-1: none;
  --shadow-sheet: 0 -8px 32px rgb(0 0 0 / 0.5);
  --mark-plate: #1C2B42;
}
:root[data-theme="light"] { color-scheme: light; }

@media (prefers-contrast: more) {
  :root { --line: var(--line-strong); --muted: var(--text); --faint: var(--text); --hint: var(--muted); }
}

/* ---------- 3. Base -------------------------------------------------------- */
*, *::before, *::after { box-sizing: border-box; }
html {
  -webkit-text-size-adjust: 100%;
  text-size-adjust: 100%;
  -webkit-tap-highlight-color: transparent;
  background: var(--paper);
}
body {
  margin: 0;
  min-height: 100dvh;
  display: flex;
  flex-direction: column;
  background: var(--paper);
  color: var(--text);
  font-family: var(--font);
  font-size: var(--t-body);
  line-height: 1.5;
  font-variant-numeric: tabular-nums;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  overscroll-behavior-y: none;
  overflow-wrap: break-word;
}
img, svg { display: block; max-width: 100%; }
h1, h2, h3, p { margin: 0; }
h1, h2, h3 { text-wrap: balance; }
p { text-wrap: pretty; }
a { color: var(--ink); text-underline-offset: 0.18em; text-decoration-thickness: 1px; }
button, input, select { font: inherit; color: inherit; }
:focus-visible { outline: 3px solid var(--ink); outline-offset: 2px; }
.visually-hidden {
  position: absolute !important; width: 1px; height: 1px; padding: 0; margin: -1px;
  overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; border: 0;
}

/* Page frame */
.page {
  flex: 1 0 auto;
  width: 100%;
  max-width: var(--page-max);
  margin-inline: auto;
  padding: var(--s-6) calc(var(--gutter) + var(--safe-r)) var(--s-10) calc(var(--gutter) + var(--safe-l));
  min-width: 0;
}
.page-title {
  font-size: var(--t-title);
  line-height: 1.15;
  letter-spacing: -0.02em;
  font-weight: 750;
}
.page-lede { margin-top: var(--s-2); color: var(--muted); max-width: 36em; }

/* ---------- 4. Header ------------------------------------------------------ */
/* The budget at 320px, left to right, and nothing else:
   way-home . page name . "More calculators". */
.header {
  position: sticky;
  top: 0;
  z-index: 10;
  background: var(--paper);
  border-bottom: 1px solid var(--line);
  padding-top: var(--safe-t);
}
.header-row {
  display: flex;
  flex-wrap: wrap;   /* under big text zoom the button drops to its own line instead of running off screen */
  align-items: center;
  gap: var(--s-2);
  min-height: var(--header-h);
  max-width: var(--page-max);
  margin-inline: auto;
  padding-inline: calc(var(--gutter) + var(--safe-l)) calc(var(--gutter) + var(--safe-r));
}
.header-name {
  flex: 1 1 auto;
  min-width: 0;
  font-size: var(--t-head);
  font-weight: 750;
  letter-spacing: -0.005em;
  line-height: 1.2;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
/* The button wraps onto a second line only when it cannot fit on one
   (text zoomed to 200% on a narrow phone); the row wraps first. */
.header .btn { flex: 0 1 auto; min-width: 0; max-width: 100%; white-space: normal; text-align: center; }

/* ---------- 5. Way-home mark ---------------------------------------------- */
.way-home {
  display: inline-flex; align-items: center; gap: 2px; flex: none;
  min-height: var(--tap); min-width: var(--tap);
  padding: 0 4px 0 0; margin-left: -6px;
  color: var(--muted); text-decoration: none; border-radius: 10px;
  touch-action: manipulation; user-select: none; -webkit-user-select: none;
  transition: opacity var(--dur-press) var(--ease-out), transform var(--dur-press) var(--ease-out);
}
.way-home:active { opacity: 0.6; transform: scale(0.98); }
.way-home-chev { width: 18px; height: 18px; }
.way-home-tile { width: 32px; height: 32px; }
.way-home-tile .wh-plate { fill: var(--mark-plate); }
.way-home-tile .wh-rim,
.way-home-tile .wh-tick { stroke: var(--gold); }

/* ---------- 6. Buttons ----------------------------------------------------- */
.btn {
  display: inline-flex; align-items: center; justify-content: center; gap: var(--s-2);
  min-height: var(--tap);
  padding: 0 var(--s-4);
  border: 1px solid transparent;
  border-radius: var(--r-pill);
  font-size: var(--t-small);
  font-weight: 700;
  line-height: 1;
  text-decoration: none;
  white-space: nowrap;
  cursor: pointer;
  touch-action: manipulation;
  user-select: none; -webkit-user-select: none;
  transition: transform var(--dur-press) var(--ease-out);
}
/* PRESS LAW: every button gives way under the finger: scale(0.98) over
   --dur-press, ease-out, no bounce. Colours change instantly. */
.btn:active,
button.chip:active,
.footer-links button:active { transform: scale(0.98); }
.btn svg { width: 18px; height: 18px; flex: none; }
.btn-primary   { background: var(--ink); color: var(--on-ink); }
.btn-primary:active { background: var(--ink-press); }
.btn-secondary { background: var(--surface); color: var(--ink); border-color: var(--line-strong); }
.btn-ghost     { background: transparent; color: var(--ink); }
.btn-ghost:active { background: var(--ink-soft); }
.btn-block { width: 100%; min-height: 3rem; font-size: var(--t-body); }
.btn-icon { width: var(--tap); padding: 0; }
.btn-icon svg { width: 20px; height: 20px; }
.btn[disabled] { opacity: 0.45; cursor: not-allowed; transform: none; }
@media (hover: hover) and (pointer: fine) {
  .btn-primary:hover   { background: var(--ink-press); }
  .btn-secondary:hover { border-color: var(--ink); }
  .btn-ghost:hover     { background: var(--ink-soft); }
  a:hover { text-decoration-thickness: 2px; }
}

/* ---------- 7. Fields: number field + unit picker -------------------------- */
/* STEP LAW: the required boxes are numbered 1, 2, 3 in the order they are
   filled, by a CSS counter (nothing to type in the HTML). Optional boxes sit
   last, carry no number, and are visibly lighter (.field-optional). */
.fields { display: grid; gap: var(--s-5); margin-top: var(--s-6); border: 0; padding: 0; min-width: 0; counter-reset: field; }
.field { display: grid; gap: var(--s-2); min-width: 0; counter-increment: field; }
.field-top {
  display: flex; flex-wrap: wrap; align-items: center; justify-content: space-between; gap: var(--s-2) var(--s-3);
  min-width: 0;
}
.field-label {
  display: inline-flex; align-items: center; gap: var(--s-2);
  font-weight: 750; font-size: var(--t-body); line-height: 1.2;
  letter-spacing: -0.005em;
}
.field-label::before {
  content: counter(field);
  content: counter(field) / "";  /* the number is seen, not read aloud */
  display: inline-grid; place-items: center; flex: none;
  width: 1.5rem; height: 1.5rem; border-radius: 50%;
  background: var(--ink-soft); color: var(--ink);
  font-size: var(--t-tiny); font-weight: 800;
  font-variant-numeric: tabular-nums;
}
.field-optional { counter-increment: none; padding-top: var(--s-4); border-top: 1px solid var(--line); }
.field-optional .field-label { font-weight: 600; color: var(--muted); }
.field-optional .field-label::before { content: none; }
.field-hint { color: var(--faint); font-size: var(--t-small); font-weight: 600; }
.field-optional .number { box-shadow: none; }
.field-optional .number input { font-weight: 600; }

.number {
  position: relative;
  display: flex; align-items: center;
  min-height: 3.25rem;
  background: var(--surface);
  border: 1px solid var(--line-strong);
  border-radius: var(--r-field);
  box-shadow: var(--shadow-1);
}
/* FOCUS LAW: the ring draws in (opacity + a 2% grow) over --dur-press. */
.number::after {
  content: "";
  position: absolute; inset: -1px;
  border-radius: inherit;
  box-shadow: 0 0 0 2px var(--ink);
  opacity: 0;
  transform: scale(0.98);
  pointer-events: none;
  transition: opacity var(--dur-press) var(--ease-out), transform var(--dur-press) var(--ease-out);
}
.number:focus-within { border-color: var(--ink); }
.number:focus-within::after { opacity: 1; transform: none; }
@media (forced-colors: active) {
  .number:focus-within { outline: 2px solid CanvasText; outline-offset: 1px; }
}
.number input {
  flex: 1 1 auto; min-width: 0; width: 100%;
  height: 3.25rem;
  padding: 0 var(--s-2) 0 var(--s-4);
  border: 0; background: transparent; outline: none;
  font-size: var(--t-field);      /* >= 16px: no page zoom on focus */
  font-weight: 700;
  font-variant-numeric: tabular-nums;
}
.number input::placeholder { color: var(--hint); opacity: 1; font-weight: 400; }
.number-unit {
  flex: none;
  padding-right: var(--s-4);
  color: var(--muted);
  font-size: var(--t-small);
  font-weight: 700;
  pointer-events: none;
}
.field[data-invalid] .number { border-color: var(--error); }
.field[data-invalid] .number::after { box-shadow: 0 0 0 2px var(--error); }
/* The note slides down out of its box when it first appears. */
.field-error {
  display: flex; gap: var(--s-2); align-items: flex-start;
  color: var(--error); font-size: var(--t-small); font-weight: 700; line-height: 1.35;
  transition: opacity var(--dur-enter) var(--ease-out), transform var(--dur-enter) var(--ease-out);
}
@starting-style { .field-error { opacity: 0; transform: translateY(calc(-1 * var(--rise))); } }
.field-error:empty { display: none; }
.field-error svg { flex: none; width: 16px; height: 16px; margin-top: 2px; }

/* Unit picker: a segmented control of real radio buttons. Every segment is
   the same width and a full 44px tall (the whole segment is the target).
   One pill sits behind the words and slides to the chosen segment with
   transform; the words above it change colour instantly. Works for 2, 3 or
   4 segments with no script. */
.units {
  --n: 3;
  position: relative;
  display: inline-grid; grid-auto-flow: column; grid-auto-columns: minmax(var(--tap), 1fr);
  flex: none;
  max-width: 100%;
  padding: 0; margin: 0; border: 0;
  height: var(--tap);
  background: var(--sunk);
  box-shadow: inset 0 0 0 1px var(--line);
  border-radius: var(--r-pill);
}
.units:has(> label:nth-of-type(2):last-of-type) { --n: 2; }
.units:has(> label:nth-of-type(4)) { --n: 4; }
.units::before {
  content: "";
  position: absolute; top: 3px; bottom: 3px; left: 3px;
  width: calc((100% - 6px) / var(--n));
  border-radius: var(--r-pill);
  background: var(--surface);
  box-shadow: 0 0 0 1px var(--line-strong);
  transition: transform var(--dur-view) var(--ease-in-out);
}
.units:has(> label:nth-of-type(2) > input:checked)::before { transform: translateX(100%); }
.units:has(> label:nth-of-type(3) > input:checked)::before { transform: translateX(200%); }
.units:has(> label:nth-of-type(4) > input:checked)::before { transform: translateX(300%); }
.units label { position: relative; z-index: 1; display: block; }
.units input { position: absolute; inset: 0; width: 100%; height: 100%; opacity: 0; margin: 0; cursor: pointer; }
.units span {
  display: grid; place-items: center;
  height: var(--tap);
  padding: 0 var(--s-2);   /* columns are 44px minimum anyway; small padding lets the picker fit under 200% text */
  border-radius: var(--r-pill);
  color: var(--muted);
  font-size: var(--t-small); font-weight: 700;
  user-select: none; -webkit-user-select: none;
}
.units input:checked + span { color: var(--ink); }
.units input:focus-visible + span { outline: 3px solid var(--ink); outline-offset: -3px; }
@media (forced-colors: active) {
  .units input:checked + span { outline: 2px solid Highlight; outline-offset: -4px; }
}

/* ---------- 8. Answer card ------------------------------------------------- */
.answer {
  position: relative;
  container: answer / inline-size;   /* the stats stack when this card is narrow */
  margin-top: var(--s-8);
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: var(--r-card);
  box-shadow: var(--shadow-1);
  overflow: hidden;
  min-width: 0;
}
/* HALLMARK LAW: a gold hairline crowns the card only while the proof is
   showing. Gold means "this was proved"; an empty card never wears it. */
.answer::before {
  content: "";
  position: absolute; inset: 0 0 auto 0; height: 3px;
  background: var(--gold);
  opacity: 0;
  pointer-events: none;
}
.answer[data-state="ready"]::before { opacity: 1; }
.answer-body { padding: var(--s-5) var(--s-5) var(--s-4); }
.answer-label { color: var(--muted); font-size: var(--t-small); font-weight: 700; }
/* FIT LAW: a number never breaks in the middle. It stays on one line
   (nowrap) and its size is fitted to the width it has, so it never needs to
   break. The script puts the number's width on it as data-fit, counted in
   half-digit steps: each digit 2, each comma, point or space 1, each letter
   2 ("5.2" = 5, "17" = 4, "5,185,185" = 16). One step is 0.306em of this
   face at this tracking (a tabular digit is 0.612em, a comma 0.28em).
     A = (row - unit room) / width   the size with the unit beside it
     B =  row              / width   the size with the row to itself
     size = min(max, max(A, min(B, floor)))
   So a short number fills the row with its unit on the baseline beside it;
   a long one keeps the unit beside it while it can stay at least --floor
   tall, and past that takes the whole row and the unit drops under it. */
.answer-main {
  container-type: inline-size;   /* "row" above is this box's width */
  display: flex; align-items: baseline; flex-wrap: wrap; gap: 0 var(--s-2);
  margin-top: var(--s-1);
  min-width: 0;
}
.answer-num {
  font-size: var(--t-answer);
  font-size: min(var(--t-answer-max),
             max(calc((100cqi - var(--answer-unit-room)) / (var(--fit, 6) * 0.306)),
                 min(calc(100cqi / (var(--fit, 6) * 0.306)), var(--t-answer-floor))));
  font-weight: 800;
  line-height: 1;
  letter-spacing: -0.02em;
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
}
[data-fit="1"]{--fit:1} [data-fit="2"]{--fit:2} [data-fit="3"]{--fit:3} [data-fit="4"]{--fit:4}
[data-fit="5"]{--fit:5} [data-fit="6"]{--fit:6} [data-fit="7"]{--fit:7} [data-fit="8"]{--fit:8}
[data-fit="9"]{--fit:9} [data-fit="10"]{--fit:10} [data-fit="11"]{--fit:11} [data-fit="12"]{--fit:12}
[data-fit="13"]{--fit:13} [data-fit="14"]{--fit:14} [data-fit="15"]{--fit:15} [data-fit="16"]{--fit:16}
[data-fit="17"]{--fit:17} [data-fit="18"]{--fit:18} [data-fit="19"]{--fit:19} [data-fit="20"]{--fit:20}
[data-fit="21"]{--fit:21} [data-fit="22"]{--fit:22} [data-fit="23"]{--fit:23} [data-fit="24"]{--fit:24}
.answer-unit { font-size: var(--t-answer-unit); font-weight: 750; letter-spacing: -0.01em; white-space: nowrap; }
.answer-more {
  display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: var(--s-3);
  margin-top: var(--s-4);
  padding-top: var(--s-4);
  border-top: 1px solid var(--line);
}
/* Under big text the two stats stack, so each number keeps a readable size. */
@container answer (max-width: 17rem) {
  .answer-more { grid-template-columns: minmax(0, 1fr); }
}
.answer-stat { min-width: 0; container-type: inline-size; }
/* Same FIT LAW, smaller: never above --t-stat, shrinks to fit its column. */
.answer-stat b {
  display: block; font-weight: 800; letter-spacing: -0.015em; line-height: 1.15;
  font-size: var(--t-stat);
  font-size: min(var(--t-stat), calc(100cqi / (var(--fit, 6) * 0.31)));
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
}
.answer-stat span { display: block; margin-top: 2px; color: var(--muted); font-size: var(--t-small); line-height: 1.35; }
/* A stat that needs an optional box shows a quiet prompt in place of a number. */
.answer-stat[data-empty] b { display: none; }
.answer-stat[data-empty] span { margin-top: 0; color: var(--faint); }
/* ACTION PAIR: the card's two actions are one matched pair. Both are
   .btn-secondary with an icon (copy glyph, circular-arrow restart glyph),
   the same height, sharing one row at 320. Under big text each may wrap
   its own words rather than run off the card. */
.answer-actions { display: flex; flex-wrap: wrap; gap: var(--s-2); margin-top: var(--s-5); }
.answer-actions .btn { flex: 1 1 auto; max-width: 100%; white-space: normal; text-align: center; line-height: 1.2; padding-block: var(--s-1); }

/* The empty state lives inside the same card, so the page never jumps.
   It is designed, not a grey sentence: a quiet two-tone glyph (inline SVG,
   coloured only from tokens), a one-line promise, and what to do next. */
.answer-empty { padding: var(--s-5); color: var(--muted); }
.answer-empty strong { display: block; color: var(--text); font-size: var(--t-head); font-weight: 750; letter-spacing: -0.01em; line-height: 1.3; margin-bottom: var(--s-1); text-wrap: balance; }
.empty-glyph { width: 4.5rem; height: auto; margin-bottom: var(--s-4); }
.empty-glyph .eg-face { fill: var(--ink-soft); stroke: var(--ink); }
.empty-glyph .eg-side { fill: var(--surface); stroke: var(--ink); }
.empty-glyph .eg-dim  { fill: none; stroke: var(--muted); }
.answer[data-state="empty"] .answer-body,
.answer[data-state="empty"] .proof { display: none; }
.answer[data-state="ready"] .answer-empty { display: none; }

/* Settle: the answer fades up once when it first appears, not on every key. */
.answer[data-state="ready"] .answer-body,
.answer[data-state="ready"] .proof {
  transition: opacity var(--dur-view) var(--ease-out), transform var(--dur-view) var(--ease-out);
}
@starting-style {
  .answer[data-state="ready"] .answer-body,
  .answer[data-state="ready"] .proof { opacity: 0; transform: translateY(var(--rise)); }
}

/* THE SIGNATURE: the proof. Every answer prints its own working underneath,
   stamped in hallmark gold. Nobody has to take the number on trust. */
.proof {
  background: var(--gold-wash);
  border-top: 1px solid var(--gold-line);
  color: var(--on-gold);
  padding: var(--s-4) var(--s-5) var(--s-5);
}
.proof-head {
  display: flex; align-items: center; gap: var(--s-2);
  font-size: var(--t-small); font-weight: 800;
}
.proof-head svg { flex: none; width: 20px; height: 20px; }
.proof-head .ring { stroke: var(--gold); }
.proof-head .tick { stroke: var(--on-gold); }
.proof-lines {
  list-style: none; margin: var(--s-3) 0 0; padding: 0;
  display: grid; gap: var(--s-1);
  font-family: var(--font-mono);
  font-size: 0.875rem;
  line-height: 1.45;
  font-variant-numeric: tabular-nums;
}
/* EQUALS LAW (for the next builder):
   1. Join every number to its unit with a no-break space ("\u00A0" in JS):
      "40 ft", "100 cu ft". They never split. For a phrase ("2 truck
      loads", "0.4 of a truck load") join only the number and the first
      word ("2 truck", "0.4 of"); the words after it may wrap.
   2. A step breaks only before the times sign, the divide sign or "=".
      Put the normal space before the sign and a no-break space after it.
      Each step ("40 ft x 10 ft", "= 100 cu ft") is one .eq box that
      moves down whole, so a result never stands alone.
   3. A step wider than the whole line (200% text, 9+ digits) scrolls
      sideways inside the proof list. It never wraps mid-number: the li
      turns off the body's overflow-wrap (which would break anywhere,
      even at a no-break space). The page itself never scrolls sideways. */
.proof-lines li {
  min-width: 0;
  overflow-wrap: normal; word-break: normal;
  overflow-x: auto; overflow-y: hidden;
  scrollbar-width: thin;
}
.proof-lines .eq { display: inline-block; max-width: 100%; }
.proof-lines b { font-weight: 800; }
.proof-note {
  margin-top: var(--s-3); padding-top: var(--s-3);
  border-top: 1px dashed var(--gold-line);
  font-size: var(--t-small); line-height: 1.45;
}
.proof-note b { font-weight: 800; }

/* ---------- 9. Chip -------------------------------------------------------- */
.chip {
  display: inline-flex; align-items: center; gap: var(--s-1);
  min-height: 1.75rem;
  padding: 0 var(--s-3);
  border-radius: var(--r-pill);
  border: 1px solid var(--line-strong);
  background: var(--surface);
  color: var(--muted);
  font-size: var(--t-tiny); font-weight: 700; letter-spacing: 0.01em;
  white-space: nowrap;
}
.chip-ink  { border-color: transparent; background: var(--ink-soft); color: var(--ink); }
.chip-gold { border-color: var(--gold-line); background: var(--gold-wash); color: var(--on-gold); }
/* A chip that is a control is a <button class="chip"> with aria-pressed,
   and grows to the 44px tap rule. */
button.chip { min-height: var(--tap); cursor: pointer; touch-action: manipulation; transition: transform var(--dur-press) var(--ease-out); }
button.chip[aria-pressed="true"] { background: var(--ink-soft); border-color: var(--ink); color: var(--ink); }

/* ---------- 10. Banner ----------------------------------------------------- */
/* Full width, directly under the header, above all content. One fact each. */
.banner {
  display: flex; align-items: center; gap: var(--s-3);
  min-height: var(--tap);
  padding: var(--s-2) calc(var(--gutter) + var(--safe-r)) var(--s-2) calc(var(--gutter) + var(--safe-l));
  background: var(--ink-soft);
  color: var(--text);
  font-size: var(--t-small); font-weight: 600;
  border-bottom: 1px solid var(--line);
}
.banner p { flex: 1 1 auto; min-width: 0; }
.banner-error { background: var(--error-wash); color: var(--error); }

/* ---------- 11. Save prompt ("Tap to save this app") ----------------------- */
.save-prompt {
  display: flex; align-items: center; gap: var(--s-1);
  margin-top: var(--s-4);
  padding: var(--s-1);
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: var(--r-pill);
  /* Rises only after the answer has settled (a --dur-view wait), so two
     things never move at once. */
  transition: opacity var(--dur-enter) var(--ease-out) var(--dur-view),
              transform var(--dur-enter) var(--ease-out) var(--dur-view);
}
@starting-style { .save-prompt { opacity: 0; transform: translateY(var(--rise)); } }
.save-prompt .btn-primary { flex: 1 1 auto; min-width: 0; white-space: normal; text-align: center; line-height: 1.2; padding-block: var(--s-2); }  /* wraps under big text zoom, never clips */
.save-prompt[hidden] { display: none; }

/* ---------- 12. Footer ----------------------------------------------------- */
.footer {
  border-top: 1px solid var(--line);
  padding: var(--s-6) calc(var(--gutter) + var(--safe-r)) calc(var(--s-8) + var(--safe-b)) calc(var(--gutter) + var(--safe-l));
  color: var(--muted);
  font-size: var(--t-small);
}
.footer-inner { max-width: calc(var(--page-max) - 2 * var(--gutter)); margin-inline: auto; display: grid; gap: var(--s-2); }
.footer-plan { color: var(--text); font-weight: 700; }
.footer-links { display: flex; flex-wrap: wrap; gap: 0 var(--s-5); }
.footer-links a, .footer-links button {
  display: inline-flex; align-items: center; min-height: var(--tap);
  padding: 0; border: 0; background: none;
  color: var(--ink); font-weight: 700; font-size: var(--t-small);
  text-decoration: underline; text-underline-offset: 0.2em; text-decoration-thickness: 1px;
  cursor: pointer; touch-action: manipulation;
}
.footer-links button { transition: transform var(--dur-press) var(--ease-out); }
.footer-links [hidden] { display: none; }

/* ---------- 13. Bottom sheet (native <dialog class="sheet">) --------------- */
.sheet {
  position: fixed;
  inset: auto 0 0 0;
  width: 100%;
  max-width: 30rem;
  max-height: calc(100dvh - var(--safe-t) - 2rem);
  margin: 0 auto;
  padding: 0;
  border: 0;
  border-radius: var(--r-sheet) var(--r-sheet) 0 0;
  background: var(--surface);
  color: var(--text);
  box-shadow: var(--shadow-sheet);
  overflow: hidden;
  transform: translateY(100%);
  transition: transform var(--dur-quick) var(--ease-out),
              overlay var(--dur-quick) allow-discrete,
              display var(--dur-quick) allow-discrete;
}
.sheet[open] {
  transform: translateY(0);
  transition: transform var(--dur-sheet) var(--ease-drawer),
              overlay var(--dur-sheet) allow-discrete,
              display var(--dur-sheet) allow-discrete;
}
@starting-style { .sheet[open] { transform: translateY(100%); } }
.sheet::backdrop {
  background: var(--scrim);
  opacity: 0;
  transition: opacity var(--dur-quick) var(--ease-out),
              overlay var(--dur-quick) allow-discrete,
              display var(--dur-quick) allow-discrete;
}
.sheet[open]::backdrop { opacity: 1; transition-duration: var(--dur-sheet); }
@starting-style { .sheet[open]::backdrop { opacity: 0; } }
.sheet-scroll {
  max-height: inherit;
  overflow-y: auto;
  overscroll-behavior: contain;
  padding: var(--s-2) var(--s-5) calc(var(--s-6) + var(--safe-b));
}
.sheet-grip { width: 36px; height: 5px; margin: var(--s-1) auto var(--s-3); border-radius: 3px; background: var(--line-strong); }
.sheet-top { display: flex; align-items: center; justify-content: space-between; gap: var(--s-3); }
.sheet-top .btn-icon { margin-right: calc(-1 * var(--s-2)); }
.sheet-title { font-size: 1.25rem; font-weight: 750; letter-spacing: -0.01em; line-height: 1.2; }
.sheet-lede { margin-top: var(--s-2); color: var(--muted); }
.steps { list-style: none; margin: var(--s-5) 0 var(--s-6); padding: 0; display: grid; gap: var(--s-4); counter-reset: step; }
.steps li { display: grid; grid-template-columns: 2rem 1fr; gap: var(--s-3); align-items: start; counter-increment: step; }
.steps li::before {
  content: counter(step);
  display: grid; place-items: center;
  width: 2rem; height: 2rem; border-radius: 50%;
  background: var(--ink-soft); color: var(--ink);
  font-weight: 800; font-size: var(--t-small);
}
.steps p { padding-top: 0.2rem; }
.steps .glyph { display: inline-block; width: 1.15em; height: 1.15em; vertical-align: -0.2em; color: var(--ink); }

/* ---------- 14. Toast ------------------------------------------------------ */
.toast {
  position: fixed;
  left: 50%;
  bottom: calc(var(--s-6) + var(--safe-b));
  z-index: 30;
  max-width: calc(100% - 2 * var(--gutter));
  padding: var(--s-3) var(--s-5);
  border-radius: var(--r-pill);
  background: var(--text);
  color: var(--paper);
  font-size: var(--t-small); font-weight: 700;
  box-shadow: 0 6px 20px rgb(0 0 0 / 0.18);
  pointer-events: none;
  opacity: 0;
  transform: translate(-50%, var(--rise));
  transition: opacity var(--dur-quick) var(--ease-out), transform var(--dur-quick) var(--ease-out);
}
.toast[data-show] {
  opacity: 1;
  transform: translate(-50%, 0);
  transition-duration: var(--dur-enter);
}

/* ---------- 15. Calculators index list ------------------------------------ */
.calc-list { list-style: none; margin: var(--s-6) 0 0; padding: 0; display: grid; gap: var(--s-3); }
.calc-row {
  display: grid; grid-template-columns: minmax(0, 1fr) auto; align-items: center; gap: var(--s-3);
  min-height: 4.5rem;
  padding: var(--s-4) var(--s-4) var(--s-4) var(--s-5);
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: var(--r-card);
  color: var(--text);
  text-decoration: none;
  touch-action: manipulation;
  transition: transform var(--dur-press) var(--ease-out);
}
a.calc-row:active { transform: scale(0.985); }
.calc-row-name { display: block; font-size: var(--t-head); font-weight: 750; letter-spacing: -0.005em; line-height: 1.25; }
.calc-row-what { display: block; margin-top: 2px; color: var(--muted); font-size: var(--t-small); line-height: 1.4; }
.calc-row-go { width: 20px; height: 20px; color: var(--ink); }
.calc-row[aria-disabled="true"] { background: transparent; border-style: dashed; }
.calc-row[aria-disabled="true"] .calc-row-name { color: var(--muted); }
@media (hover: hover) and (pointer: fine) {
  a.calc-row:hover { border-color: var(--ink); }
}

/* ---------- 16. Wide layout (desktop is the wide case) -------------------- */
@media (min-width: 30rem) and (max-width: 59.99rem) {
  .calc { max-width: var(--col-max); }
}
@media (min-width: 60rem) {
  :root { --gutter: 2rem; }
  .page { padding-top: var(--s-10); }
  .calc {
    display: grid;
    grid-template-columns: minmax(0, 1fr) minmax(0, 26rem);
    gap: var(--s-12);
    align-items: start;
  }
  .calc .answer-col { position: sticky; top: calc(var(--header-h) + var(--s-6)); }
  .calc .answer { margin-top: 0; }
  .calc-list { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}

/* ---------- 17. Reduced motion and transparency -------------------------- */
/* Keep the fades (they tell you something changed); drop all travel.
   Every transition that moves something is restated here as opacity-only
   (or none), so no transform transition can fire. The count-up is skipped
   in script (it reads the same media query). */
@media (prefers-reduced-motion: reduce) {
  :root { --rise: 0px; }
  .btn:active, .way-home:active, a.calc-row:active,
  button.chip:active, .footer-links button:active { transform: none; }
  .btn, button.chip, .footer-links button, a.calc-row { transition: none; }
  .way-home { transition: opacity var(--dur-press) linear; }
  .units::before { transition: none; }
  .number::after { transform: none; transition: opacity var(--dur-press) linear; }
  .field-error { transition: opacity var(--dur-enter) linear; }
  @starting-style { .field-error { opacity: 0; transform: none; } }
  .answer[data-state="ready"] .answer-body,
  .answer[data-state="ready"] .proof { transition: opacity var(--dur-view) linear; }
  @starting-style {
    .answer[data-state="ready"] .answer-body,
    .answer[data-state="ready"] .proof { opacity: 0; transform: none; }
  }
  .save-prompt { transition: opacity var(--dur-enter) linear var(--dur-view); }
  @starting-style { .save-prompt { opacity: 0; transform: none; } }
  .toast, .toast[data-show] { transform: translate(-50%, 0); transition-property: opacity; }
  .sheet, .sheet[open] { transform: none; }
  .sheet {
    opacity: 0;
    transition: opacity var(--dur-quick) linear,
                overlay var(--dur-quick) allow-discrete,
                display var(--dur-quick) allow-discrete;
  }
  .sheet[open] {
    opacity: 1;
    transition: opacity var(--dur-view) linear,
                overlay var(--dur-view) allow-discrete,
                display var(--dur-view) allow-discrete;
  }
  @starting-style { .sheet[open] { opacity: 0; transform: none; } }
}
@media (prefers-reduced-transparency: reduce) {
  :root { --scrim: rgb(10 16 26 / 0.72); }
}

/* ---------- 18. Print: the answer and its working, nothing else ---------- */
@media print {
  .header, .footer, .save-prompt, .answer-actions, .toast, .fields { display: none !important; }
  .answer, .proof { break-inside: avoid; }
}
