/* undercart.app
 *
 * ── What this page is ───────────────────────────────────────────────────
 *
 * The product argues that a confident figure you cannot check is worthless.
 * A marketing page for it that made that argument in invented graphics would
 * be doing the exact thing it warns about. **So the page is the product**:
 * three of the app's real surfaces, rendered at full size, carrying the
 * figures from the handoff's own worked week.
 *
 * ── Where every value comes from ────────────────────────────────────────
 *
 * The palette, the two typefaces, the radii and the motion are the app's own
 * (.claude/rules/brand.md, docs/MOTION.md). The components below are lifted
 * from the signed-off screens in docs/reference/tabs/:
 *
 *   .block           the coloured surface atop Plan, Shop and Spend
 *   .money / .track  the two-tier figure and its bar
 *   .pill            the cream pill carrying the verdict
 *   .rows / .row     the hairline dinner list with its day marker
 *   .aisle / .item   the Shop tab's aisle strip and its tick rows
 *   .weeks           the Spend history, with the miss in brick
 *
 * ── Motion, and the one rule that shapes all of it ──────────────────────
 *
 * **Never fade.** A thing that changes state moves, travels, or changes
 * colour; it does not dissolve (brand.md 7b). Entrances are travel only, with
 * no opacity ramp, and figures roll their digits rather than swapping. Short
 * throughout, because premium reads as immediate and considered, never slow.
 *
 * ── Light only ──────────────────────────────────────────────────────────
 *
 * O-008. A marketing page is read once in whatever light the reader is in;
 * the app earns its dark palette because it is opened in a kitchen at six.
 */

/* ═══ Tokens ═══════════════════════════════════════════════════════════ */

:root {
  /* The seven, exact. Each carries one job (brand.md 1). */
  --pine: #1C5B4A;
  --ink: #211E1A;
  --brick: #B0472C;
  --indigo: #45557E;
  --sand: #E7DFD0;
  --cream: #FBF7EF;
  --paper: #F1EBE0;

  /* The dim vocabulary: a fixed set, never an ad hoc opacity in a rule. */
  --ink-78: rgba(33, 30, 26, .78);
  --ink-60: rgba(33, 30, 26, .60);
  --ink-45: rgba(33, 30, 26, .45);
  /* **The lightest ink that still clears AA as body text**, measured over all
   * three grounds: 4.81 on paper, 5.04 on cream, 4.59 on sand. Everything a
   * reader has to read uses this or darker. The lighter dims above it survive
   * for hairlines and fills, which are not text. */
  --ink-65: rgba(33, 30, 26, .65);
  --ink-30: rgba(33, 30, 26, .30);
  /* The lightest that clears 3:1 on cream, which is the floor for a UI
   * component's own outline (the unticked box). */
  --ink-50: rgba(33, 30, 26, .50);
  --ink-12: rgba(33, 30, 26, .12);
  --ink-07: rgba(33, 30, 26, .07);
  --cream-78: rgba(251, 247, 239, .78);
  --cream-60: rgba(251, 247, 239, .60);
  /* Pine is the stricter of the two coloured grounds: cream at .60 is 3.81
   * on it, which fails AA for body text. At .72 it is 4.7 on pine and 8.5 on
   * ink, so one value serves both blocks. */
  --cream-72: rgba(251, 247, 239, .72);
  /* The bar's hatching is a UI component and needs 3:1, which .30 missed. */
  --cream-55: rgba(251, 247, 239, .55);
  --cream-16: rgba(251, 247, 239, .16);

  /* 4px scale. */
  --s1: 4px;  --s2: 8px;  --s3: 12px; --s4: 16px;
  --s5: 24px; --s6: 32px; --s7: 48px; --s8: 64px; --s9: 96px;

  --r-sm: 10px;
  --r-card: 18px;
  --r-block: 26px;
  --r-pill: 999px;

  --gutter: var(--s5);
  --max: 1080px;

  /* The app's own curves and durations (MOTION.md). */
  --settle: cubic-bezier(.22, .61, .36, 1);
  --snap: cubic-bezier(.2, .8, .3, 1);
  --t-tap: .16s;
  --t-arrive: .42s;
}

/* ═══ Ground ═══════════════════════════════════════════════════════════ */

*, *::before, *::after { box-sizing: border-box; }
html { -webkit-text-size-adjust: 100%; scroll-behavior: smooth; }

body {
  margin: 0;
  background: var(--paper);
  color: var(--ink);
  font-family: "Instrument Sans", system-ui, -apple-system, "Segoe UI", Helvetica, Arial, sans-serif;
  font-size: 17px;
  line-height: 1.55;
  -webkit-font-smoothing: antialiased;
  text-wrap: pretty;
}

/* Archivo carries every figure and every heading. Instrument Sans carries
 * every sentence. The two never swap jobs (brand.md 2). */
h1, h2, h3, .fig, .fig-sm, .day, .plate, .amount, .wordmark, .num, .price, .cost {
  font-family: "Archivo", system-ui, -apple-system, "Segoe UI", Helvetica, Arial, sans-serif;
}

/* Tabular, always. A figure whose width jitters as its digits change reads as
 * uncertainty about the number rather than about the price. */
.fig, .fig-sm, .plate, .price, .amount, .num, .cost, .count { font-variant-numeric: tabular-nums; }

/* Tracking is banded by size, negative on Archivo, tightening as it grows. */
h1 { font-weight: 700; font-size: clamp(40px, 6.4vw, 66px); line-height: 1.04; letter-spacing: -.045em; margin: 0; }
h2 { font-weight: 700; font-size: clamp(27px, 3.6vw, 40px); line-height: 1.1; letter-spacing: -.035em; margin: 0 0 var(--s3); }
h3 { font-weight: 600; font-size: 19px; line-height: 1.26; letter-spacing: -.02em; margin: 0 0 var(--s1); }

p { margin: 0 0 var(--s3); }
p:last-child { margin-bottom: 0; }

a { color: var(--pine); text-underline-offset: 3px; text-decoration-thickness: 1px; }
a:hover { text-decoration-thickness: 2px; }
:focus-visible { outline: 2px solid var(--pine); outline-offset: 3px; border-radius: 4px; }

.wrap { max-width: var(--max); margin: 0 auto; padding: 0 var(--gutter); }
.wrap.narrow > * { max-width: 680px; }

.eyebrow {
  font-family: "Instrument Sans", system-ui, sans-serif;
  font-weight: 600; font-size: 11.5px; letter-spacing: .14em;
  text-transform: uppercase; color: var(--ink-65); margin: 0 0 var(--s3);
}

.lede { font-size: clamp(18px, 2vw, 20px); line-height: 1.5; color: var(--ink-78); margin: var(--s4) 0 0; max-width: 46ch; }
.sub { color: var(--ink-65); margin: 0; max-width: 58ch; }

.skip { position: absolute; left: -9999px; background: var(--ink); color: var(--cream); padding: var(--s3) var(--s4); border-radius: var(--r-sm); z-index: 100; }
.skip:focus { left: var(--s4); top: var(--s4); }

/* ═══ Chrome ═══════════════════════════════════════════════════════════
 *
 * One header on every page, legal pages included. Those used to carry a lone
 * "Home" link under the mark, which read as a broken nav rather than as a
 * decision (Elias, 28 August 2026).
 */

.site-head {
  position: sticky; top: 0; z-index: 50;
  padding: var(--s4) 0;
  background: var(--paper);
  /* A colour change on scroll, never a fade: the rule the app follows. */
  border-bottom: 1px solid transparent;
  transition: border-color var(--t-tap) linear, background var(--t-tap) linear;
}
.site-head.moved { border-bottom-color: var(--ink-12); background: rgba(241, 235, 224, .92); backdrop-filter: blur(8px); }
.site-head .wrap { display: flex; align-items: center; justify-content: space-between; gap: var(--s4); }

.brand { display: inline-flex; align-items: center; gap: var(--s3); text-decoration: none; color: inherit; }
.brand .tile {
  width: 34px; height: 34px; border-radius: var(--r-sm);
  background: var(--pine); display: grid; place-items: center;
  transition: transform var(--t-tap) var(--snap);
}
.brand:hover .tile { transform: scale(1.06); }
.brand .tile img { width: 62%; display: block; }
.wordmark { font-weight: 700; font-size: 19px; letter-spacing: -.02em; }

.site-nav { display: flex; align-items: center; gap: var(--s5); font-size: 15px; }
.site-nav a { color: var(--ink-65); text-decoration: none; transition: color var(--t-tap) linear; }
.site-nav a:hover, .site-nav a[aria-current] { color: var(--ink); }

/* ═══ Sections ═════════════════════════════════════════════════════════ */

section { padding: var(--s9) 0; }
section.tight { padding: var(--s8) 0; }
.hero { padding: var(--s7) 0 var(--s9); }

/* ═══ The coloured block ═══════════════════════════════════════════════
 *
 * The surface at the top of Plan, Shop and Spend. Painted per tab so it says
 * where you are without moving (D-029).
 */

.block { border-radius: var(--r-block); padding: var(--s6); color: var(--cream); }
.block.pine { background: var(--pine); }
.block.ink { background: var(--ink); }
.block .eyebrow { color: var(--cream-72); margin-bottom: var(--s4); }
.block h2 { color: var(--cream); }
.block .head { display: flex; align-items: flex-end; justify-content: space-between; gap: var(--s4); flex-wrap: wrap; }

/* Two tier. `$54` is what we expect, `$61` the most it can be, and the type
 * carries that asymmetry. A dash between them would say the two ends are
 * equally likely, which is the one distinction a reader needs (DESIGN.md 2). */
.money { display: flex; align-items: flex-end; gap: var(--s3); }
.fig { font-weight: 700; font-size: clamp(48px, 7vw, 64px); letter-spacing: -.045em; line-height: .92; }
.upto { display: flex; flex-direction: column; line-height: 1.05; padding-bottom: 5px; }
.upto small {
  font-family: "Instrument Sans", system-ui, sans-serif;
  font-weight: 600; font-size: 10px; letter-spacing: .14em;
  text-transform: uppercase; color: var(--cream-72);
}
.fig-sm { font-weight: 700; font-size: 22px; letter-spacing: -.02em; color: var(--cream-78); }
.against { text-align: right; line-height: 1.2; color: var(--cream-72); font-size: 13px; padding-bottom: 6px; }
.against b { display: block; font-family: "Archivo", sans-serif; font-weight: 700; font-size: 17px; color: var(--cream-78); letter-spacing: -.02em; }

/* The bar. The solid run is what we expect; the hatched run beside it is the
 * room up to the ceiling, drawn as a range because that is what it is. */
.track { height: 12px; border-radius: var(--r-pill); background: var(--cream-16); margin: var(--s5) 0 var(--s4); overflow: hidden; display: flex; }
.track span { transition: width var(--t-arrive) var(--settle); }
.track .done { background: var(--cream); }
.track .room { background-image: repeating-linear-gradient(-45deg, var(--cream-55) 0 4px, transparent 4px 8px); }

.pillrow { display: flex; align-items: center; gap: var(--s4); flex-wrap: wrap; }
.pill {
  display: inline-flex; align-items: center; gap: var(--s2);
  background: var(--cream); color: var(--pine);
  border-radius: var(--r-pill); padding: var(--s2) var(--s4);
  font-weight: 600; font-size: 14.5px; font-variant-numeric: tabular-nums;
}
.pill svg { width: 14px; height: 14px; }
.block .aside { color: var(--cream-78); font-size: 14px; line-height: 1.4; margin: 0; flex: 1 1 190px; min-width: 0; max-width: 290px; }
.block .why { color: var(--cream-78); margin: var(--s4) 0 0; font-size: 15.5px; max-width: 60ch; }

/* ═══ Plan rows ════════════════════════════════════════════════════════ */

.rows { margin-top: var(--s6); }
.row { display: grid; grid-template-columns: 46px 1fr auto; gap: var(--s4); align-items: start; padding: var(--s4) 0; border-bottom: 1px solid var(--ink-07); }
.row:last-child { border-bottom: 0; }
.day { font-weight: 700; font-size: 11px; letter-spacing: .1em; color: var(--ink-65); padding-top: 4px; }
.day.tonight { color: var(--pine); }
.day.tonight::after { content: ""; display: block; width: 5px; height: 5px; border-radius: 50%; background: var(--pine); margin-top: 5px; }
.row .name { font-weight: 600; font-size: 17px; line-height: 1.3; margin: 0; }
.row .facts { display: flex; align-items: center; gap: 6px; color: var(--ink-65); font-size: 13px; margin: 5px 0 0; }
.row .facts svg { width: 13px; height: 13px; flex: none; }

/* Indigo means exactly one thing: an ingredient shared across nights, bought
 * once and charged once. It is the strictest colour in the system. */
.row .shared { display: flex; align-items: flex-start; gap: 7px; color: var(--indigo); font-size: 13px; line-height: 1.35; margin: 5px 0 0; }
.row .shared svg { width: 14px; height: 14px; flex: none; margin-top: 2px; }
.row .cost { text-align: right; white-space: nowrap; }
.row .price { font-weight: 700; font-size: 18px; letter-spacing: -.02em; }
.row .to { font-family: "Instrument Sans", sans-serif; color: var(--ink-65); font-size: 13px; margin-left: 4px; }
.plate { display: block; margin-top: 3px; font-weight: 600; font-size: 10px; letter-spacing: .1em; color: var(--ink-65); }

/* ═══ Shop list ════════════════════════════════════════════════════════
 *
 * The one interactive surface, and it earns being interactive: the product
 * claims every item is priced before it goes in the trolley, and a reader can
 * check that claim here by ticking one and watching the total keep up.
 */

.shop { margin-top: var(--s5); border-radius: var(--r-card); overflow: hidden; }
.aisle { display: flex; align-items: center; justify-content: space-between; gap: var(--s4); background: var(--sand); padding: var(--s3) var(--s5); font-size: 14.5px; font-weight: 600; }
.aisle .count { font-weight: 500; color: var(--ink-65); font-size: 13.5px; }

.item {
  display: flex; align-items: center; gap: var(--s4);
  width: 100%; text-align: left;
  background: var(--cream); border: 0; border-bottom: 1px solid var(--ink-07);
  padding: var(--s4) var(--s5);
  font: inherit; color: inherit; cursor: pointer;
  transition: background var(--t-tap) linear;
}
.item:last-child { border-bottom: 0; }
.item:hover { background: #F7F2E8; }
.item:active { background: var(--sand); }

.tick {
  width: 26px; height: 26px; border-radius: 7px; flex: none;
  border: 2px solid var(--ink-50);
  display: grid; place-items: center;
  transition: background var(--t-tap) var(--snap), border-color var(--t-tap) linear;
}
.tick svg { width: 14px; height: 14px; color: var(--cream); transform: scale(.4); transition: transform var(--t-tap) var(--snap); }
.item[aria-pressed="true"] .tick { background: var(--pine); border-color: var(--pine); }
.item[aria-pressed="true"] .tick svg { transform: scale(1); }

.item .what { flex: 1; min-width: 0; }
.item .label { font-weight: 600; font-size: 16.5px; display: block; transition: color var(--t-tap) linear; }
.item .nights { font-size: 11px; font-weight: 600; letter-spacing: .1em; color: var(--ink-65); text-transform: uppercase; }
.item .cost { font-weight: 700; font-size: 17px; letter-spacing: -.02em; transition: color var(--t-tap) linear; }
/* A ticked item stays legible. Completion is carried by the filled pine box
 * and the strikethrough, which are two signals; dimming it below AA as well
 * would make colour the only way to read the row's state. */
.item[aria-pressed="true"] .label { color: var(--ink-65); text-decoration: line-through; text-decoration-color: var(--ink-50); }
.item[aria-pressed="true"] .cost { color: var(--ink-65); }

.shop-hint { margin-top: var(--s4); font-size: 14px; color: var(--ink-65); }

/* ═══ The weeks, owned ═════════════════════════════════════════════════
 *
 * Spend's history. Brick appearing is the product working, so the miss sits
 * in the same list as the weeks we made rather than in a box of its own. On
 * cream, because brick on sand is 4.19:1 and fails AA for body text.
 */

.weeks { background: var(--cream); border-radius: var(--r-card); padding: 0 var(--s5); margin-top: var(--s6); }
.week { display: grid; grid-template-columns: 76px 1fr auto; gap: var(--s4); align-items: baseline; padding: var(--s4) 0; border-bottom: 1px solid var(--ink-07); }
.week:last-child { border-bottom: 0; }
.week .when { color: var(--ink-65); font-size: 13px; }
.week .what { font-size: 15px; color: var(--ink-78); }
.week .amount { font-weight: 700; font-size: 16.5px; letter-spacing: -.02em; color: var(--pine); text-align: right; }
.week .amount.over { color: var(--brick); }

/* ═══ Steps ════════════════════════════════════════════════════════════ */

.steps { display: grid; gap: var(--s4); grid-template-columns: 1fr; margin-top: var(--s6); }
.step { background: var(--cream); border-radius: var(--r-card); padding: var(--s5); }
.step .num { display: block; font-weight: 700; font-size: 12px; letter-spacing: .06em; color: var(--pine); margin-bottom: var(--s2); }
.step p { color: var(--ink-65); font-size: 15.5px; margin: 0; }

/* ═══ Refusals ═════════════════════════════════════════════════════════ */


/* ═══ Price ════════════════════════════════════════════════════════════ */

.plans { display: flex; gap: var(--s3); flex-wrap: wrap; margin-top: var(--s5); }
.plan { flex: 1 1 190px; border: 1px solid var(--cream-16); border-radius: var(--r-card); padding: var(--s5); }
.plan .amount { font-weight: 700; font-size: 32px; letter-spacing: -.035em; display: block; }
.plan .per { color: var(--cream-72); font-size: 14px; }
.plan .save { color: var(--cream-78); font-size: 13px; margin: var(--s2) 0 0; }

/* Not a button: there is nothing to tap yet, and a control that looks
 * pressable and is not is the same class of thing as a figure we cannot
 * defend. */
.soon { display: inline-flex; align-items: center; gap: var(--s2); margin-top: var(--s6); padding: var(--s3) var(--s5); border-radius: var(--r-pill); background: var(--ink); color: var(--cream); font-size: 15px; font-weight: 500; }
.soon .dot { width: 7px; height: 7px; border-radius: 50%; background: var(--cream); opacity: .7; }

/* ═══ Footer ═══════════════════════════════════════════════════════════ */

.site-foot { border-top: 1px solid var(--ink-12); padding: var(--s7) 0 var(--s8); color: var(--ink-65); font-size: 15px; }
.site-foot .cols { display: flex; justify-content: space-between; gap: var(--s5); flex-wrap: wrap; align-items: baseline; }
.site-foot nav { display: flex; gap: var(--s5); flex-wrap: wrap; }
.site-foot a { color: var(--ink-65); text-decoration: none; }
.site-foot a:hover { color: var(--ink); text-decoration: underline; }

/* ═══ Legal pages ══════════════════════════════════════════════════════ */

/* Vertical only. The shorthand used to set the horizontal padding to 0,
 * which wiped the wrap's own gutter and left every legal page's text 24px
 * to the left of the header above it. */
.legal { padding-top: var(--s7); padding-bottom: var(--s9); }
.legal h1 { font-size: clamp(30px, 4.6vw, 44px); margin-bottom: var(--s1); }
.legal .updated { color: var(--ink-65); font-size: 15px; margin: 0 0 var(--s7); }
.legal h2 { font-size: 21px; margin: var(--s7) 0 var(--s3); letter-spacing: -.025em; }
.legal p, .legal li { max-width: 68ch; }
.legal ul { padding-left: var(--s5); }
.legal li { margin-bottom: var(--s2); }

/* ═══ Motion ═══════════════════════════════════════════════════════════
 *
 * **Travel, never fade.** Everything below moves into place; nothing
 * dissolves. The stagger is 90ms, which is the app's own interval, applied
 * as a per-child delay by the script.
 */

.rise { transform: translateY(22px); transition: transform var(--t-arrive) var(--settle); }
.rise.in { transform: none; }

@media (prefers-reduced-motion: reduce) {
  html { scroll-behavior: auto; }
  *, *::before, *::after { animation-duration: .01ms !important; transition-duration: .01ms !important; }
  .rise { transform: none; }
}

/* ═══ Responsive ═══════════════════════════════════════════════════════ */

@media (min-width: 700px) {
  .steps { grid-template-columns: 1fr 1fr; }
}

@media (min-width: 960px) {
  .hero .wrap { display: grid; grid-template-columns: 1.02fr .98fr; gap: var(--s8); align-items: center; }
  .steps { grid-template-columns: repeat(4, 1fr); }
  .split { display: grid; grid-template-columns: 1fr 1fr; gap: var(--s8); align-items: start; }
  .split .sticky { position: sticky; top: 110px; }
}

@media (max-width: 620px) {
  :root { --gutter: 20px; }
  .site-nav { display: none; }
  section { padding: var(--s8) 0; }
  .hero { padding: var(--s6) 0 var(--s8); }
  .block { padding: var(--s5); }
  .hero .block { margin-top: var(--s6); }
  .row { grid-template-columns: 38px 1fr auto; gap: var(--s3); }
  .week { grid-template-columns: 62px 1fr auto; gap: var(--s3); }
  .item, .aisle { padding-left: var(--s4); padding-right: var(--s4); }
  .block .aside { max-width: none; }
}
