/* ============================================================================
   Kezi — Editorial Ink on the web.

   The same design language as the app (app/lib/ui/theme.dart), ported rather
   than reinvented: the page IS the paper. Content sits directly on a warm
   off-white ground, separated by hairline rules and whitespace — never boxed
   cards, never elevation, never a gradient. Two voices only: Fraunces for
   display, the platform sans for body. One accent, seal red (印泥), used for
   the single thing that matters in each band.

   Structure borrows from print, not from the web: a left gutter rail carrying
   folio numbers and marginalia, a hairline running the length of the page, and
   section rules that behave like the rules in a book.
   ========================================================================= */

/* --- Fonts --------------------------------------------------------------- */
/* Both are the app's own subsets, converted to woff2 by build/build.sh.
   OFL requires the licence to travel with them — see /licenses.html. */
@font-face {
  font-family: 'Fraunces';
  src: url('fonts/Fraunces-Kezi.woff2') format('woff2');
  font-weight: 100 900; /* the wght axis is kept variable, as in the app */
  font-style: normal;
  font-display: swap;
}
/* Only the hanzi that appear in the markup — a couple of dozen KB, so the
   page's typographic centrepiece is not waiting on a 331 KB download. */
@font-face {
  font-family: 'Kezi Han';
  src: url('fonts/Hanzi-Page.woff2') format('woff2');
  font-weight: 600;
  font-display: swap;
}
/* The full 1,825-glyph set, for text the visitor pastes. Fetched by kezi.js
   when the reading check comes into view, never on load. */
@font-face {
  font-family: 'Kezi Han Full';
  src: url('fonts/Hanzi-Full.woff2') format('woff2');
  font-weight: 600;
  font-display: swap;
}

/* --- Tokens -------------------------------------------------------------- */
:root {
  /* The palette, spelled out once. Day and night values live here and nowhere
     else; the blocks below only ever choose between them, so a colour cannot
     drift between the two themes. Both sets are the app's own — see
     KeziColors in app/lib/ui/theme.dart. */
  --day-paper: #faf6ee;
  --day-paper-raised: #fffdf8;
  --day-paper-edge: #efe7d6;
  --day-ink: #211e1a;
  --day-ink-muted: #897f72;
  --day-hairline: #e6decf;
  --day-seal: #b3272d;
  --day-seal-soft: rgba(179, 39, 45, 0.09);
  --day-gold: #b8863b;

  --night-paper: #14120f;
  --night-paper-raised: #24201a;
  --night-paper-edge: #24201a;
  --night-ink: #ede6da;
  --night-ink-muted: #9a9082;
  --night-hairline: #37322a;
  /* The deep seal red goes muddy on near-black, so night lifts to cinnabar. */
  --night-seal: #d8453f;
  --night-seal-soft: rgba(216, 69, 63, 0.12);
  --night-gold: #cba25a;

  --paper: var(--day-paper);
  --paper-raised: var(--day-paper-raised);
  --paper-edge: var(--day-paper-edge);
  --ink: var(--day-ink);
  --ink-muted: var(--day-ink-muted);
  --hairline: var(--day-hairline);
  --seal: var(--day-seal);
  --seal-soft: var(--day-seal-soft);
  --gold: var(--day-gold);

  --serif: 'Fraunces', 'Iowan Old Style', Georgia, serif;
  --sans: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue',
    'Noto Sans', sans-serif;
  /* Songti/Source Han Serif are the closest system stand-ins while the subset
     loads, so the swap does not change the texture of the page. */
  --han: 'Kezi Han', 'Songti SC', 'Source Han Serif SC', 'Noto Serif CJK SC',
    'Noto Serif SC', serif;
  --han-full: 'Kezi Han Full', var(--han);

  /* Wide enough that a Vietnamese marginal note sets two or three words to the
     line rather than one — any narrower and it reads as a column of fragments. */
  --rail: 178px;
  --gap: clamp(28px, 5vw, 64px);
  --band: clamp(72px, 9vw, 132px); /* vertical rhythm between bands */

  color-scheme: light dark;
}

/* Night, selected in two places on purpose:
   • the media query, so the operating system's preference is honoured before
     any script runs and for anyone browsing without JavaScript;
   • the attribute, so the toggle in the masthead can overrule the OS.
   The mapping is repeated; no colour is. */
@media (prefers-color-scheme: dark) {
  :root:not([data-theme='light']) {
    --paper: var(--night-paper);
    --paper-raised: var(--night-paper-raised);
    --paper-edge: var(--night-paper-edge);
    --ink: var(--night-ink);
    --ink-muted: var(--night-ink-muted);
    --hairline: var(--night-hairline);
    --seal: var(--night-seal);
    --seal-soft: var(--night-seal-soft);
    --gold: var(--night-gold);
    color-scheme: dark;
  }
}
:root[data-theme='dark'] {
  --paper: var(--night-paper);
  --paper-raised: var(--night-paper-raised);
  --paper-edge: var(--night-paper-edge);
  --ink: var(--night-ink);
  --ink-muted: var(--night-ink-muted);
  --hairline: var(--night-hairline);
  --seal: var(--night-seal);
  --seal-soft: var(--night-seal-soft);
  --gold: var(--night-gold);
  color-scheme: dark;
}
:root[data-theme='light'] {
  color-scheme: light;
}

/* --- Reset --------------------------------------------------------------- */
*,
*::before,
*::after {
  box-sizing: border-box;
}
html {
  -webkit-text-size-adjust: 100%;
  scroll-behavior: smooth;
}
@media (prefers-reduced-motion: reduce) {
  html {
    scroll-behavior: auto;
  }
}
body {
  margin: 0;
  background: var(--paper);
  color: var(--ink);
  font-family: var(--sans);
  font-size: 17px;
  line-height: 1.55;
  letter-spacing: 0.1px;
  -webkit-font-smoothing: antialiased;
  text-rendering: optimizeLegibility;
}
img {
  max-width: 100%;
  display: block;
}
a {
  color: inherit;
  text-decoration-color: var(--hairline);
  text-underline-offset: 3px;
}
a:hover {
  text-decoration-color: var(--seal);
}
button,
input,
textarea {
  font: inherit;
  color: inherit;
}
::selection {
  background: var(--seal-soft);
}
:focus-visible {
  outline: 2px solid var(--seal);
  outline-offset: 3px;
  border-radius: 2px;
}

/* --- Type ---------------------------------------------------------------- */
h1,
h2,
h3 {
  font-family: var(--serif);
  font-weight: 500;
  letter-spacing: -0.01em;
  margin: 0;
  text-wrap: balance;
}
h1 {
  /* Capped well below the column width: the Vietnamese headline is long, and
     at 84px it broke to four lines with one word stranded on the last. */
  font-size: clamp(38px, 4.9vw, 66px);
  line-height: 1.02;
  font-variation-settings: 'wght' 480;
}
h2 {
  font-size: clamp(30px, 3.8vw, 46px);
  line-height: 1.04;
  font-variation-settings: 'wght' 500;
}
h3 {
  font-size: clamp(20px, 2vw, 24px);
  line-height: 1.12;
  font-variation-settings: 'wght' 540;
}
p {
  margin: 0 0 1em;
}
.lede {
  font-size: clamp(18px, 1.7vw, 21px);
  line-height: 1.5;
  color: var(--ink-muted);
  max-width: 34em;
}
.muted {
  color: var(--ink-muted);
}
.seal-text {
  color: var(--seal);
}
.eyebrow {
  font-size: 11.5px;
  font-weight: 600;
  letter-spacing: 0.17em;
  text-transform: uppercase;
  color: var(--ink-muted);
  margin: 0 0 18px;
}
.note {
  font-size: 13.5px;
  line-height: 1.5;
  color: var(--ink-muted);
}
/* Editorial figures: the app sets every number that carries an argument in
   Fraunces, and so does the page. */
.figure {
  font-family: var(--serif);
  font-variation-settings: 'wght' 460;
  line-height: 0.9;
  letter-spacing: -0.02em;
  font-feature-settings: 'tnum' 1;
}

/* --- Sheet & rail -------------------------------------------------------- */
.sheet {
  max-width: 1180px;
  margin: 0 auto;
  padding: 0 clamp(20px, 4vw, 44px);
}
.band {
  padding: var(--band) 0;
  border-top: 1px solid var(--hairline);
}
.band--tight {
  padding: calc(var(--band) * 0.6) 0;
}
.grid {
  display: grid;
  gap: var(--gap);
}
@media (min-width: 900px) {
  .grid--rail {
    grid-template-columns: var(--rail) minmax(0, 1fr);
    gap: 0;
  }
  .grid--rail > .col {
    /* The hairline that runs the length of the page — the single structural
       device the whole layout hangs off. */
    border-left: 1px solid var(--hairline);
    padding-left: var(--gap);
    min-width: 0;
  }
}
.rail {
  display: flex;
  flex-direction: column;
  gap: 20px;
  padding-right: 20px;
}
.folio {
  font-family: var(--serif);
  font-size: 13px;
  font-variation-settings: 'wght' 500;
  color: var(--ink-muted);
  letter-spacing: 0.06em;
}
.marginalia {
  display: none;
}
@media (min-width: 1140px) {
  .marginalia {
    display: block;
    font-size: 12.5px;
    line-height: 1.5;
    color: var(--ink-muted);
    text-align: right;
    margin: 0;
    text-wrap: pretty;
  }
}
@media (max-width: 899px) {
  .rail {
    flex-direction: row;
    align-items: baseline;
    gap: 12px;
    padding: 0;
  }
}

/* --- Brand marks --------------------------------------------------------- */
/* A seal chop: vermilion square, character in relief, set a couple of degrees
   off true the way a hand-pressed chop lands. */
.chop {
  display: inline-grid;
  place-items: center;
  width: 1em;
  height: 1em;
  font-size: 30px;
  background: var(--seal);
  color: #faf6ee;
  font-family: var(--han);
  font-size: 30px;
  line-height: 1;
  border-radius: 3px;
  transform: rotate(-2.5deg);
  box-shadow: inset 0 0 0 1px rgba(0, 0, 0, 0.16);
  user-select: none;
}
.chop > span {
  font-size: 0.62em;
  transform: translateY(0.02em);
}
.chop--lg {
  font-size: 76px;
  border-radius: 5px;
}

/* 米字格 — the rice grid the app uses for writing practice, drawn in CSS. */
.ricegrid {
  position: relative;
  aspect-ratio: 1;
  display: grid;
  place-items: center;
  border: 1px solid var(--hairline);
  overflow: hidden;
}
.ricegrid i {
  position: absolute;
  background: none;
  border-color: var(--hairline);
  pointer-events: none;
}
.ricegrid .rg-v {
  left: 50%;
  top: 0;
  bottom: 0;
  border-left: 1px dashed var(--hairline);
}
.ricegrid .rg-h {
  top: 50%;
  left: 0;
  right: 0;
  border-top: 1px dashed var(--hairline);
}
.ricegrid .rg-d1,
.ricegrid .rg-d2 {
  left: 50%;
  top: -20.8%;
  height: 141.6%;
  border-left: 1px dashed var(--hairline);
  transform-origin: center;
}
.ricegrid .rg-d1 {
  transform: rotate(45deg);
}
.ricegrid .rg-d2 {
  transform: rotate(-45deg);
}
.ricegrid .glyph {
  font-family: var(--han);
  font-weight: 600;
  font-size: 0.68em;
  line-height: 1;
  position: relative;
}

.han {
  font-family: var(--han);
  font-weight: 600;
}

/* --- Header -------------------------------------------------------------- */
.masthead {
  position: sticky;
  top: 0;
  z-index: 40;
  background: color-mix(in srgb, var(--paper) 88%, transparent);
  backdrop-filter: saturate(150%) blur(8px);
  border-bottom: 1px solid var(--hairline);
}
.masthead .sheet {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 20px;
  height: 66px;
}
.wordmark {
  display: inline-flex;
  align-items: center;
  gap: 11px;
  text-decoration: none;
  font-family: var(--serif);
  font-size: 21px;
  font-variation-settings: 'wght' 560;
  letter-spacing: -0.01em;
}
.wordmark .chop {
  font-size: 26px;
}
.nav {
  display: flex;
  align-items: center;
  gap: clamp(14px, 2.4vw, 30px);
  font-size: 13.5px;
  font-weight: 550;
}
.nav a {
  text-decoration: none;
  color: var(--ink-muted);
  white-space: nowrap;
}
.nav a:hover {
  color: var(--ink);
}
@media (max-width: 720px) {
  .nav .nav-hide {
    display: none;
  }
}
.lang {
  display: inline-flex;
  align-items: center;
  gap: 7px;
  padding-left: clamp(10px, 2vw, 18px);
  border-left: 1px solid var(--hairline);
  font-size: 12px;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  font-weight: 650;
}
.lang [aria-current='true'] {
  color: var(--ink);
  text-decoration: none;
}
.lang .sep {
  color: var(--hairline);
}

/* Theme toggle. Two glyphs live in the markup and CSS picks one, so the button
   never needs the script to redraw it — and it shows the theme you would
   *switch to*, not the one you are in, which is the only reading that makes a
   single button unambiguous. */
.theme-toggle {
  display: inline-grid;
  place-items: center;
  width: 32px;
  height: 32px;
  padding: 0;
  margin-left: clamp(6px, 1.4vw, 12px);
  border: 1px solid transparent;
  border-radius: 9px;
  background: transparent;
  color: var(--ink-muted);
  cursor: pointer;
  transition: color 0.16s ease, border-color 0.16s ease;
}
.theme-toggle:hover {
  color: var(--ink);
  border-color: var(--hairline);
}
.theme-toggle svg {
  width: 17px;
  height: 17px;
  display: block;
}
/* Light now → offer night. */
.theme-toggle .to-dark {
  display: block;
}
.theme-toggle .to-light {
  display: none;
}
@media (prefers-color-scheme: dark) {
  :root:not([data-theme='light']) .theme-toggle .to-dark {
    display: none;
  }
  :root:not([data-theme='light']) .theme-toggle .to-light {
    display: block;
  }
}
:root[data-theme='dark'] .theme-toggle .to-dark {
  display: none;
}
:root[data-theme='dark'] .theme-toggle .to-light {
  display: block;
}
/* An explicit choice beats the OS, including when the OS says dark. */
:root[data-theme='light'] .theme-toggle .to-dark {
  display: block;
}
:root[data-theme='light'] .theme-toggle .to-light {
  display: none;
}

/* --- Buttons ------------------------------------------------------------- */
.btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 9px;
  padding: 15px 24px;
  border-radius: 14px; /* the app's FilledButton radius */
  border: 1px solid transparent;
  background: var(--seal);
  color: #faf6ee;
  font-size: 15px;
  font-weight: 600;
  letter-spacing: 0.2px;
  text-decoration: none;
  cursor: pointer;
  transition: transform 0.16s ease, background 0.16s ease;
}
.btn:hover {
  background: color-mix(in srgb, var(--seal) 88%, #000);
  transform: translateY(-1px);
}
.btn:active {
  transform: none;
}
.btn--ghost {
  background: transparent;
  color: var(--ink);
  border-color: var(--hairline);
}
.btn--ghost:hover {
  background: transparent;
  border-color: var(--ink-muted);
}
.actions {
  display: flex;
  flex-wrap: wrap;
  gap: 12px;
  align-items: center;
}

/* --- Hero ---------------------------------------------------------------- */
.hero {
  padding: clamp(56px, 8vw, 104px) 0 clamp(44px, 6vw, 76px);
}
.hero-grid {
  display: grid;
  gap: clamp(40px, 6vw, 72px);
  align-items: center;
}
@media (min-width: 940px) {
  .hero-grid {
    grid-template-columns: minmax(0, 1.32fr) minmax(0, 1fr);
  }
}
.hero h1 {
  margin-bottom: 26px;
}
.hero .lede {
  margin-bottom: 30px;
}
.hero-mark {
  position: relative;
  max-width: 340px;
  margin-inline: auto;
  width: 100%;
}
.hero-mark .ricegrid {
  font-size: clamp(190px, 26vw, 300px);
}
.hero-mark .chop--lg {
  position: absolute;
  right: -14px;
  bottom: -18px;
}
.hero-gloss {
  margin-top: 22px;
  text-align: center;
  font-size: 13.5px;
  color: var(--ink-muted);
  letter-spacing: 0.02em;
}
.hero-gloss b {
  color: var(--ink);
  font-weight: 600;
}

/* A row of measured facts, ruled off like a table of contents. */
.facts {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  border-top: 1px solid var(--hairline);
}
@media (min-width: 780px) {
  .facts {
    grid-template-columns: repeat(4, minmax(0, 1fr));
  }
}
.fact {
  padding: 26px clamp(14px, 2vw, 26px) 26px 0;
  border-bottom: 1px solid var(--hairline);
}
@media (min-width: 780px) {
  .fact {
    border-bottom: none;
  }
  .fact + .fact {
    border-left: 1px solid var(--hairline);
    padding-left: clamp(18px, 2.4vw, 30px);
  }
}
.fact dt {
  font-size: clamp(30px, 3.4vw, 40px);
  margin-bottom: 9px;
}
.fact dd {
  margin: 0;
  font-size: 13.5px;
  line-height: 1.4;
  color: var(--ink-muted);
}

/* --- Reading check (the live demo) --------------------------------------- */
.check-head {
  display: flex;
  flex-wrap: wrap;
  align-items: flex-end;
  justify-content: space-between;
  gap: 24px;
  margin-bottom: 30px;
}
.check-paper {
  background: var(--paper-raised);
  border: 1px solid var(--hairline);
  border-radius: 18px;
  overflow: hidden;
}
.check-input {
  width: 100%;
  display: block;
  border: 0;
  background: transparent;
  resize: vertical;
  min-height: 108px;
  padding: 22px clamp(18px, 2.4vw, 26px);
  font-family: var(--han);
  font-size: 20px;
  line-height: 1.85;
  letter-spacing: 0.02em;
}
.check-input:focus {
  outline: none;
}
.check-input::placeholder {
  color: var(--ink-muted);
}
.check-bar {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: 14px;
  padding: 12px clamp(18px, 2.4vw, 26px);
  border-top: 1px solid var(--hairline);
  background: var(--paper);
}
.samples {
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
  align-items: center;
}
.chip {
  border: 1px solid var(--hairline);
  background: transparent;
  border-radius: 999px;
  padding: 7px 14px;
  font-size: 13px;
  font-weight: 600;
  cursor: pointer;
  color: var(--ink);
  transition: border-color 0.16s ease, background 0.16s ease;
}
.chip:hover {
  border-color: var(--ink-muted);
}
.chip[aria-pressed='true'] {
  background: var(--seal-soft);
  border-color: color-mix(in srgb, var(--seal) 40%, transparent);
  color: var(--seal);
}

.check-out {
  display: grid;
  gap: clamp(26px, 4vw, 46px);
  margin-top: clamp(30px, 4vw, 46px);
  align-items: start;
}
@media (min-width: 860px) {
  .check-out {
    grid-template-columns: minmax(0, 1fr) 210px;
  }
}
/* Starts on the small page subset — everything the page ships with is in it.
   kezi.js promotes both fields to the full 1,825-glyph face the moment the
   visitor brings text of their own, and not a moment sooner. */
.han-full .check-input,
.han-full .inked,
.han-full .popover .pop-han {
  font-family: var(--han-full);
}
.inked {
  font-family: var(--han);
  font-weight: 600;
  font-size: clamp(24px, 2.6vw, 31px);
  line-height: 1.95;
  letter-spacing: 0.04em;
  word-break: break-word;
  min-height: 2em;
}
/* The same three states the app inks on screen, with the same meanings:
   confident ink for what is already yours, seal red for what Kezi will teach
   you next, faded for what falls outside the set. Because ordinary Chinese is
   dominated by the first hundred characters, ink carries most of the text and
   red stays what it is everywhere else on this page — the rare accent. */
.inked .k {
  cursor: pointer;
  border-radius: 3px;
  transition: opacity 0.5s ease, color 0.5s ease;
}
.inked .k-own {
  color: var(--ink);
} /* in the first 100 — yours inside a week */
.inked .k-next {
  color: var(--seal);
} /* elsewhere in the 1,000 — Kezi teaches it */
.inked .k-out {
  color: var(--ink-muted);
  opacity: 0.42;
  cursor: default;
} /* beyond the set — shown, never hidden */
.inked .k-punct {
  color: var(--ink-muted);
  opacity: 0.55;
  cursor: default;
}
.inked .k:not(.k-out):not(.k-punct):hover {
  background: var(--seal-soft);
}
.inked .k.bloom {
  animation: bloom 0.5s ease both;
}
@keyframes bloom {
  from {
    opacity: 0;
    filter: blur(2px);
  }
  to {
    opacity: 1;
    filter: none;
  }
}
@media (prefers-reduced-motion: reduce) {
  .inked .k.bloom {
    animation: none;
  }
}

.score {
  border-top: 1px solid var(--hairline);
  padding-top: 18px;
}
@media (min-width: 860px) {
  .score {
    border-top: none;
    border-left: 1px solid var(--hairline);
    padding: 2px 0 2px 26px;
  }
}
.score dt {
  font-size: clamp(46px, 6vw, 66px);
  color: var(--seal);
  margin-bottom: 10px;
}
.score dd {
  margin: 0 0 18px;
  font-size: 13.5px;
  line-height: 1.45;
  color: var(--ink-muted);
}
.legend {
  display: grid;
  gap: 9px;
  font-size: 12.5px;
  color: var(--ink-muted);
  border-top: 1px solid var(--hairline);
  padding-top: 16px;
}
.legend span {
  display: flex;
  align-items: center;
  gap: 9px;
}
.legend i {
  width: 10px;
  height: 10px;
  border-radius: 2px;
  flex: none;
}
.legend .sw-own {
  background: var(--ink);
}
.legend .sw-next {
  background: var(--seal);
}
.legend .sw-out {
  background: var(--ink-muted);
  opacity: 0.42;
}

/* The second number: coverage from the first hundred characters alone. It is
   the more interesting one — it says how far a single week already gets you. */
.score-sub {
  margin: 0 0 18px;
  padding-top: 15px;
  border-top: 1px solid var(--hairline);
  font-size: 13px;
  line-height: 1.45;
  color: var(--ink-muted);
}
.score-sub b {
  display: block;
  font-family: var(--serif);
  font-variation-settings: 'wght' 500;
  font-size: 25px;
  line-height: 1;
  color: var(--ink);
  margin-bottom: 5px;
  font-feature-settings: 'tnum' 1;
}

/* Character card, raised off the paper only enough to read as an overlay. */
.popover {
  position: absolute;
  z-index: 30;
  min-width: 216px;
  max-width: 280px;
  background: var(--paper-raised);
  border: 1px solid var(--hairline);
  border-radius: 14px;
  padding: 15px 17px;
  box-shadow: 0 10px 30px rgba(33, 30, 26, 0.1);
  font-size: 14px;
  line-height: 1.45;
}
.popover .pop-han {
  font-family: var(--han);
  font-weight: 600;
  font-size: 38px;
  line-height: 1;
  margin-bottom: 8px;
}
.popover .pop-read {
  font-family: var(--serif);
  font-variation-settings: 'wght' 560;
  font-size: 17px;
  color: var(--seal);
  margin-bottom: 3px;
}
.popover .pop-pin {
  font-size: 13px;
  color: var(--ink-muted);
  margin-bottom: 8px;
}
.popover .pop-mean {
  font-size: 13.5px;
  color: var(--ink);
}
.popover .pop-rank {
  margin-top: 10px;
  padding-top: 9px;
  border-top: 1px solid var(--hairline);
  font-size: 12px;
  color: var(--ink-muted);
}

/* --- The compounding equation ------------------------------------------- */
.equation {
  display: flex;
  align-items: center;
  justify-content: center;
  flex-wrap: wrap;
  gap: clamp(14px, 3vw, 34px);
  font-family: var(--han);
  font-weight: 600;
  font-size: clamp(48px, 9vw, 104px);
  line-height: 1.1;
  margin: 8px 0 clamp(26px, 4vw, 40px);
}
.equation .op {
  font-family: var(--serif);
  font-variation-settings: 'wght' 300;
  font-size: 0.44em;
  color: var(--ink-muted);
}
.equation .sum {
  color: var(--seal);
}
.eq-gloss {
  display: flex;
  justify-content: center;
  flex-wrap: wrap;
  gap: clamp(14px, 3vw, 34px);
  font-size: 13px;
  color: var(--ink-muted);
  text-align: center;
}
.eq-gloss span {
  min-width: 3em;
}

/* --- Ruled lists (Hán-Việt rows, ladder, do/don't) ----------------------- */
.rows {
  border-top: 1px solid var(--hairline);
  margin: 0;
  padding: 0;
  list-style: none;
}
.rows > li {
  border-bottom: 1px solid var(--hairline);
  padding: clamp(16px, 2vw, 22px) 0;
  display: flex;
  align-items: center;
  gap: clamp(16px, 3vw, 30px);
}
.hv-han {
  font-family: var(--han);
  font-weight: 600;
  font-size: clamp(34px, 4.4vw, 52px);
  line-height: 1;
  flex: none;
  min-width: 1.9em;
}
.hv-arrow {
  color: var(--hairline);
  font-size: 20px;
  flex: none;
}
.hv-read {
  font-family: var(--serif);
  font-variation-settings: 'wght' 560;
  font-size: clamp(22px, 2.6vw, 30px);
  color: var(--seal);
  line-height: 1.1;
  flex: none;
  min-width: 4.6em;
}
.hv-mean {
  font-size: 14.5px;
  color: var(--ink-muted);
  line-height: 1.4;
}

/* Coverage ladder: the bar is the type, not a chart widget. */
.ladder {
  border-top: 1px solid var(--hairline);
  margin: 0;
  padding: 0;
  list-style: none;
}
.ladder li {
  display: grid;
  grid-template-columns: 5.2em minmax(0, 1fr) 4.4em;
  align-items: center;
  gap: clamp(14px, 2.6vw, 26px);
  padding: clamp(14px, 1.8vw, 20px) 0;
  border-bottom: 1px solid var(--hairline);
}
.ladder .step {
  font-family: var(--serif);
  font-variation-settings: 'wght' 520;
  font-size: clamp(17px, 1.8vw, 21px);
}
.ladder .track {
  height: 3px;
  background: var(--hairline);
  position: relative;
  overflow: hidden;
}
.ladder .fill {
  position: absolute;
  inset: 0 auto 0 0;
  width: var(--w, 0%);
  background: var(--seal);
  transform-origin: left;
  transform: scaleX(0);
  transition: transform 1.1s cubic-bezier(0.2, 0.7, 0.2, 1);
}
.ladder.in .fill {
  transform: scaleX(1);
}
@media (prefers-reduced-motion: reduce) {
  .ladder .fill {
    transition: none;
    transform: scaleX(1);
  }
}
.ladder .pct {
  font-family: var(--serif);
  /* Heavier and inked rather than muted: at Fraunces' high contrast a 500
     weight in warm grey all but disappears against the paper. */
  font-variation-settings: 'wght' 600;
  font-size: clamp(17px, 1.8vw, 21px);
  text-align: right;
  color: var(--ink);
  font-feature-settings: 'tnum' 1;
}
.ladder li:last-child .pct,
.ladder li:last-child .step {
  color: var(--seal);
}

/* --- The menu board ------------------------------------------------------ */
.board-wrap {
  display: grid;
  gap: clamp(28px, 4vw, 52px);
  align-items: center;
}
@media (min-width: 900px) {
  .board-wrap {
    grid-template-columns: minmax(0, 420px) minmax(0, 1fr);
  }
}
/* Vertical setting, right to left — how a board like this is actually written,
   and something no template layout ever does.
   Deliberately NOT flex: in a vertical writing mode the flex axes swap, which
   scrambled the column order and wrapped long lines into each other. Plain
   block children stack right to left on their own, one column per line, which
   is exactly the behaviour wanted. */
.board {
  writing-mode: vertical-rl;
  text-orientation: upright;
  font-family: var(--han);
  font-weight: 600;
  font-size: clamp(18px, 2vw, 22px);
  /* In vertical text, line-height sets the gap *between* columns. */
  line-height: 2.3;
  letter-spacing: 0.1em;
  /* In vertical text `height` is the inline size — i.e. how long a line may
     run. Sized to the longest line (11 characters) so the board hugs its
     content instead of framing a field of empty paper. */
  height: clamp(290px, 38vw, 336px);
  width: fit-content;
  max-width: 100%;
  margin-inline: auto;
  padding: clamp(20px, 3vw, 30px) clamp(16px, 2.4vw, 24px);
  border: 1px solid var(--hairline);
  border-radius: 4px;
  background: var(--paper-raised);
  position: relative;
}
.board p {
  margin: 0;
}
.board .head {
  color: var(--seal);
  letter-spacing: 0.24em;
}
.board-note {
  font-size: 14.5px;
  color: var(--ink-muted);
}
.board-lines {
  list-style: none;
  margin: 18px 0 0;
  padding: 0;
  border-top: 1px solid var(--hairline);
}
.board-lines li {
  border-bottom: 1px solid var(--hairline);
  padding: 11px 0;
  font-size: 14.5px;
  color: var(--ink-muted);
  display: flex;
  gap: 14px;
  flex-wrap: wrap;
}
.board-lines b {
  font-family: var(--han);
  font-weight: 600;
  color: var(--ink);
  font-size: 16px;
  flex: none;
}

/* --- Confusables --------------------------------------------------------- */
.lookalikes {
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  border-top: 1px solid var(--hairline);
  border-bottom: 1px solid var(--hairline);
}
.lookalikes > div {
  padding: clamp(22px, 3vw, 34px) clamp(12px, 2vw, 22px);
  text-align: center;
}
.lookalikes > div + div {
  border-left: 1px solid var(--hairline);
}
.lookalikes .glyph {
  font-family: var(--han);
  font-weight: 600;
  font-size: clamp(56px, 9vw, 96px);
  line-height: 1;
  margin-bottom: 14px;
}
.lookalikes .read {
  font-family: var(--serif);
  font-variation-settings: 'wght' 540;
  font-size: 16px;
  color: var(--seal);
  margin-bottom: 6px;
}
.lookalikes .tell {
  font-size: 13px;
  line-height: 1.45;
  color: var(--ink-muted);
}

/* --- Dictionary entry ---------------------------------------------------- */
.entry {
  display: grid;
  gap: clamp(28px, 4vw, 52px);
}
@media (min-width: 860px) {
  .entry {
    grid-template-columns: minmax(0, 260px) minmax(0, 1fr);
    align-items: start;
  }
}
.entry .ricegrid {
  font-size: clamp(170px, 22vw, 240px);
}
.spec {
  margin: 0;
  border-top: 1px solid var(--hairline);
}
.spec > div {
  display: grid;
  grid-template-columns: 8.6em minmax(0, 1fr);
  gap: 16px;
  padding: 13px 0;
  border-bottom: 1px solid var(--hairline);
}
.spec dt {
  font-size: 11.5px;
  font-weight: 600;
  letter-spacing: 0.13em;
  text-transform: uppercase;
  color: var(--ink-muted);
  padding-top: 3px;
}
.spec dd {
  margin: 0;
  font-size: 15.5px;
  line-height: 1.45;
}
.spec dd .han {
  font-size: 19px;
}
.spec .accent {
  font-family: var(--serif);
  font-variation-settings: 'wght' 560;
  font-size: 19px;
  color: var(--seal);
}

/* --- Two-column ledger (what it is / is not) ----------------------------- */
.ledger {
  display: grid;
  gap: clamp(26px, 4vw, 48px);
}
@media (min-width: 800px) {
  .ledger {
    grid-template-columns: repeat(2, minmax(0, 1fr));
  }
}
.ledger ul {
  list-style: none;
  margin: 16px 0 0;
  padding: 0;
  border-top: 1px solid var(--hairline);
}
.ledger li {
  padding: 13px 0 13px 26px;
  border-bottom: 1px solid var(--hairline);
  position: relative;
  font-size: 15.5px;
  line-height: 1.45;
}
.ledger li::before {
  position: absolute;
  left: 0;
  top: 13px;
  font-size: 14px;
  line-height: 1.45;
}
.ledger .is li::before {
  content: '—';
  color: var(--seal);
}
.ledger .isnt li {
  color: var(--ink-muted);
}
.ledger .isnt li::before {
  content: '×';
  color: var(--ink-muted);
}

/* --- Closing / waitlist -------------------------------------------------- */
.closing {
  text-align: center;
  padding: clamp(80px, 11vw, 150px) 0;
  border-top: 1px solid var(--hairline);
}
.closing h2 {
  max-width: 16em;
  margin: 0 auto 20px;
}
.closing .lede {
  margin-inline: auto;
  margin-bottom: 34px;
}
/* An underline field, not a boxed input — the page has no boxes anywhere else. */
.signup {
  display: flex;
  flex-wrap: wrap;
  gap: 12px;
  justify-content: center;
  align-items: flex-end;
  max-width: 520px;
  margin: 0 auto;
}
.signup .field {
  flex: 1 1 240px;
  text-align: left;
}
.signup label {
  display: block;
  font-size: 11.5px;
  font-weight: 600;
  letter-spacing: 0.15em;
  text-transform: uppercase;
  color: var(--ink-muted);
  margin-bottom: 8px;
}
.signup input {
  width: 100%;
  border: 0;
  border-bottom: 1px solid var(--ink-muted);
  background: transparent;
  padding: 10px 2px;
  font-size: 16px;
  border-radius: 0;
}
.signup input:focus {
  outline: none;
  border-bottom-color: var(--seal);
}
.signup-msg {
  margin-top: 18px;
  font-size: 14px;
  min-height: 1.4em;
}
.signup-msg[data-state='ok'] {
  color: var(--seal);
}
.signup-msg[data-state='err'] {
  color: var(--ink-muted);
}
.stores {
  margin-top: 26px;
  font-size: 13px;
  color: var(--ink-muted);
  letter-spacing: 0.02em;
}

/* --- Footer -------------------------------------------------------------- */
.colophon {
  border-top: 1px solid var(--hairline);
  padding: clamp(40px, 5vw, 60px) 0 clamp(48px, 6vw, 70px);
  font-size: 13.5px;
  color: var(--ink-muted);
}
.colophon .cols {
  display: grid;
  gap: 30px;
  margin-bottom: 34px;
}
@media (min-width: 760px) {
  .colophon .cols {
    grid-template-columns: minmax(0, 1.4fr) repeat(2, minmax(0, 1fr));
  }
}
.colophon h4 {
  font-size: 11.5px;
  font-weight: 600;
  letter-spacing: 0.15em;
  text-transform: uppercase;
  color: var(--ink-muted);
  margin: 0 0 14px;
}
.colophon ul {
  list-style: none;
  margin: 0;
  padding: 0;
  display: grid;
  gap: 9px;
}
.colophon a {
  text-decoration: none;
}
.colophon a:hover {
  color: var(--ink);
}
.colophon .fine {
  border-top: 1px solid var(--hairline);
  padding-top: 22px;
  font-size: 12.5px;
  line-height: 1.6;
  max-width: 62em;
}

/* --- Long-form (privacy, support) ---------------------------------------- */
.prose {
  max-width: 40em;
}
.prose h2 {
  margin: clamp(38px, 5vw, 58px) 0 14px;
}
.prose h2:first-child {
  margin-top: 0;
}
.prose p,
.prose li {
  font-size: 16.5px;
  line-height: 1.62;
}
.prose ul {
  padding-left: 1.1em;
}
.prose li {
  margin-bottom: 8px;
}
.doc-switch {
  display: flex;
  gap: 10px;
  margin-bottom: 34px;
  font-size: 13px;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  font-weight: 650;
  color: var(--ink-muted);
}
.doc-switch a {
  text-decoration: none;
}
.doc-switch [aria-current='true'] {
  color: var(--seal);
}

/* --- Reveal -------------------------------------------------------------- */
[data-reveal] {
  opacity: 0;
  transform: translateY(7px);
  transition: opacity 0.75s cubic-bezier(0.2, 0.6, 0.2, 1),
    transform 0.75s cubic-bezier(0.2, 0.6, 0.2, 1);
}
[data-reveal].in {
  opacity: 1;
  transform: none;
}
@media (prefers-reduced-motion: reduce) {
  [data-reveal] {
    opacity: 1;
    transform: none;
    transition: none;
  }
}

.skip {
  position: absolute;
  left: -9999px;
}
.skip:focus {
  left: 12px;
  top: 12px;
  z-index: 100;
  background: var(--paper-raised);
  border: 1px solid var(--hairline);
  padding: 10px 14px;
  border-radius: 10px;
}
