/* trackchat.fit — the only stylesheet.
   Tokens, type scale, layout and components. Hand-written; no build step.
   Palette ratios are recorded in docs/web_landing_implementation_plan.md → Palette;
   re-measure there before changing any hex here. */

/* ---------------------------------------------------------------- typeface
   Onest, self-hosted (ADR-054). Split by unicode-range so Cyrillic only loads
   for the languages that need it. The wordmark is set in the same face. */

@font-face {
  font-family: "Onest";
  src: url("fonts/onest-latin.woff2") format("woff2");
  font-weight: 100 900;
  font-style: normal;
  font-display: swap;
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA,
    U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193,
    U+2212, U+2215, U+FEFF, U+FFFD;
}

@font-face {
  font-family: "Onest";
  src: url("fonts/onest-cyrillic.woff2") format("woff2");
  font-weight: 100 900;
  font-style: normal;
  font-display: swap;
  unicode-range: U+0301, U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116;
}

/* Latin Extended. The latin face above stops at U+00FF (Latin-1), so Czech ě š č ř ž ů,
   Slovak ĺ ľ ŕ, Polish ł ą ę ś ź ż ć ń, Hungarian ő ű, Romanian ș ț and Turkish ğ ş İ all
   fell through to a system font MID-WORD — one word rendered in two typefaces. Nobody
   caught it before because no page had ever rendered those glyphs.

   ~10-15 kB, and `unicode-range` means only the six languages that need it ever download
   it, so the <100 kB first view (ADR-054) is untouched for everyone else. Turkish dotless
   ı (U+0131) stays in the latin face, where it already was — do not move it here, or it
   would pull this subset down for a page that needs nothing else from it. */
@font-face {
  font-family: "Onest";
  src: url("fonts/onest-latin-ext.woff2") format("woff2");
  font-weight: 100 900;
  font-style: normal;
  font-display: swap;
  unicode-range: U+0100-0130, U+0132-024F, U+0259, U+2020, U+2113, U+20A0-20AB,
    U+20AD-20CF, U+2C60-2C7F, U+A720-A7FF;
}

/* ------------------------------------------------------------------ tokens */

:root {
  --paper: #F2EFEC;
  --surface: #FFFFFF;
  --ink: #161311;
  --muted: #6E675E;
  --edge: #DCDAD4;
  --band: #10321E;
  --brand: #16A34A;
  --brand-deep: #117A39;
  --brand-soft: #A7E9BF;
  --brand-lift: #4ADE80;
  --logged: #15803D;
  --ink-full: #0C0F0C;
  --tg-blue: #229ED9;
  --wa-green: #25D366;

  /* Onest carries Latin, Latin-ext and Cyrillic. Devanagari (hi), Japanese and Hangul (ko)
     get the platform's own script font instead of a per-script webfont: each would be
     100 kB+ on its own and blow the first-view budget, and every platform already ships a
     good one. The stack names them explicitly rather than trusting `sans-serif`, which on
     some Linux builds picks a font with no coverage and renders tofu. */
  --face: "Onest", system-ui, -apple-system, "Segoe UI",
    "Noto Sans Devanagari", "Noto Sans JP", "Noto Sans KR",
    "Hiragino Sans", "Yu Gothic", "Meiryo", "Apple SD Gothic Neo", "Malgun Gothic",
    sans-serif;
  --mono: ui-monospace, SFMono-Regular, Menlo, "Cascadia Mono", monospace;

  --pad: clamp(6rem, 12vh, 10rem);
  --radius: 18px;

  color-scheme: light;
}

/* -------------------------------------------------------------------- base */

* { box-sizing: border-box; }

html { scroll-behavior: smooth; }

body {
  margin: 0;
  background: var(--paper);
  color: var(--ink);
  font-family: var(--face);
  font-size: 1.125rem;
  line-height: 1.7;
  -webkit-font-smoothing: antialiased;
  text-rendering: optimizeLegibility;
}

h1, h2, h3 { font-weight: 800; letter-spacing: -0.03em; line-height: 1.08; margin: 0; }

h2 { font-size: clamp(2rem, 5vw, 3.5rem); margin-bottom: 1rem; }
h3 { font-size: 1.3rem; letter-spacing: -0.02em; }

p { margin: 0 0 1rem; }

a { color: var(--brand-deep); }

:focus-visible {
  outline: 3px solid var(--brand-deep);
  outline-offset: 3px;
  border-radius: 6px;
}

.wrap { width: min(1120px, 90vw); margin: 0 auto; }
.wrap.narrow { width: min(760px, 90vw); }

section { padding: var(--pad) 0; }

.lede {
  color: var(--muted);
  font-size: 1.2rem;
  max-width: 58ch;
  margin: 0 0 3rem;
}
/* One sentence per line: wide enough that the longest of the three holds. */
.lede.wide { max-width: 90ch; }
/* A lede that introduces a later row rather than the section — it needs air above it. */
.lede.midlede { margin: 6.75rem 0 2rem; }


.footnote { color: var(--muted); font-size: 0.92rem; margin-top: 2rem; max-width: 62ch; }
.footnote sup { color: var(--brand-deep); font-weight: 700; }

.sprite { position: absolute; }

.skip {
  position: absolute; left: -9999px;
  background: var(--ink); color: var(--paper);
  padding: 0.7rem 1.1rem; border-radius: 0 0 12px 0;
  text-decoration: none; z-index: 100;
}
.skip:focus { left: 0; top: 0; }

/* ------------------------------------------------------------------- logos */

/* The wordmark is outlined Onest 800 (web/live/wordmark.svg), so the logo never
   waits on the webfont and never flashes a fallback. Its two colours cannot be
   reached by a class selector through the <use> shadow tree — custom properties
   inherit, so --wm-track / --wm-chat are what recolour it on the dark sections. */
.wordmark {
  --wm-track: var(--brand);
  --wm-chat: var(--ink);
  --wm-fit: var(--ink);
  height: 0.78em;          /* cap height of the outline, so it sits like text */
  width: auto;
  aspect-ratio: 4279 / 715;  /* the viewBox — without it the box fills its parent
                                and preserveAspectRatio centres the ink in a gap */
  display: block;
  flex: none;
}

/* fill and stroke are inherited properties, so they reach the <use> shadow tree;
   the badge rect carries stroke="none" so only the knocked-out tick is stroked. */
.wordmark-fit { aspect-ratio: 4731 / 715; }

.mark { width: 30px; height: 30px; flex: none; fill: var(--brand); stroke: var(--paper); }
.dark .mark { fill: var(--brand-lift); stroke: var(--band); }

.lockup {
  display: inline-flex; align-items: center; gap: 0.6rem;
  color: var(--ink); text-decoration: none;
  font-size: 1.35rem;
}

/* ------------------------------------------------------------------ header */

header {
  position: sticky; top: 0; z-index: 30;
  background: color-mix(in srgb, var(--paper) 88%, transparent);
  backdrop-filter: saturate(140%) blur(12px);
  border-bottom: 1px solid transparent;
  transition: border-color 0.2s ease;
}
header.scrolled { border-bottom-color: var(--edge); }

/* One gap value for the whole bar, shared with the anchors below: the picker reads as
   the last item in the same rhythm rather than something crowding the final anchor.
   The lockup keeps its distance regardless — `margin-left: auto` on the nav eats the
   remaining space. */
.bar { display: flex; align-items: center; gap: clamp(1.5rem, 5vw, 4.5rem); padding: 1rem 0; }

nav { margin-left: auto; display: flex; align-items: center; gap: clamp(1.5rem, 5vw, 4.5rem); }
nav a {
  color: var(--ink); text-decoration: none;
  font-size: 0.82rem; font-weight: 700;
  letter-spacing: 0.1em; text-transform: uppercase;
}
nav a:hover { color: var(--brand-deep); }

/* language picker — a subordinate control, not a nav item.
   A pill at the top right: quiet until you reach for it, then it lifts, the brand
   edge comes up and the caret turns over. Everything here is a state on the
   <details>, so the whole control still works with the script switched off. */

.lang { position: relative; }
.lang summary {
  list-style: none; cursor: pointer;
  display: inline-flex; align-items: center; gap: 0.4rem;
  font-size: 0.82rem; font-weight: 700; letter-spacing: 0.06em;
  color: var(--muted);
  border: 1px solid var(--edge); border-radius: 999px;
  padding: 0.34rem 0.6rem 0.34rem 0.65rem;
  background: var(--surface);
  box-shadow: 0 1px 2px rgba(22, 19, 17, 0.05);
  transition: color 0.18s ease, border-color 0.18s ease,
              box-shadow 0.18s ease, background-color 0.18s ease;
}
.lang summary::-webkit-details-marker { display: none; }
.lang summary:hover {
  color: var(--ink);
  border-color: color-mix(in srgb, var(--brand) 45%, var(--edge));
  box-shadow: 0 5px 16px rgba(22, 19, 17, 0.10);
}
.lang summary:focus-visible { outline: 2px solid var(--brand); outline-offset: 3px; }
.lang .caret {
  width: 0.75rem; height: 0.75rem; opacity: 0.55;
  transition: transform 0.18s ease, opacity 0.18s ease;
}
.lang[open] summary {
  color: var(--ink); border-color: var(--brand);
  background: color-mix(in srgb, var(--brand) 7%, var(--surface));
  box-shadow: 0 5px 16px rgba(22, 19, 17, 0.10);
}
.lang[open] .caret { transform: rotate(180deg); opacity: 1; }

.lang-menu {
  position: absolute; right: 0; top: calc(100% + 0.55rem); z-index: 40;
  background: var(--surface);
  border: 1px solid var(--edge); border-radius: 16px;
  padding: 0.35rem; min-width: 12.5rem;
  box-shadow: 0 20px 44px rgba(22, 19, 17, 0.16), 0 2px 6px rgba(22, 19, 17, 0.06);
  display: grid;
  /* Eighteen rows is taller than a phone viewport, so the menu scrolls rather than running
     off the bottom of the screen. Harmless at seven — the cap is never reached. */
  max-height: min(70vh, 30rem); overflow-y: auto; overscroll-behavior: contain;
  transform-origin: top right;
  animation: lang-pop 0.16s ease-out;
}
/* Past nine languages one column is a wall — build.py adds this class and the list runs in
   two. Multi-column rather than a two-column grid because the list is alphabetical: columns
   fill top-to-bottom and then continue in the next one, so the eye reads A–M down the left and
   N–Z down the right. A grid would lay the same list out sideways, one row at a time, which
   puts consecutive languages side by side and the alphabet nowhere. Below 24rem of room a
   second column would be narrower than the endonyms, so it drops back to one and relies on the
   scroll cap above. */
.lang-menu-wide { display: block; columns: 2; column-gap: 0.25rem; min-width: 24rem; }
.lang-menu-wide a { break-inside: avoid; }
@media (max-width: 30rem) {
  .lang-menu-wide { columns: 1; min-width: 12.5rem; }
}

@keyframes lang-pop {
  from { opacity: 0; transform: translateY(-4px) scale(0.97); }
  to { opacity: 1; transform: none; }
}
.lang-menu a {
  position: relative;
  display: flex; align-items: center; gap: 0.6rem;
  padding: 0.45rem 1.9rem 0.45rem 0.7rem; border-radius: 10px;
  color: var(--ink); text-decoration: none; font-size: 0.95rem;
  transition: background-color 0.12s ease, color 0.12s ease;
}
/* The language you are already reading is not a choice, so it does not light up
   under the pointer — hover means "clicking this changes something". A keyboard
   focus ring is different: that is position, not affordance, so it stays. */
.lang-menu a:not([aria-current]):hover,
.lang-menu a:not([aria-current]):focus-visible {
  background: color-mix(in srgb, var(--brand) 12%, var(--surface));
  color: var(--brand-deep);
}
.lang-menu a:not([aria-current]):focus-visible { outline: none; }
.lang-menu a[aria-current] { cursor: default; }
.lang-menu a[aria-current] { color: var(--brand-deep); font-weight: 700; }
/* The tick marks the language you are already reading — decorative, since
   aria-current already says it to a screen reader. */
.lang-menu a[aria-current]::after {
  content: "✓"; position: absolute; right: 0.7rem;
  color: var(--brand); font-weight: 700;
}
/* The picker's flags are sprite `<use>`s, not emoji — Segoe UI Emoji carries no flag
   glyphs, so Chrome and Edge on Windows drew the old emoji as "GB"/"DE" letter boxes.
   Sized off the row's own type so the artwork tracks the text it labels. */
.flag { width: 1.35em; height: auto; flex: none; border-radius: 2px; }
.lang summary .flag { width: 1.55em; }

@media (prefers-reduced-motion: reduce) {
  .lang summary, .lang .caret, .lang-menu a { transition: none; }
  .lang-menu { animation: none; }
}

/* -------------------------------------------------------------------- hero */

.hero { padding-top: clamp(3rem, 7vh, 6rem); }

.kicker {
  display: inline-block;
  font-size: 0.78rem; font-weight: 800;
  letter-spacing: 0.14em; text-transform: uppercase;
  color: var(--brand-deep);
  background: color-mix(in srgb, var(--brand) 12%, transparent);
  border-radius: 999px; padding: 0.4rem 0.95rem;
  margin-bottom: 2.5rem;
}

.hero-mark { font-size: clamp(3.5rem, 12vw, 9rem); }
.hero-tag {
  font-size: clamp(1.4rem, 2.8vw, 2.2rem);
  font-weight: 700; letter-spacing: -0.025em; line-height: 1.15;
  margin: 0.75rem 0 0;
}

.hero-sub {
  /* wide enough that the second line does not wrap and orphan "you." */
  margin: 2.5rem 0 0; max-width: 74ch; font-size: 1.2rem;
  color: var(--muted); white-space: pre-line; text-wrap: pretty;
}

.cta-row { display: flex; gap: 1rem; flex-wrap: wrap; margin-top: 2.5rem; }

.btn {
  display: inline-flex; align-items: center; gap: 0.65rem;
  padding: 0.95rem 1.6rem; border-radius: 14px;
  font: inherit; font-weight: 700; font-size: 1rem;
  text-decoration: none; cursor: pointer;
  color: var(--ink); border: none;
  background: var(--tg-blue);
}
.btn.wa { background: var(--wa-green); }
.btn svg { width: 20px; height: 20px; fill: #fff; }
.btn:hover { filter: brightness(1.05); }
.btn[data-live="false"] { background: var(--edge); color: var(--muted); }
.btn[data-live="false"] svg { fill: var(--muted); }
.dark .btn { color: var(--ink); }
.dark .btn[data-live="false"] {
  background: #252B25;
  color: color-mix(in srgb, var(--paper) 65%, transparent);
}
.dark .btn[data-live="false"] svg { fill: color-mix(in srgb, var(--paper) 65%, transparent); }

/* The launch date sits under both button rows. It is the one red on the page, so it
   reads as a notice rather than as part of the brand. */
.launch {
  margin: 1rem 0 0;
  color: #C0392B; font-weight: 700;
  font-size: 0.95rem; letter-spacing: 0.01em;
}
.dark .launch { color: #FF8A7A; }

/* -------------------------------------------------------- §2 claim band */

.claim-band { padding: clamp(3rem, 7vh, 5rem) 0; }
.band-head { font-size: clamp(1.8rem, 4.5vw, 3rem); margin-bottom: 1.75rem; }
.claims {
  display: flex; flex-wrap: wrap;
  gap: clamp(1rem, 5vw, 4rem);
  color: var(--muted); font-size: 1.1rem;
}
.claims b { color: var(--ink); font-weight: 700; }
.band-lede { margin: 1.75rem 0 0; font-size: 1.2rem; color: var(--muted); }

/* --------------------------------------------------------- dark sections */

/* The dark bands keep their punch but carry the signature green rather than plain
   ink, so a near-black bezel or chip has a hue to sit against. */
.dark { color: var(--paper); }
.dark h2, .dark h3 { color: var(--paper); }
.dark .lede { color: color-mix(in srgb, var(--paper) 62%, transparent); }
.dark .wordmark {
  --wm-track: var(--brand-lift); --wm-chat: var(--paper);
  --wm-fit: color-mix(in srgb, var(--paper) 62%, transparent);
}
.dark a { color: var(--brand-lift); }
.dark :focus-visible { outline-color: var(--brand-lift); }

/* A flat green field this tall reads as dead space, so every dark band carries a
   backdrop, and no two bands carry the same one — see web/make_backdrops.py for
   the five styles. The footer is the exception: it is short, and it sits directly
   under the FAQ, so it reads as the floor of that band rather than as a field of
   its own. Each graphic fades out on its own before its edges, so no seam
   shows where two bands meet. The band colour stays the last layer, under it. */
.dark {
  position: relative; isolation: isolate;
  background: var(--band);
  background-repeat: no-repeat;
  background-position: center;
  background-size: cover;
}
/* The ribbon keeps its own proportions and crosses the full width, so it stays a
   band of pixels rather than being blown up to cover a tall section. */
.dark[data-bg="ribbon"] { background-image: url(img/bg-ribbon.svg); background-size: 100% auto; }
.dark[data-bg="topo"] { background-image: url(img/bg-topo.svg); }
.dark[data-bg="flow"] { background-image: url(img/bg-flow.svg); }
.dark[data-bg="wave"] { background-image: url(img/bg-wave.svg); }
/* The only band whose height changes while you read it: every question opened grows
   it, and under `cover` that rescales the graphic, so the whole field slides down
   the page and back up again as questions open and close. Sizing by width alone and
   pinning to the top makes it independent of the band's height, and bg-ripple.svg is
   drawn tall enough to still reach the floor at that size. */
.dark[data-bg="ripple"] {
  background-image: url(img/bg-ripple.svg);
  background-size: 100% auto; background-position: center top;
}

/* The flagship band gets one soft halo behind the phone so the frame lifts off
   the lattice instead of lying flat on it. Only this band — repeated on all five
   it would stop reading as emphasis. */
#how::after {
  content: ""; position: absolute; inset: 0; z-index: -1; pointer-events: none;
  background: radial-gradient(
    circle at 72% 48%, color-mix(in srgb, var(--brand) 18%, transparent), transparent 62%);
}

/* ------------------------------------------------------------ layout bits */

.split {
  display: grid; grid-template-columns: 1.05fr 0.95fr;
  gap: clamp(2rem, 6vw, 5rem); align-items: center;
}

/* Columns are the width of a phone, not of the free space, so a caption always
   sits directly over the frame it labels. align-items:start keeps every caption
   on the same line and stops a short phone from being stretched by a tall one. */
/* Same column geometry and gap as .split, so the right-hand phones land on the
   exact rail as the centred phone in the section above. Each cell is phone-wide,
   which keeps a caption directly over the frame it labels; align-items:start puts
   every caption on one line and stops a short phone stretching to a tall one. */
.stagger {
  display: grid; grid-template-columns: 1.05fr 0.95fr;
  align-items: start;
  gap: clamp(2rem, 5vw, 3.5rem) clamp(2rem, 6vw, 5rem);
}
.stagger .cell:nth-child(even) { justify-self: center; }

figure.cell {
  margin: 0; width: min(350px, 100%);
  display: grid; justify-items: center;
  align-content: start; gap: 0.6rem;
}
figure.cell figcaption {
  text-align: center;
  font-size: 0.9rem; font-weight: 700;
  letter-spacing: 0.06em; text-transform: uppercase;
  color: var(--muted); order: -1;
}
figure.cell figcaption b { color: var(--brand-deep); text-transform: none; letter-spacing: 0; }
figure.cell sup a { text-decoration: none; }

.quote {
  font-size: clamp(1.4rem, 3vw, 2.1rem); font-weight: 700;
  letter-spacing: -0.025em; line-height: 1.3; margin: 1.5rem 0 0;
}
.quote em { color: var(--brand-lift); font-style: normal; }
.attrib { color: color-mix(in srgb, var(--paper) 62%, transparent); margin-top: 1.5rem; max-width: 44ch; }

/* --------------------------------------------------------- phone and chat */

.phone-slot { display: flex; justify-content: center; }

/* The frame is drawn, not rendered: a hairline-thin ink outline, a soft radius and
   one hard offset shadow with no blur — the chip sits on the page like a printed
   sticker rather than a photographed handset. Nothing about it moves on hover. */
.phone {
  width: 330px; max-width: 100%;
  border: 3px solid var(--ink);
  border-radius: 22px; background: #E6E3DC;
  padding: 1rem 0.9rem 1.15rem;
  box-shadow: 6px 6px 0 var(--ink);
}
/* On the dark bands an ink shadow disappears into the band, so it deepens to black. */
.dark .phone { box-shadow: 6px 6px 0 rgba(0, 0, 0, 0.45); }
.phone .notch {
  width: 74px; height: 6px; border-radius: 99px;
  background: var(--ink); margin: 0 auto 0.9rem;
}
.phone[data-channel="whatsapp"] { background: #E7DED4; }

.screen { display: flex; flex-direction: column; }

.bubble {
  max-width: 100%; padding: 0.6rem 0.8rem; border-radius: 14px;
  font-size: 0.95rem; line-height: 1.45; margin-bottom: 0.6rem;
  box-shadow: 0 1px 1px rgba(22, 19, 17, 0.08);
  white-space: pre-line; color: var(--ink);
}
.bubble.me { align-self: flex-end; max-width: 90%; background: #D6F5DF; }
/* Replies stretch instead of shrinking to their text, so a one-line confirmation and a
   three-food log are the same width — a grid of chips reads as one set, not a ragged edge.
   The margin is what a 90%-wide bubble used to leave, so the inset from the frame is unchanged. */
.bubble.bot {
  align-self: stretch; margin-right: 1.7rem;
  background: var(--surface);
}
.shot {
  display: block; width: 170px; max-width: 100%; aspect-ratio: 4 / 3;
  border-radius: 9px; margin-bottom: 0.35rem;
  background: linear-gradient(140deg, #C9D6C2, #E6DCC8 55%, #D8C6AE);
  position: relative;
}
.shot::after {
  content: "📷"; position: absolute; inset: 0;
  display: grid; place-items: center; font-size: 2.2rem; opacity: 0.72;
}
/* A real photo gets a hairline edge so it reads as a picture, not a hole in the
   bubble. `:last-child` drops the trailing gap the caption-less photo does not need,
   so the frame sits centred in its bubble. */
/* A real photo keeps its own shape — the 4/3 above is the placeholder's. */
img.shot { background: none; object-fit: cover; height: auto; aspect-ratio: auto;
  border: 1px solid rgba(22, 19, 17, 0.16); }
img.shot::after { content: none; }
.bubble > .shot:last-child { margin-bottom: 0; }

.shot-label {
  width: 130px; aspect-ratio: 3 / 4;
  background: linear-gradient(140deg, #E4E2DC, #F1EFE9 55%, #DCD8CE);
}
.shot-label::after { content: "🏷️"; }

/* ------------------------------------------------ the input wall (§6) */

/* The 1px gap is the background showing through, which is what draws the rules
   between the tiles without a border on each one. */
.grid-io {
  display: grid; grid-template-columns: repeat(auto-fill, minmax(300px, 1fr));
  gap: 1px; background: var(--edge); border: 1px solid var(--edge);
  border-radius: var(--radius); overflow: hidden;
}
.io { background: var(--surface); padding: 1rem 1.15rem; }
.io .in {
  font-family: var(--mono); font-size: 0.85rem;
  color: var(--muted); margin: 0;
}
.io .out { font-size: 0.92rem; margin: 0.4rem 0 0; color: var(--ink); }

/* ------------------------------------------------------- chips (§5) */

/* Every showcase on the page is a phone, these included — three of them across,
   each one a single exchange under its own label. */
.chip-grid {
  display: grid; grid-template-columns: repeat(auto-fit, minmax(260px, 1fr));
  gap: clamp(2rem, 4vw, 3rem); justify-items: center;
}
/* With no grille at the top, the frame's own padding is the only top margin — so the
   last bubble drops its trailing gap and the padding evens out. */
.chip-grid .phone { padding: 1rem 0.9rem; }
.chip-grid .bubble:last-child { margin-bottom: 0; }
/* A reply that runs to two lines would otherwise leave its neighbours short — every
   frame takes the tallest one's height and centres its exchange inside. */
.chip-grid { align-items: stretch; grid-auto-rows: 1fr; }
.chip-grid figure.cell { height: 100%; align-content: stretch; grid-template-rows: auto 1fr; }
.chip-grid .phone { display: flex; flex-direction: column; }
.chip-grid .screen { flex: 1; justify-content: flex-start; }

/* Six summary showcases across — same framed phone, but each reply is a different
   length. Frames stretch to the tallest in their own row, so a row reads as one
   band; rows size independently, which lets a short-reply row stay short. */
.summary-grid {
  display: grid; grid-template-columns: repeat(auto-fit, minmax(260px, 1fr));
  gap: clamp(2rem, 4vw, 3rem); justify-items: center; align-items: stretch;
}
.summary-grid figure.cell { height: 100%; align-content: stretch; grid-template-rows: auto 1fr; }
/* The memory pair is one before/after, so it stays two across and centred rather than
   spreading over the auto-fit track count a wide band would allow. */
.summary-grid.duo {
  grid-template-columns: repeat(2, minmax(0, 350px));
  justify-content: center; column-gap: clamp(2.5rem, 9vw, 7rem);
}
.summary-grid .phone { display: flex; flex-direction: column; }
.summary-grid .screen { flex: 1; justify-content: flex-start; }

/* The captions label a light frame on a dark band, so they read in paper, not ink. */
.dark figure.cell figcaption { color: color-mix(in srgb, var(--paper) 70%, transparent); }
.dark .footnote { color: color-mix(in srgb, var(--paper) 62%, transparent); }

/* --------------------------------------------------------- §8 dashboard */

/* Real dashboard screenshots, one per tab. The grid holds a single shot today and takes
   more without changing anything; each sits in the same figure/caption frame the chat
   showcases use, so a screenshot and a chat chip read as the same kind of exhibit. */
.shot-grid {
  display: grid; grid-template-columns: repeat(auto-fit, minmax(260px, 1fr));
  gap: clamp(2rem, 4vw, 3rem); justify-items: center; margin-top: 3rem;
}
/* Seven shots divide as 3 + 3 + 1 (2 + 2 + 2 + 1 on a narrower screen): the odd one out
   takes the whole last row and centres itself, rather than hanging off the left edge. */
.shot-grid figure.cell:last-child { grid-column: 1 / -1; justify-self: center; }
/* The shot is a phone screen, so it keeps a phone's rounded corners and lift off the page.
   `height: auto` lets the intrinsic width/height attributes reserve the right box while the
   image scales — no reflow when it loads. */
/* The shot goes in the site's own phone frame — the same ink border the chat chips wear —
   so a screenshot and a chat exchange read as the same device. The frame's padding and
   bubble background are dropped: this screen is the phone's whole display (status bar and
   all, only Safari's bottom bar cut off), so it runs edge to edge inside the border. */
.phone-shot { padding: 0; background: none; overflow: hidden; }
.dash-shot { display: block; width: 100%; height: auto; border-radius: 16px; }

.panel {
  background: var(--surface); border: 1px solid var(--edge);
  border-radius: 22px; overflow: hidden;
  box-shadow: 0 20px 50px rgba(22, 19, 17, 0.08);
}
.tabstrip {
  display: flex; gap: 0.25rem; overflow-x: auto;
  padding: 0.9rem 1rem; border-bottom: 1px solid var(--edge);
  background: var(--paper);
}
.tab {
  flex: none; padding: 0.35rem 0.85rem; border-radius: 999px;
  font-size: 0.85rem; font-weight: 700; color: var(--muted);
  border: 1px solid transparent; white-space: nowrap;
}
.tab.on { background: var(--surface); border-color: var(--edge); color: var(--ink); }

.panel-body {
  display: grid; grid-template-columns: 1.3fr 1fr;
  gap: 2rem; padding: 1.75rem; align-items: start;
}
.viz { margin: 0; }
.viz svg { width: 100%; height: auto; display: block; }
.viz figcaption { color: var(--muted); font-size: 0.85rem; margin-top: 0.75rem; }
.viz .grid line { stroke: var(--edge); stroke-width: 1; }
.viz .series { fill: none; stroke: var(--brand); stroke-width: 2.5; stroke-linejoin: round; }
.viz .trend { fill: none; stroke: var(--brand-soft); stroke-width: 6; stroke-linecap: round; }
.viz .dots circle { fill: var(--surface); stroke: var(--brand); stroke-width: 2.5; }

.cal { display: grid; grid-template-columns: repeat(7, 1fr); gap: 6px; max-width: 320px; }
.cal .d {
  aspect-ratio: 1; border-radius: 8px; background: var(--paper);
  border: 1px solid var(--edge); position: relative;
}
.cal .d::after {
  content: ""; position: absolute; inset: 0; margin: auto;
  width: 0; height: 0; border-radius: 50%;
}
.cal .s1::after { width: 26%; height: 26%; }
.cal .s2::after { width: 42%; height: 42%; }
.cal .s3::after { width: 58%; height: 58%; }
.cal .c1::after { background: var(--brand); }
.cal .c2::after { background: #D97706; }
.cal .c3::after { background: #2563EB; }
.cal .lift::before {
  content: "🏋️"; position: absolute; inset: 0;
  display: grid; place-items: center; font-size: 0.7rem;
}

.note-line { color: var(--muted); max-width: 58ch; margin-top: 2rem; }
.note-line b { color: var(--ink); font-family: var(--mono); font-size: 0.92em; }

/* ------------------------------------------------------------ §9 versus */

.versus { display: grid; grid-template-columns: 1fr 1fr; gap: clamp(1.5rem, 5vw, 4rem); }
.col h3 { margin-bottom: 1.25rem; }
/* Our column is headed by the lockup, so the heading is a logo, not a word — it takes
   the heading's own size rather than the header's. */
.brand-head .lockup { font-size: inherit; }
.brand-head .mark { width: 1.1em; height: 1.1em; }
.col.old h3 { color: var(--muted); }
.col.new h3 { color: var(--brand-deep); }
.steps { list-style: none; counter-reset: step; padding: 0; margin: 0; }
.steps li {
  counter-increment: step; position: relative;
  padding: 0.55rem 0 0.55rem 2.6rem;
  border-bottom: 1px solid var(--edge);
  font-size: 1.05rem;
}
.steps li::before {
  content: counter(step); position: absolute; left: 0; top: 0.55rem;
  width: 1.8rem; height: 1.8rem; border-radius: 50%;
  display: grid; place-items: center;
  font-size: 0.8rem; font-weight: 800;
  background: var(--edge); color: var(--muted);
}
.col.new .steps li::before { background: var(--brand); color: #fff; }

/* On the band the step list has to be redrawn: the light rules paint hairlines and
   number chips in --edge, which on dark reads as a row of bright dashes. */
.dark .col.old h3 { color: color-mix(in srgb, var(--paper) 55%, transparent); }
.dark .steps li { border-bottom-color: color-mix(in srgb, var(--paper) 16%, transparent); }
.dark .steps li::before {
  background: color-mix(in srgb, var(--paper) 14%, transparent);
  color: color-mix(in srgb, var(--paper) 70%, transparent);
}
.dark .col.new .steps li::before { background: var(--brand); color: #fff; }

/* ----------------------------------------------------------- §11 price */

.cards { display: grid; grid-template-columns: 1fr 1fr; gap: 1.5rem; }
.card {
  background: var(--surface); border: 1px solid var(--edge);
  border-radius: 20px; padding: 2rem;
}
.card.hi { border-color: var(--brand); box-shadow: 0 0 0 3px color-mix(in srgb, var(--brand) 14%, transparent); }
.card h3 {
  font-size: 0.8rem; font-weight: 800;
  letter-spacing: 0.12em; text-transform: uppercase; color: var(--muted);
}
.price { font-size: 2.6rem; font-weight: 800; letter-spacing: -0.035em; margin: 0.5rem 0 0; }
.price small { font-size: 1rem; font-weight: 600; color: var(--muted); letter-spacing: 0; }
.price-alt { color: var(--muted); font-size: 0.98rem; margin: 0.25rem 0 0; }
/* The free card has no yearly line, so it reserves the same height instead: the two
   feature lists then start on the same baseline and their rows read across. */
.price-alt.spacer { visibility: hidden; }
/* Premium lists only what Free does not already have, so it opens on the inheritance
   line every tiered pricing table uses. The free card reserves the same height with a
   hidden twin, keeping both tick lists on one baseline. */
.inherits {
  margin: 1.5rem 0 0; padding-left: 1.7rem; position: relative; font-size: 1rem;
}
.inherits::before {
  content: "✓"; position: absolute; left: 0; top: 0;
  color: var(--brand-deep); font-weight: 800;
}
.inherits.spacer { visibility: hidden; }
.ticks { list-style: none; padding: 0; margin: 1.5rem 0 0; }
.inherits + .ticks { margin-top: 0.6rem; }
.ticks li { padding-left: 1.7rem; position: relative; margin-bottom: 0.6rem; font-size: 1rem; }
/* Every row on the premium card is a row Free does not have, so they all carry the weight. */
.card.hi .ticks li { font-weight: 700; }
.ticks li::before {
  content: "✓"; position: absolute; left: 0; top: 0;
  color: var(--brand-deep); font-weight: 800;
}
.price-claim {
  font-size: clamp(1.5rem, 3.4vw, 2.4rem); font-weight: 800;
  letter-spacing: -0.03em; line-height: 1.2;
  margin: 3.5rem 0 0; max-width: 20ch;
}

/* ------------------------------------------------------------- §13 FAQ */

.faq details {
  border-bottom: 1px solid var(--edge);
}
.faq summary {
  cursor: pointer; list-style: none;
  padding: 1.25rem 2rem 1.25rem 0; position: relative;
  font-size: 1.15rem; font-weight: 700; letter-spacing: -0.015em;
}
.faq summary::-webkit-details-marker { display: none; }
.faq summary::after {
  content: "+"; position: absolute; right: 0; top: 1.05rem;
  font-size: 1.5rem; font-weight: 400; color: var(--brand-deep);
}
.faq details[open] summary::after { content: "–"; }
.faq p { color: var(--muted); margin: 0 0 1.5rem; max-width: 62ch; }

/* The FAQ runs straight into the closing band, so it is dark too — pricing then
   sits alone on paper between two dark zones instead of merging into the
   questions. The rules below are only the four colours the light version bakes
   in; everything else already comes from .dark. */
.dark .faq details { border-bottom-color: color-mix(in srgb, var(--paper) 18%, transparent); }
.dark .faq summary::after { color: var(--brand-lift); }
.dark .faq p { color: color-mix(in srgb, var(--paper) 62%, transparent); }
.dark .faq p b { color: var(--paper); }

.faq details:last-child { border-bottom: none; }

/* --------------------------------------------------------- §14 footer */

/* One band, questions through copyright, so the ripple runs behind all of it. The
   sign-off gets an even 4rem of air on each side rather than a full section pad —
   it closes the band it is already in, it does not open a new one. */
.closing { padding: var(--pad) 0 3.5rem; }
/* line-height: 1 rather than the body's 1.7: at this size the inherited leading is
   ~1.2rem of invisible space on each side of the words, which throws the sign-off
   off-centre once the last question's own padding is added above it. With the
   leading gone the two margins below are the whole of the gap, and the extra
   1.5rem under the motto answers that padding. */
.motto {
  font-size: clamp(2rem, 5vw, 3.5rem); font-weight: 800;
  letter-spacing: -0.03em; line-height: 1; margin: 3.5rem 0 0;
  color: var(--brand-lift);
}
.foot {
  margin-top: 5rem;
  display: grid; gap: 1.25rem;
}
.foot-mark { font-size: 1.6rem; }
.foot-links { display: flex; gap: 2rem; flex-wrap: wrap; }
.foot-links a {
  color: color-mix(in srgb, var(--paper) 60%, transparent);
  text-decoration: none; font-size: 0.95rem;
}
.foot-links a:hover { color: var(--paper); }
.foot-contact, .foot-copy {
  color: color-mix(in srgb, var(--paper) 60%, transparent); font-size: 0.95rem;
}
/* ------------------------------------------------- terms / legal document
   The page is generated by web/terms_build.py from web/src/terms/<lang>.md —
   the words are legal-review-gated, the styling below is the brand's, and it
   has to hold for every language the document is ever translated into. */

/* The lockup, and — once the document exists in more than one language — the switcher,
   which is the landing page's picker pill with real hrefs instead of a cookie: this is the
   one page that has to work with no JavaScript at all. */
.doc-head {
  display: flex; align-items: center; justify-content: space-between; gap: 1rem;
  width: min(720px, 90vw); margin: 0 auto; padding: 2rem 0 0;
}

.doc {
  width: min(720px, 90vw); margin: 0 auto;
  padding: 3rem 0 7rem;
  font-size: 1.0625rem;
}
.doc h1 { font-size: clamp(1.8rem, 4vw, 2.6rem); margin-bottom: 0.5rem; }
.doc h2 { font-size: 1.35rem; margin: 3rem 0 0.6rem; }
.doc h3 { font-size: 1.08rem; margin: 2rem 0 0.4rem; }
.doc p, .doc li { margin: 0.6rem 0; }
.doc ul, .doc ol { padding-left: 1.3rem; }
.doc ol { padding-left: 1.5rem; }
.doc li::marker { color: var(--brand); }
/* The effective date stands apart from the prose that follows it — the same distance the
   opening paragraphs keep from the contents box below them, so the document opens on one
   rhythm rather than on a rule. */
.doc .meta { color: var(--muted); font-size: 0.98rem; margin-bottom: 2.5rem; }
/* The name, set like the logo where the document introduces it: green "track", ink "chat".
   The palette tokens directly, not the wordmark's --wm-* pair: those are declared on
   `.wordmark` for the sprite's shadow tree and do not reach text elsewhere on the page.
   Only terms_build.py's first mention carries the class. */
.doc .brand { color: var(--ink); }
.doc .brand span { color: var(--brand); }
.doc .meta strong { color: var(--ink); }
.doc address { font-style: normal; margin: 0.5rem 0 0; }
.doc hr { border: none; border-top: 1px solid var(--edge); margin: 2.8rem 0; }
.doc footer { color: var(--muted); font-size: 0.92rem; }
/* The Twemoji credit. CC-BY's one condition, so it is fine print but not optional —
   see web/src/flags/README.md before touching it. */
.doc .credits { font-size: 0.82rem; margin-top: 1.2rem; }

/* The generated contents list. A long legal document needs a way in, and the
   entries are numbered by the <ol> itself so the markup carries no numbers a
   translation could leave stale. */
/* The header's bare `nav` / `nav a` selectors would otherwise claim this one and lay it
   out as a row of uppercase bar links, so the block undoes both here. */
.doc-toc {
  display: block; margin: 2.5rem 0 0; padding: 1.25rem 1.5rem;
  border: 1px solid var(--edge); border-radius: 14px;
}
.doc-toc-title {
  margin: 0 0 0.6rem; font-weight: 600; font-size: 0.95rem;
  text-transform: uppercase; letter-spacing: 0.06em; color: var(--muted);
}
.doc-toc ul { margin: 0; padding-left: 0; list-style: none; }
.doc-toc li { margin: 0.25rem 0; }
.doc-toc a {
  color: var(--ink); text-decoration: none;
  font-size: inherit; font-weight: inherit; letter-spacing: normal; text-transform: none;
}
.doc-toc a:hover { color: var(--brand); text-decoration: underline; }
/* An in-page jump must not land the heading under the top edge of the viewport. */
.doc h2 { scroll-margin-top: 1.5rem; }

/* ------------------------------------------------------------ 404 page */

.page-404 { display: grid; min-height: 100vh; place-items: center; text-align: center; }
.page-404 main { display: grid; justify-items: center; gap: 1.5rem; }
.page-404 h1 { font-size: clamp(2.5rem, 8vw, 5rem); }
.page-404 .lede { margin: 0; }

/* -------------------------------------------------------------- motion */

@media (prefers-reduced-motion: reduce) {
  html { scroll-behavior: auto; }
}

/* ---------------------------------------------------------- responsive */

@media (max-width: 900px) {
  .split, .versus, .cards, .panel-body { grid-template-columns: 1fr; }
  #how::after { background: radial-gradient(
    circle at 50% 62%, color-mix(in srgb, var(--brand) 18%, transparent), transparent 62%); }
  .bar, nav { gap: 1.25rem; }
  nav a { font-size: 0.75rem; letter-spacing: 0.06em; }
}

@media (max-width: 620px) {
  body { font-size: 1.0625rem; }
  /* The bar becomes two rows: mark + picker, then the anchors. Three uppercase
     anchors and a picker cannot share 360 px without forcing the page wider. */
  .bar { flex-wrap: wrap; gap: 0.5rem 0.75rem; }
  nav { order: 3; width: 100%; margin-left: 0; justify-content: space-between; gap: 0.5rem; }
  .lang { margin-left: auto; }
  /* Header only — the footer lockup has the room and is the last thing on the page. */
  .bar .lockup .wordmark { display: none; }
  .claims { gap: 0.5rem 1.5rem; }
  .phone { width: 100%; }
  .stagger { grid-template-columns: minmax(0, 1fr); justify-content: stretch; }
  .grid-io, .chip-grid, .summary-grid, .summary-grid.duo { grid-template-columns: minmax(0, 1fr); }
  .price-claim { max-width: none; }
}
