/* ============================================================
   Chinese Word Daily — "the practice grid and the instrument"

   Two ideas carry the interface:
   1. 田字格 — the cross-ruled square Chinese children write characters
      into. It is the study card, the stat cells, and the heatmap.
   2. The tones are real data, so they get real colour. Pinyin is tinted
      by tone everywhere it appears; nothing else in the UI is.
   ============================================================ */

:root {
  color-scheme: light;

  --paper:      #EDEFF2;
  --surface:    #FFFFFF;
  --surface-2:  #F6F7F9;
  --ink:        #14171D;
  --ink-soft:   #5A6170;
  --ink-faint:  #8D94A3;
  --rule:       #D6DAE1;
  --rule-soft:  #E5E8ED;
  --heat-empty: #DFE3E9;
  --cinnabar:   #C0342B;

  --tone-1: #2F7FD4;
  --tone-2: #2E8F5F;
  --tone-3: #B27B0E;
  --tone-4: #C0342B;
  --tone-5: #8D94A3;

  --grade-again: #C0342B;
  --grade-hard:  #B27B0E;
  --grade-good:  #2E8F5F;
  --grade-easy:  #2F7FD4;

  --sans: -apple-system, BlinkMacSystemFont, "Segoe UI", Inter, Roboto, "Helvetica Neue", Arial, sans-serif;
  --mono: ui-monospace, "SF Mono", SFMono-Regular, "JetBrains Mono", Menlo, Consolas, monospace;
  /* Kaiti is the regular-script face Chinese textbooks teach characters in —
     it shows stroke entry and exit the way they are actually written. */
  --hanzi: "Kaiti SC", "STKaiti", KaiTi, "Kaiti TC", "Songti SC", "Noto Serif CJK SC", serif;
  --hanzi-ui: "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei", "Noto Sans CJK SC", sans-serif;

  --shadow: 0 1px 2px rgb(20 23 29 / .05), 0 8px 24px -12px rgb(20 23 29 / .16);
  --radius: 3px;
  --rail: 56px;
}

:root[data-theme="dark"] {
  color-scheme: dark;

  --paper:      #101319;
  --surface:    #171B23;
  --surface-2:  #1E232C;
  --ink:        #E3E7EF;
  --ink-soft:   #939BAC;
  --ink-faint:  #6B7387;
  --rule:       #2B313D;
  --rule-soft:  #232935;
  --heat-empty: #262D3A;
  --cinnabar:   #E0574B;

  --tone-1: #5AA3EE;
  --tone-2: #4FB683;
  --tone-3: #D9A73C;
  --tone-4: #E0574B;
  --tone-5: #7C8497;

  --grade-again: #E0574B;
  --grade-hard:  #D9A73C;
  --grade-good:  #4FB683;
  --grade-easy:  #5AA3EE;

  --shadow: 0 1px 2px rgb(0 0 0 / .3), 0 10px 28px -14px rgb(0 0 0 / .7);
}

* { box-sizing: border-box; }

body {
  margin: 0;
  background: var(--paper);
  color: var(--ink);
  font-family: var(--sans);
  font-size: 15px;
  line-height: 1.5;
  -webkit-font-smoothing: antialiased;
}

a { color: inherit; }

button, input, select, textarea {
  font: inherit;
  color: inherit;
}

:focus-visible {
  outline: 2px solid var(--tone-1);
  outline-offset: 2px;
  border-radius: 2px;
}

/* --- the shared utility voice: mono, uppercase, wide ------------------ */
.eyebrow,
.metric-label,
.nav a,
.btn,
.tag,
.grade b {
  font-family: var(--mono);
  text-transform: uppercase;
  letter-spacing: .10em;
  font-size: 11px;
  font-weight: 500;
}

.eyebrow {
  color: var(--ink-faint);
  display: flex;
  align-items: center;
  gap: 12px;
  margin: 0 0 14px;
}
.eyebrow::after {
  content: "";
  flex: 1;
  height: 1px;
  background: var(--rule);
}

.num { font-family: var(--mono); font-variant-numeric: tabular-nums; }

/* ============================== chrome ============================== */

.rail {
  position: sticky;
  top: 0;
  z-index: 30;
  display: flex;
  align-items: center;
  gap: 28px;
  height: var(--rail);
  padding: 0 24px;
  background: color-mix(in srgb, var(--paper) 88%, transparent);
  backdrop-filter: blur(12px);
  border-bottom: 1px solid var(--rule);
}

.brand {
  display: flex;
  align-items: baseline;
  gap: 9px;
  text-decoration: none;
  font-weight: 600;
  letter-spacing: -.02em;
}
.brand .chop {
  font-family: var(--hanzi);
  font-size: 15px;
  line-height: 1;
  color: #FFF;
  background: var(--cinnabar);
  padding: 5px 5px 6px;
  border-radius: 2px;
}

.nav { display: flex; gap: 4px; margin-left: auto; }
.nav a {
  padding: 7px 11px;
  border-radius: var(--radius);
  text-decoration: none;
  color: var(--ink-soft);
}
.nav a:hover { color: var(--ink); background: var(--surface-2); }
.nav a[aria-current="page"] { color: var(--ink); background: var(--surface); box-shadow: inset 0 0 0 1px var(--rule); }

.icon-btn {
  display: grid;
  place-items: center;
  width: 32px; height: 32px;
  border: 1px solid var(--rule);
  border-radius: var(--radius);
  background: var(--surface);
  cursor: pointer;
  color: var(--ink-soft);
}
.icon-btn:hover { color: var(--ink); border-color: var(--ink-faint); }

main { max-width: 1000px; margin: 0 auto; padding: 40px 24px 96px; }

h1 { font-size: 26px; font-weight: 600; letter-spacing: -.025em; margin: 0 0 6px; }
h2 { font-size: 17px; font-weight: 600; letter-spacing: -.015em; margin: 0 0 4px; }
/* A second h1-weight break inside a page, for Progress, whose bottom half was a
   page of its own until 2026-09-07. Room above so it reads as a new section
   and not as a caption on the panel before it. */
.section-title { font-size: 22px; margin: 44px 0 6px; }
/* A run of headed paragraphs inside one panel, as on About: each heading
   after the first needs air above it or the panel reads as one block. */
.panel p + h2 { margin-top: 18px; }
.lede { color: var(--ink-soft); margin: 0 0 32px; max-width: 62ch; }

/* The standing link to the credits page. Quiet, but never hidden: it is on
   every screen including the signed-out gate, because CC-CEDICT's licence needs
   a way to its attribution from wherever the data is shown. Sized and coloured
   like .field .help so it reads as apparatus rather than as something the page
   is trying to tell you. */
.colophon {
  max-width: 1000px;
  margin: 0 auto;
  padding: 18px 24px 34px;
  border-top: 1px solid var(--rule-soft);
  color: var(--ink-faint);
  font-size: 12.5px;
  text-align: center;
}
.colophon a { color: var(--ink-soft); }
.colophon a:hover { color: var(--ink); }

/* ============================== buttons ============================== */

.btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 8px;
  padding: 11px 18px;
  border: 1px solid var(--rule);
  border-radius: var(--radius);
  background: var(--surface);
  color: var(--ink);
  cursor: pointer;
  text-decoration: none;
  transition: background .12s, border-color .12s, transform .06s;
}
.btn:hover { border-color: var(--ink-faint); background: var(--surface-2); }
.btn:active { transform: translateY(1px); }
.btn[disabled] { opacity: .42; cursor: not-allowed; }

.btn.primary {
  background: var(--ink);
  border-color: var(--ink);
  color: var(--paper);
}
.btn.primary:hover { background: color-mix(in srgb, var(--ink) 88%, var(--cinnabar)); border-color: transparent; }

.btn.danger { color: var(--cinnabar); border-color: color-mix(in srgb, var(--cinnabar) 34%, var(--rule)); }
.btn.small { padding: 6px 11px; }
/* A secondary action sitting beside a primary one: present, but not competing
   with it. Used by the sentence re-roll, which is a repair, not a step. */
.btn.ghost { color: var(--ink-soft, inherit); border-color: transparent; opacity: .72; }
.btn.ghost:hover { opacity: 1; border-color: var(--rule); }

/* ==================== 田字格 — the structural motif ==================== */

/* A grid of cells sharing hairline rules, like the squared paper
   characters are practised on. */
.tianzi {
  display: grid;
  border: 1px solid var(--rule);
  border-radius: var(--radius);
  background: var(--rule);
  gap: 1px;
  overflow: hidden;
}
.tianzi > * { background: var(--surface); }

.metrics { grid-template-columns: repeat(4, 1fr); margin-bottom: 28px; }

.metric { padding: 20px 18px 18px; position: relative; }
.metric-label { color: var(--ink-faint); display: block; margin-bottom: 10px; }
.metric-value {
  font-family: var(--mono);
  font-size: 34px;
  font-weight: 500;
  letter-spacing: -.03em;
  line-height: 1;
  font-variant-numeric: tabular-nums;
}
.metric-sub { color: var(--ink-faint); font-size: 12px; margin-top: 8px; }
.metric.is-due .metric-value { color: var(--cinnabar); }

/* ============================== panels ============================== */

.panel {
  background: var(--surface);
  border: 1px solid var(--rule);
  border-radius: var(--radius);
  padding: 22px;
  margin-bottom: 28px;
}
/* `clip`, not `hidden`. Both clip the table's corners to the panel radius, but
   `hidden` also makes this a scroll container, which re-anchors the sticky
   header inside it: `top: var(--rail)` then measured 56px down from the panel
   instead of from the viewport, parking the header mid-panel with the first row
   showing through the gap above it. `clip` clips without becoming a scrollport,
   so the header keeps sticking to the viewport under the rail. */
.panel.flush { padding: 0; overflow: clip; }

.start {
  display: flex;
  align-items: center;
  gap: 20px;
  flex-wrap: wrap;
}
.start .grow { flex: 1; min-width: 220px; }
.start p { margin: 4px 0 0; color: var(--ink-soft); font-size: 14px; }

/* ============================== heatmap ============================== */

.heatmap {
  display: grid;
  grid-auto-flow: column;
  grid-template-rows: repeat(7, 11px);
  grid-auto-columns: 11px;   /* auto tracks would stretch to fill the panel */
  justify-content: start;
  gap: 3px;
  overflow-x: auto;
  padding-bottom: 4px;
}
.heat-cell {
  width: 11px; height: 11px;
  border-radius: 1px;
  background: var(--heat-empty);
}
.heat-cell[data-level="1"] { background: color-mix(in srgb, var(--tone-2) 30%, var(--heat-empty)); }
.heat-cell[data-level="2"] { background: color-mix(in srgb, var(--tone-2) 54%, var(--heat-empty)); }
.heat-cell[data-level="3"] { background: color-mix(in srgb, var(--tone-2) 77%, var(--heat-empty)); }
.heat-cell[data-level="4"] { background: var(--tone-2); }

/* ============================== forecast ============================== */

.forecast {
  display: grid;
  grid-auto-flow: column;
  grid-auto-columns: 1fr;
  align-items: end;
  gap: 3px;
  height: 110px;
}
.fc-col { display: flex; flex-direction: column; justify-content: flex-end; height: 100%; gap: 1px; }
.fc-bar { background: var(--tone-1); border-radius: 1px 1px 0 0; min-height: 0; }
.fc-bar.learning { background: var(--tone-3); }
.fc-col[data-today="1"] .fc-bar { background: var(--cinnabar); }
.fc-axis {
  display: grid; grid-auto-flow: column; grid-auto-columns: 1fr; gap: 3px;
  margin-top: 8px; font-family: var(--mono); font-size: 9px; color: var(--ink-faint);
  text-align: center;
}

/* ============================== lists ============================== */

.list-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(268px, 1fr)); gap: 12px; margin-bottom: 34px; }

.list-card {
  display: block;
  padding: 16px 17px 15px;
  background: var(--surface);
  border: 1px solid var(--rule);
  border-radius: var(--radius);
  text-decoration: none;
  transition: border-color .12s, transform .12s;
}
.list-card:hover { border-color: var(--ink-faint); transform: translateY(-1px); }
.list-card h3 { margin: 0 0 4px; font-size: 15px; font-weight: 600; letter-spacing: -.01em; }
.list-card p { margin: 0 0 13px; font-size: 13px; color: var(--ink-soft); line-height: 1.45; }

.bar { height: 4px; background: var(--rule-soft); border-radius: 2px; overflow: hidden; display: flex; }
.bar i { display: block; height: 100%; }
.bar .known { background: var(--tone-2); }
.bar .started { background: color-mix(in srgb, var(--tone-1) 55%, var(--rule-soft)); }

.list-meta {
  display: flex; justify-content: space-between; margin-top: 9px;
  font-family: var(--mono); font-size: 10.5px; letter-spacing: .06em;
  color: var(--ink-faint); text-transform: uppercase;
}

/* ============================== word table ============================== */

.words { width: 100%; border-collapse: collapse; }
.words th {
  text-align: left; padding: 11px 16px;
  font-family: var(--mono); font-size: 10px; text-transform: uppercase; letter-spacing: .1em;
  color: var(--ink-faint); font-weight: 500;
  border-bottom: 1px solid var(--rule);
  background: var(--surface-2);
  position: sticky; top: var(--rail);
}
.words td { padding: 11px 16px; border-bottom: 1px solid var(--rule-soft); vertical-align: top; }
.words tr:last-child td { border-bottom: 0; }
.words tr:hover td { background: var(--surface-2); }
.words .hz { font-family: var(--hanzi-ui); font-size: 19px; letter-spacing: .02em; white-space: nowrap; }
.words .gloss { color: var(--ink-soft); font-size: 13.5px; }
.words .col-state { width: 108px; }

.tag {
  display: inline-block; padding: 2px 7px; border-radius: 2px;
  background: var(--surface-2); border: 1px solid var(--rule);
  color: var(--ink-soft); font-size: 10px;
}
.tag[data-state="review"]     { color: var(--tone-2); border-color: color-mix(in srgb, var(--tone-2) 35%, var(--rule)); }
.tag[data-state="learning"],
.tag[data-state="relearning"] { color: var(--tone-3); border-color: color-mix(in srgb, var(--tone-3) 35%, var(--rule)); }
.tag[data-state="new"]        { color: var(--tone-1); border-color: color-mix(in srgb, var(--tone-1) 35%, var(--rule)); }

/* Remove one word from the collection. Faint until wanted -- it sits on every
   row of a long table and must not compete with the words themselves -- but
   never hidden behind :hover, which would put it out of reach on a touch
   screen. Cinnabar on hover follows .btn.danger. */
.row-x {
  border: 0; background: none; cursor: pointer;
  font-size: 17px; line-height: 1; padding: 0 2px;
  color: var(--rule); transition: color .12s;
}
.words tr:hover .row-x { color: var(--ink-soft); }
.row-x:hover, .row-x:focus-visible { color: var(--cinnabar); }
.row-x[disabled] { opacity: .4; cursor: default; }

.sr-only {
  position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px;
  overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; border: 0;
}

/* --- tone colouring: the one place colour carries meaning ------------- */
.py { font-family: var(--mono); font-size: 13px; letter-spacing: .01em; white-space: nowrap; }
.t1 { color: var(--tone-1); }
.t2 { color: var(--tone-2); }
.t3 { color: var(--tone-3); }
.t4 { color: var(--tone-4); }
.t5 { color: var(--tone-5); }

/* ============================== study ============================== */

.study {
  position: fixed; inset: 0; z-index: 60;
  background: var(--paper);
  display: flex; flex-direction: column;
}

.study-top {
  display: flex; align-items: center; gap: 16px;
  padding: 14px 22px;
  border-bottom: 1px solid var(--rule);
}
.progress { flex: 1; height: 3px; background: var(--rule-soft); border-radius: 2px; overflow: hidden; }
.progress i { display: block; height: 100%; background: var(--ink); transition: width .3s ease; }
.study-top .num { font-size: 12px; color: var(--ink-soft); }

.study-body {
  flex: 1;
  display: flex; flex-direction: column; align-items: center; justify-content: center;
  gap: 30px; padding: 28px 22px;
  text-align: center;
}

/* The character sits in a practice square. On reveal the guides fade —
   the word graduates off the grid. */
.cell {
  position: relative;
  width: min(300px, 62vw);
  aspect-ratio: 1;
  display: grid; place-items: center;
  border: 1px solid var(--rule);
  border-radius: var(--radius);
  background: var(--surface);
  box-shadow: var(--shadow);
}
.cell::before, .cell::after {
  content: "";
  position: absolute;
  background: var(--rule);
  transition: opacity .45s ease;
}
.cell::before { left: 8%; right: 8%; height: 1px; top: 50%; }
.cell::after  { top: 8%; bottom: 8%; width: 1px; left: 50%; }
.cell .diag {
  position: absolute; inset: 8%;
  background:
    linear-gradient(to bottom right, transparent calc(50% - .5px), var(--rule-soft) calc(50% - .5px), var(--rule-soft) calc(50% + .5px), transparent calc(50% + .5px)),
    linear-gradient(to bottom left,  transparent calc(50% - .5px), var(--rule-soft) calc(50% - .5px), var(--rule-soft) calc(50% + .5px), transparent calc(50% + .5px));
  transition: opacity .45s ease;
}
.cell.revealed::before,
.cell.revealed::after,
.cell.revealed .diag { opacity: 0; }

.cell .hanzi {
  font-family: var(--hanzi);
  font-size: clamp(64px, 17vw, 140px);
  line-height: 1;
  letter-spacing: .04em;
  position: relative;
  padding: 0 6px;
}
.cell .hanzi.long { font-size: clamp(40px, 10vw, 78px); }

.prompt-gloss {
  font-size: clamp(19px, 3.4vw, 27px);
  font-weight: 500;
  letter-spacing: -.015em;
  max-width: 22ch;
  line-height: 1.32;
}

.answer { display: flex; flex-direction: column; gap: 10px; align-items: center; max-width: 46ch; }
.answer .py { font-size: 22px; letter-spacing: .02em; }
.answer .glosses { color: var(--ink-soft); font-size: 15.5px; line-height: 1.55; }
.answer .glosses li { margin: 2px 0; }
.answer ol { margin: 0; padding-left: 1.2em; text-align: left; }

.reveal-in { animation: rise .28s cubic-bezier(.2,.7,.3,1) both; }
@keyframes rise { from { opacity: 0; transform: translateY(7px); } to { opacity: 1; transform: none; } }

.study-foot { padding: 0 22px 30px; display: flex; flex-direction: column; align-items: center; gap: 14px; }

.grades { display: grid; grid-template-columns: repeat(4, minmax(0, 148px)); gap: 8px; width: 100%; max-width: 620px; }
.grade {
  display: flex; flex-direction: column; align-items: center; gap: 4px;
  padding: 12px 6px 10px;
  border: 1px solid var(--rule);
  border-top: 2px solid var(--g);
  border-radius: var(--radius);
  background: var(--surface);
  cursor: pointer;
  transition: background .12s, transform .06s;
}
.grade:hover { background: var(--surface-2); }
.grade:active { transform: translateY(1px); }
.grade b { color: var(--g); font-weight: 600; }
.grade span { font-family: var(--mono); font-size: 11px; color: var(--ink-faint); font-variant-numeric: tabular-nums; }
.grade[data-g="1"] { --g: var(--grade-again); }
.grade[data-g="2"] { --g: var(--grade-hard); }
.grade[data-g="3"] { --g: var(--grade-good); }
.grade[data-g="4"] { --g: var(--grade-easy); }

.hint {
  font-family: var(--mono); font-size: 10.5px; letter-spacing: .1em;
  text-transform: uppercase; color: var(--ink-faint);
}
.hint kbd {
  font: inherit; border: 1px solid var(--rule); border-bottom-width: 2px;
  border-radius: 3px; padding: 1px 5px; background: var(--surface); color: var(--ink-soft);
}

.summary { text-align: center; max-width: 420px; }
.summary .metrics { grid-template-columns: repeat(3, 1fr); margin: 26px 0; text-align: left; }

/* ============================== forms ============================== */

.field { margin-bottom: 18px; }
.field label { display: block; font-family: var(--mono); font-size: 10.5px; text-transform: uppercase; letter-spacing: .1em; color: var(--ink-faint); margin-bottom: 7px; }
.field .help { font-size: 12.5px; color: var(--ink-faint); margin-top: 6px; }

input[type="text"], input[type="number"], input[type="search"], select, textarea {
  width: 100%;
  padding: 10px 12px;
  background: var(--surface);
  border: 1px solid var(--rule);
  border-radius: var(--radius);
}
textarea { min-height: 130px; resize: vertical; font-family: var(--hanzi-ui); }
input:focus, select:focus, textarea:focus { border-color: var(--tone-1); outline: none; box-shadow: 0 0 0 3px color-mix(in srgb, var(--tone-1) 16%, transparent); }

.settings-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(220px, 1fr)); gap: 0 22px; }

.searchbar { display: flex; gap: 10px; margin-bottom: 22px; }
.searchbar input { flex: 1; font-size: 16px; padding: 13px 15px; }

.row { display: flex; gap: 10px; align-items: center; flex-wrap: wrap; }
.spread { display: flex; justify-content: space-between; align-items: flex-start; gap: 20px; flex-wrap: wrap; margin-bottom: 26px; }

.empty { text-align: center; padding: 54px 20px; color: var(--ink-faint); }
.empty .big { font-family: var(--hanzi); font-size: 42px; color: var(--rule); display: block; margin-bottom: 12px; }

.toast {
  position: fixed; left: 50%; bottom: 28px; transform: translateX(-50%);
  z-index: 90;
  background: var(--ink); color: var(--paper);
  padding: 10px 16px; border-radius: var(--radius);
  font-size: 13.5px; box-shadow: var(--shadow);
  animation: rise .2s both;
}

.pager { display: flex; justify-content: center; gap: 10px; padding: 16px; }

/* ============================== responsive ============================== */

@media (max-width: 760px) {
  .metrics { grid-template-columns: repeat(2, 1fr); }
  .grades { grid-template-columns: repeat(2, 1fr); }
  .rail { gap: 12px; padding: 0 14px; }
  .nav a { padding: 7px 8px; }
  main { padding: 28px 14px 80px; }
  .words .col-gloss { display: none; }
}

@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after { animation-duration: .01ms !important; transition-duration: .01ms !important; }
}

/* ------------------------------------------------------------------- auth */
/* ==================== the signed-out page ====================
   The app chrome is hidden behind this, so it is the whole page — and the
   only place that has to explain what the thing is.

   It argues by demonstration rather than description. The two ideas the
   interface is built on are both shown here rather than claimed:

     1. 田字格 — the cross-ruled square characters are practised in, drawn
        properly, with a character actually sitting in it.
     2. Tone as colour — the pinyin under the character is tinted by its
        tone, and the legend names the five. Nothing else on the page is
        coloured, which is what makes the rule legible.

   The largest type on the page is a Chinese character. That is deliberate:
   --hanzi (Kaiti) covers CJK only, so Latin cannot borrow the display face,
   and rather than fight that the page lets the character be the display
   type. The gloss sits directly beneath it, so it is a demonstration and
   never a puzzle.
   ==================================================================== */

.gate {
  max-width: 61rem;
  margin: 0 auto;
  padding: clamp(2rem, 7vh, 5rem) 1.25rem 4rem;
  /* Not a full 100vh any more, and main's own padding is dropped beneath it
     (below): the colophon carries the CC-CEDICT credit, and a gate that claims
     the whole viewport pushes it past the bottom edge, where a first-time
     visitor -- the one person who most needs to see who the data belongs to --
     has no reason to scroll. The 5rem it gives back is the colophon's height. */
  min-height: calc(100vh - 5rem); box-sizing: border-box;
  align-content: center;
  display: grid;
  grid-template-columns: minmax(0, 19rem) minmax(0, 1fr);
  gap: clamp(2rem, 6vw, 4.5rem);
  align-items: start;
}

/* The gate brings its own padding and centres itself, so main's 40/96 is dead
   space stacked on top of a full-height panel -- harmless until the colophon
   arrived beneath it, at which point it is exactly what pushes the credit off
   screen. */
main:has(.gate) { padding: 0; }

/* ---- the demonstration ---- */

.gate-demo { align-self: start; }

.gate-brand {
  display: flex; align-items: center; gap: .55rem;
  margin: 0 0 1.25rem;
  font-size: .8125rem; letter-spacing: .01em; color: var(--ink-soft);
}
.gate-chop {
  width: 1.6rem; height: 1.6rem;
  display: grid; place-items: center;
  background: var(--cinnabar); border-radius: 2px;
  color: #fff; font-family: var(--hanzi); font-size: 1rem; line-height: 1;
}

/* The practice square. Border is the rule; the cross is dashed, the way the
   guide lines are printed, and sits a shade lighter so the character reads
   as the foreground and the guides as paper. */
.tzg {
  position: relative;
  aspect-ratio: 1;
  container-type: inline-size;
  background: var(--surface);
  border: 1px solid var(--rule);
  border-radius: var(--radius, 3px);
  display: grid; place-items: center;
  overflow: hidden;
}
.tzg::before,
.tzg::after {
  content: ""; position: absolute; pointer-events: none;
}
.tzg::before {
  left: 0; right: 0; top: 50%;
  height: 1px;
  background: repeating-linear-gradient(to right,
    var(--rule-soft) 0 7px, transparent 7px 14px);
}
.tzg::after {
  top: 0; bottom: 0; left: 50%;
  width: 1px;
  background: repeating-linear-gradient(to bottom,
    var(--rule-soft) 0 7px, transparent 7px 14px);
}
.tzg-char {
  font-family: var(--hanzi);
  /* Sized off the square, not the viewport: on a real 田字格 the character
     fills the cell and the strokes are placed against the guides. At half the
     width it read as a small glyph floating in a box, and Kaiti's own
     right-of-centre ink placement then looked like a bug rather than the
     typeface. */
  font-size: 66cqw;
  line-height: 1;
  color: var(--ink);
}
/* The tone number, set in the corner of the square like a mark on a
   worksheet. Coloured, because it IS the tone. */
.tzg-tone {
  position: absolute; top: .45rem; right: .55rem; z-index: 1;
  font-family: var(--mono); font-size: .8125rem; font-weight: 600;
  font-variant-numeric: tabular-nums;
}

.tzg-read {
  margin: .9rem 0 0;
  display: flex; align-items: baseline; gap: .6rem;
}
.tzg-py {
  font-family: var(--mono); font-size: 1.0625rem; letter-spacing: .01em;
}
.tzg-gloss { color: var(--ink-soft); font-size: .9375rem; }

.gate-legend {
  margin: 1.1rem 0 0;
  font-size: .8125rem; line-height: 1.55; color: var(--ink-faint);
}
.gate-legend b { font-weight: 500; }


/* ---- the offer and the form ---- */

.gate-panel { max-width: 34rem; }

.gate-panel h1 {
  font-size: clamp(1.6rem, 3.4vw, 2.15rem);
  line-height: 1.15;
  letter-spacing: -.022em;
  font-weight: 600;
  margin: 0 0 .75rem;
  text-wrap: balance;
}
.gate-lede {
  color: var(--ink-soft); font-size: 1.0625rem; line-height: 1.6;
  margin: 0 0 2rem; max-width: 32rem;
}

.gate-form { margin: 0 0 2.25rem; max-width: 24rem; }
.gate-form label {
  display: block; font-size: .75rem; font-weight: 500;
  text-transform: uppercase; letter-spacing: .08em;
  color: var(--ink-faint); margin: 0 0 .4rem;
}
.gate-form input {
  width: 100%; box-sizing: border-box;
  padding: .62rem .75rem; margin: 0 0 .75rem;
  font: inherit; color: var(--ink);
  background: var(--surface);
  border: 1px solid var(--rule); border-radius: 3px;
}
.gate-form input:focus-visible {
  outline: 2px solid var(--cinnabar); outline-offset: 1px; border-color: transparent;
}
.gate-form .btn { width: 100%; justify-content: center; }
.gate-hint { color: var(--ink-faint); font-size: .8125rem; margin: .7rem 0 0; }

.gate-msg {
  background: var(--surface-2);
  border-left: 2px solid var(--cinnabar);
  padding: .6rem .75rem; margin: 0 0 1.25rem;
  font-size: .875rem;
  max-width: 24rem;
}

/* Three claims, each one a thing the app actually does. Ruled rows rather
   than cards: the same hairline vocabulary as the rest of the interface. */
.gate-facts { list-style: none; margin: 0; padding: 0; border-top: 1px solid var(--rule-soft); }
.gate-facts li { padding: .95rem 0; border-bottom: 1px solid var(--rule-soft); }
.gate-facts h2 {
  font-size: .9375rem; font-weight: 600; margin: 0 0 .2rem; letter-spacing: -.005em;
}
.gate-facts p { margin: 0; color: var(--ink-soft); font-size: .9375rem; line-height: 1.55; }

.gate-alt { color: var(--ink-soft); font-size: .875rem; margin: 1.75rem 0 0; }

@media (max-width: 46rem) {
  .gate { grid-template-columns: minmax(0, 1fr); gap: 2.25rem; }
  .gate-demo { max-width: 15rem; }

}


/* Unconfirmed-email nag. Deliberately a strip, not a modal: it is a reminder,
   and nothing about the app is withheld until it is dealt with. */
.verify-nag {
  display: flex; gap: .75rem; align-items: center; flex-wrap: wrap;
  padding: .6rem 1.25rem;
  background: var(--surface-2);
  border-bottom: 1px solid var(--rule);
  font-size: .875rem; color: var(--ink-soft);
}
.linklike {
  background: none; border: 0; padding: 0; cursor: pointer;
  font: inherit; color: var(--cinnabar); text-decoration: underline;
}
.account-panel { display: flex; align-items: center; justify-content: space-between; gap: 1rem; }
.account-name { font-weight: 500; }
.account-email { color: var(--ink-soft); font-size: .875rem; }
.unverified { color: var(--cinnabar); }

/* ------------------------------------------------------------------ admin */
.table-wrap { overflow-x: auto; }
.admin-table { width: 100%; border-collapse: collapse; background: var(--surface);
  border: 1px solid var(--rule); border-radius: 3px; font-size: .9375rem; }
.admin-table th { font: 500 .6875rem/1 var(--mono); text-transform: uppercase;
  letter-spacing: .08em; color: var(--ink-faint); text-align: left;
  padding: .7rem .75rem; background: var(--surface-2); border-bottom: 1px solid var(--rule); }
.admin-table td { padding: .6rem .75rem; border-bottom: 1px solid var(--rule); vertical-align: top; }
.admin-table tr:last-child td { border-bottom: 0; }
.admin-table .n { text-align: right; font-variant-numeric: tabular-nums; }
.tag-admin { font: 500 .625rem/1 var(--mono); text-transform: uppercase; letter-spacing: .07em;
  background: var(--cinnabar); color: #fff; padding: .15rem .35rem; border-radius: 2px; margin-left: .4rem; }

/* --------------------------------------------------------------- sentence */
/* The example sentence under a revealed card. Quieter than the answer itself:
   it is context, not the thing being tested, and it must not compete with the
   character for attention at the moment of recall. */
.sentence {
  margin-top: 18px; padding-top: 16px;
  border-top: 1px solid var(--rule-soft);
  text-align: center;
}
.sentence-hz {
  font-family: var(--hanzi-ui); font-size: 21px; letter-spacing: .02em;
  color: var(--ink); margin-bottom: 5px;
}
.sentence-py { font-family: var(--mono); font-size: 12.5px; color: var(--ink-soft); margin-bottom: 4px; }
.sentence-en { font-size: 13.5px; color: var(--ink-faint); margin-bottom: 12px; }

/* ----------------------------------------------------------- render progress */
.render-box { margin-top: 16px; padding: 14px 16px; background: var(--surface-2);
  border: 1px solid var(--rule); border-radius: 3px; }
.render-head { display: flex; justify-content: space-between; align-items: baseline;
  font-size: .9375rem; margin-bottom: 9px; }
.render-head span { font-family: var(--mono); font-size: .8125rem; color: var(--ink-soft);
  font-variant-numeric: tabular-nums; }
.render-bar { height: 5px; background: var(--rule-soft); border-radius: 3px; overflow: hidden; }
.render-bar span { display: block; height: 100%; background: var(--good);
  border-radius: 3px; transition: width .4s ease; }
.render-note { margin-top: 8px; font-size: .8125rem; color: var(--ink-faint); }
.render-fail { color: var(--cinnabar); }
.render-samples { margin-top: 10px; }
.sample { display: inline-flex; align-items: center; gap: 6px; margin: 4px 6px 0 0;
  padding: 5px 10px; font: inherit; color: var(--ink); cursor: pointer;
  background: var(--surface); border: 1px solid var(--rule); border-radius: 3px; }
.sample:hover { border-color: var(--ink-faint); background: var(--surface-2); }
.sample-hz { font-family: var(--hanzi-ui); font-size: 15px; }
.sample-play { color: var(--cinnabar); font-size: 12px; }
/* Key hints inside buttons: present but recessive — the label is the message. */
.btn.small kbd { margin-left: 6px; opacity: .55; font-size: .9em; padding: 0 4px; }


/* ---------------- mnemonic picture ----------------
   The drawing that explains the character's shape. Answer side only. Sized well
   under the character above it: the hanzi stays the subject of the card and the
   picture is the explanation, so it must not out-shout it. The caption carries
   the actual teaching -- which strokes became what -- so it reads as a note,
   not a caption on a stock photo. */
.mnemo {
  margin: 1.25rem 0 0;
  display: flex; flex-direction: column; align-items: center; gap: .55rem;
}
.mnemo img {
  width: 100%; max-width: 13.75rem; height: auto;
  border: 1px solid var(--rule-soft);
  border-radius: var(--radius, 3px);
  background: var(--surface);
}
.mnemo figcaption {
  color: var(--ink-soft); font-size: .8125rem; line-height: 1.5;
  max-width: 22rem; text-align: center; text-wrap: balance;
}


/* ---------------- time studied ----------------
   Columns rather than the heatmap's cells: minutes vary over a wide range and
   a five-level colour scale would flatten a 40-minute day into the same bucket
   as a 12-minute one. On the Today page it is the one activity chart; on the
   Progress page it sits under the reviews heatmap, which covers the same habit
   from the other side -- how much, against how often. */
.studytime {
  display: flex; align-items: flex-end; gap: 2px;
  height: 96px; padding: 0 2px;
}
.st-col {
  flex: 1; height: 100%; min-width: 0;
  display: flex; flex-direction: column; justify-content: flex-end;
}
/* A hairline every seventh column, so the week is readable without an axis. */
.st-col[data-week="1"] { box-shadow: inset 1px 0 0 var(--rule-soft); }
.st-bar {
  background: var(--tone-1); border-radius: 1px 1px 0 0;
  transition: height .18s ease;
}
/* Today in cinnabar, matching the forecast chart, where it means the same thing. */
.st-col[data-today="1"] .st-bar { background: var(--cinnabar); }
.st-foot {
  display: flex; justify-content: space-between; gap: 12px;
  margin-top: 10px; padding-top: 9px; border-top: 1px solid var(--rule-soft);
  color: var(--ink-soft); font-size: 12px;
}
@media (max-width: 34rem) {
  /* The middle figure is the one that survives: a per-session average says more
     on a small screen than a running total. */
  .st-foot span:first-child, .st-foot span:last-child { display: none; }
  .st-foot { justify-content: center; }
}
/* A day with nothing on it still gets a tick, matching the forecast chart: an
   empty column reads as a broken chart, where a flat baseline reads as zero. */
.st-bar.zero { height: 2px; opacity: .25; background: var(--ink-soft); }


/* ---------------- the word wall ----------------
   Strength is ink weight, never hue. This app tells the learner on the very
   first screen that "colour only ever means tone", and a wall of hanzi sitting
   beside tone-coloured pinyin is exactly where breaking that would do damage --
   a green character would read as a first-tone character. Opacity and weight
   carry the scale instead, which is also how a real page of study notes looks:
   what you know is written firmly, what you don't has barely been inked in. */
.mw-wall {
  display: flex; flex-wrap: wrap; gap: 3px 6px;
}
.mw-word {
  font-family: var(--hanzi, inherit);
  font-size: 25px; line-height: 1.5;
  color: var(--ink); text-decoration: none;
  padding: 0 2px; border-radius: 2px;
  transition: opacity .12s ease, background .12s ease;
}
/* The floor is .28, not lower. A steeper ramp looked better but defeated the
   page: the whole point is to see WHICH words you have not learned, and at .17
   the unlearned mass was atmospheric rather than readable. */
.mw-word[data-band="new"]      { opacity: .28; }
.mw-word[data-band="learning"] { opacity: .45; }
.mw-word[data-band="shaky"]    { opacity: .62; }
.mw-word[data-band="settling"] { opacity: .78; }
.mw-word[data-band="known"]    { opacity: .92; }
.mw-word[data-band="strong"]   { opacity: 1; font-weight: 600; }
/* Trouble words are the one case where the wall must not be scanned past, so
   they get an underline -- a shape difference, not a colour one. */
.mw-word[data-band="leech"] {
  opacity: 1; text-decoration: underline; text-decoration-style: wavy;
  text-underline-offset: 4px; text-decoration-color: var(--rule-soft);
}
.mw-word:hover, .mw-word:focus-visible {
  opacity: 1; background: var(--rule-soft); outline: none;
}

.mw-legend {
  display: flex; flex-wrap: wrap; gap: 8px 20px;
  margin: 0 0 28px; padding-bottom: 18px;
  border-bottom: 1px solid var(--rule-soft);
  color: var(--ink-soft); font-size: 12.5px;
}
.mw-key { display: inline-flex; align-items: center; gap: 7px; }
.mw-key .mw-word { font-size: 19px; pointer-events: none; }

/* Sits at the end of an eyebrow, so it has to read as secondary to it. */
.eyebrow-link {
  margin-left: 10px; text-transform: none; letter-spacing: 0;
  color: var(--ink-soft); font-size: 12px;
}
.eyebrow-link:hover { color: var(--ink); }
/* The session's scope, in the study header. The mode and the list are one
   phrase, not two labels, so the separator is dimmed rather than spaced out. */
.study-scope { display: inline-flex; align-items: baseline; gap: 5px; min-width: 0; }
.study-scope .sep { opacity: .4; }


/* ---------------- studying from ----------------
   Where the collection came from, on Today. Rows rather than cards: this is
   orientation, not a place to choose from -- the choosing happens on Lists. */
.sources { display: flex; flex-direction: column; gap: 0; }
.src {
  display: flex; align-items: baseline; justify-content: space-between; gap: 16px;
  padding: 11px 2px; text-decoration: none; color: inherit;
  border-bottom: 1px solid var(--rule-soft);
}
.src:last-child { border-bottom: 0; }
.src-name { font-weight: 500; }
a.src:hover .src-name { text-decoration: underline; }
.queue-row { align-items: center; }
.queue-row .grow { flex: 1; }
.queue-pos { width: 1.6em; color: var(--ink-faint); font-size: 12px; }
@media (max-width: 30rem) {
  .src { flex-direction: column; gap: 3px; }
  .queue-row { flex-direction: row; flex-wrap: wrap; }
}


/* ---------------- list completed ----------------
   A milestone, not a trophy. The app has no other celebratory surface and one
   loud banner would look borrowed from a different product, so this is the
   panel it already uses, marked with a seal character rather than colour --
   colour still means tone. */
.panel.done {
  display: flex; align-items: center; gap: 18px;
  border-color: var(--tone-2);
}
.done-mark {
  font-family: var(--hanzi, serif); font-size: 34px; line-height: 1;
  color: var(--tone-2); flex: none;
}
.panel.done h2 { margin: 0 0 3px; }
.panel.done p { margin: 0; color: var(--ink-soft); font-size: 13px; max-width: 56ch; }
@media (max-width: 34rem) {
  .panel.done { flex-direction: column; align-items: flex-start; gap: 12px; }
}

/* On the lists page, a finished list keeps its place in the grid but stops
   competing for attention: the eye should land on what is unfinished. */
.list-card.is-complete { border-color: var(--tone-2); }
.list-card.is-complete .bar { opacity: .55; }


/* ---------------- tappable sentence words ----------------
   The example sentence becomes a row of words you can add. Words already in the
   collection look exactly as before -- the affordance appears only on the ones
   you could act on, so a sentence you fully know reads as ordinary text and the
   feature stays invisible until it is useful. */
.sentence-hz .sw {
  font: inherit; color: inherit;
  background: none; border: 0; padding: 0 .04em; margin: 0;
  cursor: pointer; border-radius: 2px;
  border-bottom: 1px dashed var(--tone-1);
}
.sentence-hz .sw.have { border-bottom-color: transparent; cursor: default; }
.sentence-hz .sw:not(.have):hover,
.sentence-hz .sw:not(.have):focus-visible {
  background: var(--rule-soft); border-bottom-style: solid; outline: none;
}
.sentence-hz .sw[disabled] { opacity: .5; }
.sentence-hz .sw-punct { padding: 0 .04em; }

/* Where the written tones and the spoken ones part company.

   Quiet by default: most cards have nothing to say here, and on the ones that
   do it must not compete with the sentence it sits under. Two centred rows --
   the spoken pinyin, then the rule -- because both places this appears are
   centred columns, and a flex row ignores the text-align that centres them.

   The moved syllable is picked out by weight and an underline, and carries the
   colour of the tone it is ACTUALLY said with. Those are the same tone colours
   used everywhere else in the app, so the shift from third-tone amber to
   second-tone green is legible before the sentence underneath is read. */
.tone-change {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 2px;
  margin: 4px 0 10px;
  font-size: 12.5px;
}
.tone-change .tc-said {
  display: flex;
  align-items: baseline;
  gap: 8px;
}
.tc-label {
  font-size: 10px;
  letter-spacing: .09em;
  text-transform: uppercase;
  color: var(--ink-faint);
}
.tc-spoken { font-family: var(--mono); letter-spacing: .01em; }
.tc-moved  { font-weight: 700; border-bottom: 1.5px solid currentColor; }
.tc-rule   { color: var(--ink-faint); font-size: 12px; text-align: center; }
/* Only on runs of three or more third tones, where the reading is a judgement
   about phrase grouping rather than a mechanical rule. Rarer, and worth more
   when it appears, so it gets its own line rather than being run together. */
.tc-grouping {
  color: var(--ink-faint);
  font-size: 11.5px;
  text-align: center;
  opacity: .85;
  border-top: 1px dotted var(--rule);
  padding-top: 3px;
  margin-top: 1px;
}

.answer > .tone-change .tc-spoken { font-size: 16px; }
.tone-change.in-sentence { margin: 2px 0 10px; }

/* The measure word a noun is counted with. Shares the tone-change block's shape
   -- a labelled line, then a note -- because they are the same kind of thing:
   something true of this word that the gloss alone does not tell you. */
.measure {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 2px;
  margin: 2px 0 4px;
  font-size: 12.5px;
}
.mw-list { display: inline-flex; align-items: baseline; gap: 6px; }
.mw b { font-size: 16px; font-weight: 600; }
.mw-py { font-family: var(--mono); font-size: 12.5px; color: var(--ink-soft); }
.mw-sep { color: var(--ink-faint); }
