/* ============================================================
   Chinese Reader — theme per external/website-design/sites/fgnussbaum
   Tokens mirror that site's main.css :root {} (authoritative source).
   ============================================================ */

/* ============================================================
   FONTS — self-hosted, no external requests
   ============================================================ */
@font-face {
  font-family: 'Cinzel';
  src: url('../fonts/Cinzel/cinzel.woff2') format('woff2');
  font-weight: 400 900;
  font-display: swap;
}
@font-face {
  font-family: 'Inter';
  src: url('../fonts/Inter/inter.woff2') format('woff2');
  font-weight: 100 900;
  font-style: normal;
  font-display: swap;
}

/* ============================================================
   ROOT DESIGN TOKENS
   ============================================================ */
:root {
  --color-bg:          #faf8f5;
  --color-surface:     #f0ede8;
  --color-surface-alt: #e6e0d8;
  --color-text:        #111111;
  --color-text-muted:  #666666;
  --color-heading:     #49080e;
  --color-accent:      #00662c;
  --color-divider:     #ddd8d0;
  --color-error:       #c0392b;

  --font-hero:  clamp(2rem, 5vw, 3.5rem);
  --font-h2:    clamp(1.5rem, 3vw, 2rem);
  --font-body:  1rem;
  --font-small: 0.875rem;

  --space-xs:  0.25rem;
  --space-s:   0.5rem;
  --space-m:   1rem;
  --space-l:   2rem;
  --space-xl:  4rem;

  --content-width: 1000px;
  --prose-measure: 680px;

  /* app-specific */
  --font-cjk: 'Noto Sans SC', 'Source Han Sans SC', 'PingFang SC',
              'Microsoft YaHei', sans-serif;
  --font-brush: 'Kaiti SC', KaiTi, STKaiti, 'Noto Serif SC', var(--font-cjk);
  --navbar-height: 64px;

  /* pinyin tone colors (active only under .tones) */
  --tone-1: #c0392b;
  --tone-2: #00662c;
  --tone-3: #1a5fb4;
  --tone-4: #7a3b8f;
  --tone-0: #666666;
}

/* ============================================================
   RESET & BASE
   ============================================================ */
*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }

/* taps give their own feedback (sentence highlight, dict panel) —
   the gray flash would double it */
html { -webkit-tap-highlight-color: transparent; }

[hidden] { display: none !important; }

/* cross-document navigation crossfades instead of flashing white
   (Chromium; browsers without support simply ignore this) */
@view-transition { navigation: auto; }

/* column layout so the footer sits at the viewport bottom on short pages
   (few filtered stories) instead of floating mid-screen */
body {
  background: var(--color-bg);
  color: var(--color-text);
  font-family: 'Inter', sans-serif;
  font-size: var(--font-body);
  line-height: 1.6;
  min-height: 100vh;
  min-height: 100dvh;
  display: flex;
  flex-direction: column;
}

h1, h2, h3 {
  font-family: 'Cinzel', serif;
  letter-spacing: 0.02em;
  line-height: 1.2;
  color: var(--color-heading);
}

/* CJK inside headings: Cinzel has no hanzi; fall through cleanly */
h1 :lang(zh), h2 :lang(zh), h3 :lang(zh),
.zh { font-family: var(--font-cjk); }

em { color: var(--color-accent); font-style: normal; }

a { color: inherit; text-decoration: none; }

img { display: block; max-width: 100%; height: auto; }

button {
  font: inherit;
  color: inherit;
  background: none;
  border: none;
  cursor: pointer;
}

.container {
  max-width: 1100px;
  margin-inline: auto;
  padding-inline: var(--space-l);
}

/* ============================================================
   NAVBAR — app variant: always solid surface (no hero pages)
   ============================================================ */
.navbar {
  position: fixed;
  inset-block-start: 0;
  inset-inline: 0;
  z-index: 102;
  display: flex;
  align-items: center;
  justify-content: space-between;
  height: var(--navbar-height);
  padding-inline: var(--space-l);
  background: var(--color-surface);
  border-bottom: 1px solid var(--color-divider);
}

.navbar-brand {
  font-family: 'Cinzel', serif;
  font-size: 1.4rem;
  color: var(--color-heading);
  white-space: nowrap;
}

.navbar-brand .zh { margin-inline-end: var(--space-s); }

/* mobile-only "back to library" button; on desktop the brand and page
   links serve that role, so it stays hidden (shown in the 768px query).
   A bordered chevron chip: sits in the chip row, reads as "go back" — the
   ‹ glyph is free now that the chapter arrows are gone on narrow screens */
.navbar-back {
  display: none;
  align-items: center;
  justify-content: center;
  min-width: 2.25rem;
  padding-block: var(--space-xs);
  font-size: 1.6rem;
  line-height: 1;
  color: var(--color-heading);
  border: 1px solid var(--color-divider);
  border-radius: 2px;
  transition: color 0.12s, border-color 0.12s, background-color 0.12s;
}
.navbar-back:hover,
.navbar-back:active {
  color: var(--color-accent);
  border-color: var(--color-accent);
  background: var(--color-surface-alt);
}

.navbar-links {
  display: flex;
  gap: var(--space-l);
  list-style: none;
}

.navbar-links a {
  font-size: var(--font-small);
  text-transform: uppercase;
  letter-spacing: 0.04em;
}

.navbar-links a:hover,
.navbar-links a[aria-current="page"] { color: var(--color-accent); }

/* EN/DE site-language toggle (injected by i18n.js): one button showing the
   active language; tapping switches to the other (one chip, saves row space) */
.navbar-lang { display: flex; align-items: center; }
.navbar-lang button {
  font-size: var(--font-small);
  text-transform: uppercase;
  letter-spacing: 0.04em;
  /* both options stay bold: the active one is marked by color alone, so
     marking it after load cannot change any width */
  font-weight: 700;
  color: var(--color-text-muted);
  padding: 2px 4px;
}
.navbar-lang button[aria-pressed="true"] {
  color: var(--color-accent);   /* marks the active language */
  cursor: default;
}
.navbar-lang button:not([aria-pressed="true"]):hover { color: var(--color-heading); }
.navbar-lang-sep { color: var(--color-divider); font-size: var(--font-small); }

/* reader controls live in the navbar, left of the page links */
.navbar-right {
  display: flex;
  align-items: center;
  gap: var(--space-l);
}

.navbar-tools {
  display: flex;
  gap: var(--space-s);
  font-size: var(--font-small);
}

main {
  padding-block-start: var(--navbar-height);
  position: relative;
  z-index: 1;
  flex: 1 0 auto;   /* pushes the footer to the viewport bottom on short pages */
}

/* ============================================================
   LICENSES — static prose page (licenses.html)
   ============================================================ */
.prose-page {
  max-width: var(--prose-measure);
  margin-inline: auto;
  padding-block: var(--space-l);
}
.prose-page h1 { font-size: var(--font-h2); margin-block-end: var(--space-m); }
.prose-page h2 { font-size: 1.05rem; margin-block: var(--space-l) var(--space-s); }
.prose-page p { margin-block-end: var(--space-s); }
.prose-page a { color: var(--color-accent); }
.prose-page .muted { color: var(--color-text-muted); font-size: var(--font-small); }

/* ============================================================
   FOOTER
   ============================================================ */
.footer {
  position: relative;   /* paints above the fixed ambient art layer */
  z-index: 1;
  margin-block-start: var(--space-xl);
  background: var(--color-surface);
  border-top: 1px solid var(--color-divider);
  padding: var(--space-l);
  text-align: center;
  font-size: var(--font-small);
  color: var(--color-text-muted);
}

.footer a { text-decoration: underline; }
.footer a:hover { color: var(--color-accent); }

/* cheat sheet in the reader footer: keyboard hints on mouse setups,
   gesture hints (long-press / tap / swipe) on touch devices */
.shortcut-hints {
  margin-block-end: var(--space-s);
  font-size: 0.82em;
  line-height: 1.9;
}
.hints-touch { display: none; }
@media (hover: none) {
  .hints-mouse { display: none; }
  .hints-touch { display: inline; }
}
.shortcut-hints kbd {
  font-family: inherit;
  font-size: 0.9em;
  padding: 1px 5px;
  border: 1px solid var(--color-divider);
  border-radius: 3px;
  color: var(--color-text);
  background: var(--color-surface-alt);
}

/* ============================================================
   LIBRARY — control bar (title + filters), then collection
   shelves of row-entries
   ============================================================ */
.ctrl {
  position: sticky;
  top: var(--navbar-height);
  z-index: 90;
  background: var(--color-surface);
  border-bottom: 1px solid var(--color-divider);
  padding-block: var(--space-s);
}

.ctrl-inner {
  max-width: var(--content-width);
  margin-inline: auto;
  padding-inline: var(--space-l);
}

.ctrl-row {
  display: flex;
  align-items: baseline;
  gap: var(--space-s) var(--space-l);
  flex-wrap: wrap;
}
.ctrl-row + .ctrl-row { margin-block-start: var(--space-s); }

.ctrl-inner h1 { font-size: 1.35rem; }

.ctrl-inner h1 .zh {
  font-size: 1.45rem;
  font-weight: 600;
  margin-inline-end: var(--space-s);
}

.ctrl select {
  font: inherit;
  font-size: var(--font-small);
  color: var(--color-text);
  background: var(--color-bg);
  border: 1px solid var(--color-divider);
  border-radius: 2px;
  padding: 1px var(--space-s);
}
.ctrl select:focus { outline: none; border-color: var(--color-accent); }

.chip-group {
  display: flex;
  align-items: baseline;
  gap: var(--space-s);
  flex-wrap: wrap;
}

.chip-label {
  font-size: 0.7rem;
  text-transform: uppercase;
  letter-spacing: 0.08em;
  color: var(--color-text-muted);
}

.chip {
  display: inline-block;   /* also used as <a> (hanzi chart chips) */
  padding: 1px var(--space-s);
  border: 1px solid var(--color-divider);
  border-radius: 2px;
  background: var(--color-bg);
  font-size: var(--font-small);
  color: var(--color-text-muted);
}
.chip:hover { background: var(--color-surface-alt); }
.chip[aria-pressed="true"],
.chip[aria-current="page"] { border-color: var(--color-accent); color: var(--color-accent); }

/* library search: shares the title row with the h1, pushed to the far
   right, the same pattern the Dictionary's search block takes once it is
   answering rather than asking */
.lib-searchbar {
  display: flex;
  flex: 1 1 180px;
  max-width: 300px;
  margin-inline-start: auto;
}

.lib-searchbar input {
  flex: 1;
  min-width: 0;
  font: inherit;
  font-size: var(--font-small);
  color: var(--color-text);
  padding: 1px var(--space-s);
  background: var(--color-bg);
  border: 1px solid var(--color-divider);
  border-radius: 2px;
}
.lib-searchbar input:focus { outline: none; border-color: var(--color-accent); }

.library {
  max-width: var(--content-width);
  margin-inline: auto;
  padding-block: var(--space-m) var(--space-l);
}

.shelf-head {
  display: flex;
  align-items: baseline;
  gap: var(--space-m);
  margin-block-start: var(--space-l);
  padding-block-end: var(--space-xs);
  border-bottom: 2px solid var(--color-surface-alt);
}
.shelf-head:first-child { margin-block-start: var(--space-s); }

.shelf-zh {
  font-family: var(--font-cjk);
  font-size: 1.25rem;
  font-weight: 600;
  color: var(--color-heading);
}

.shelf-en {
  font-family: 'Cinzel', serif;
  font-size: var(--font-small);
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--color-heading);
  opacity: 0.75;
}

.shelf-count {
  margin-inline-start: auto;
  font-size: var(--font-small);
  color: var(--color-text-muted);
  font-variant-numeric: tabular-nums;
}

/* optional provenance/license note under a shelf head (collections.yaml) */
.shelf-note {
  margin-block-start: var(--space-xs);
  font-size: 0.78rem;
  color: var(--color-text-muted);
}

/* row = story link + its read toggle; the toggle must sit outside the <a>
   so clicking it marks instead of navigating */
.entry-row {
  display: flex;
  align-items: stretch;
  border-bottom: 1px solid var(--color-divider);
}
.entry-row:hover { background: var(--color-surface); }

.entry {
  display: block;
  flex: 1;
  min-width: 0;
  padding: var(--space-m) var(--space-s);
}

/* read stories stay in place and stay legible — receded, not struck out */
.entry-row.read .entry { opacity: 0.55; }

/* Read mark: a seal stamp (阅, what an official stamps on a document he has
   read), not a checkbox — an empty box beside every row read as a web form.
   Unread is a faint outline that stays visible rather than appearing on
   hover, because phones have no hover; read is the stamp pressed in accent
   ink. Shared by the library rows and the reader's end-of-story control. */
.read-seal {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  flex: 0 0 auto;
  width: 1.9rem;
  height: 1.9rem;
  border: 1px solid currentcolor;
  border-radius: 3px;
  background: none;
  font-family: var(--font-brush);
  font-size: 1.1rem;
  line-height: 1;
  color: var(--color-text-muted);
  opacity: 0.4;
  transition: opacity 0.12s, background-color 0.12s, color 0.12s;
}

.read-toggle {
  align-self: center;
  margin-inline: var(--space-s) var(--space-xs);
  cursor: pointer;
}
.read-toggle:hover { opacity: 0.8; }

/* stamped: solid accent ink with the glyph reversed out in paper */
.read-seal[aria-pressed="true"] {
  background: var(--color-accent);
  border-color: var(--color-accent);
  color: var(--color-bg);
  opacity: 1;
}

@media (pointer: coarse) {
  .read-toggle { width: 2.4rem; height: 2.4rem; }   /* finger-sized target */
}

/* tags on a story double as one-click filters (library only) */
.tag-btn { cursor: pointer; }
.tag-btn:hover { border-color: var(--color-accent); color: var(--color-accent); }
.tag-btn.on { border-color: var(--color-accent); color: var(--color-accent); }

.entry-top {
  display: flex;
  align-items: baseline;
  gap: var(--space-s) var(--space-m);
  flex-wrap: wrap;
}

.entry-zh {
  font-family: var(--font-cjk);
  font-size: 1.35rem;
  line-height: 1.3;
}

.entry-lat { font-size: 1rem; }

.entry .tag { font-size: 0.78rem; padding: 0 var(--space-xs); }

.entry-sub {
  margin-block-start: 2px;
  font-size: var(--font-small);
  color: var(--color-text-muted);
  font-variant-numeric: tabular-nums;
}

.lib-empty { padding: var(--space-l) var(--space-s); color: var(--color-text-muted); }

.badge {
  display: inline-block;
  padding: 0 var(--space-s);
  border: 1px solid var(--color-accent);
  border-radius: 2px;
  color: var(--color-accent);
  font-size: var(--font-small);
}

/* ============================================================
   LIBRARY AMBIENT ART — fixed behind content (main/footer at z 1).
   Quote content comes from stories/ambient.yaml via the manifest;
   everything visual (ink, sizes, offsets) is tuned right here.
   ============================================================ */
#ambient .gutter {
  position: fixed;
  top: calc(var(--navbar-height) + 6.5rem);   /* below the sticky control bar */
  bottom: 28vh;                               /* above the ink-wash horizon */
  z-index: 0;
  width: calc((100vw - var(--content-width) - 6rem) / 2);
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;   /* scroll group centered in the zone between */
  gap: 5vh;
  overflow: hidden;
  pointer-events: none;
}
#ambient .gutter.left { left: 0; padding-block-start: 4vh; }
#ambient .gutter.right { right: 0; }

.slip {
  writing-mode: vertical-rl;
  font-family: var(--font-brush);
  line-height: 1.9;              /* column spacing inside a scroll */
  letter-spacing: 0.26em;
  color: rgba(17, 17, 17, 0.12);
  white-space: nowrap;
  height: max-content;
  flex: none;
  padding: 0.9rem 0.5rem;
  /* vertical-rl: inline-start = top — one rule above, like the rod
     of a hanging scroll; no rule below */
  border-inline-start: 1px solid rgba(73, 8, 14, 0.065);
}

.slip .author {
  font-size: 0.72em;
  letter-spacing: 0.12em;
  margin-block-start: 3em;       /* colophon starts low, classical style */
  color: rgba(73, 8, 14, 0.17);
}

/* deliberate irregularity: per-scroll size and sideways drift.
   Left side: a single, slightly larger scroll (the literary quote). */
.gutter.right .slip:nth-of-type(1) { font-size: 1.9rem;  transform: translateX(14px); }
.gutter.right .slip:nth-of-type(2) { font-size: 1.45rem; transform: translateX(-24px); }
.gutter.left  .slip:nth-of-type(1) { font-size: 2.05rem; transform: translateX(-18px); }
.gutter.left  .slip:nth-of-type(2) { font-size: 1.5rem;  transform: translateX(22px); }

/* ink-wash horizon: full strength in the gutters, faded to ~30%
   behind the content column so entries stay readable */
#horizon {
  position: fixed;
  left: 0;
  right: 0;
  bottom: 0;
  z-index: 0;
  pointer-events: none;
  -webkit-mask-image: linear-gradient(to right,
    #000 0, #000 calc(50% - 590px),
    rgba(0, 0, 0, 0.3) calc(50% - 500px), rgba(0, 0, 0, 0.3) calc(50% + 500px),
    #000 calc(50% + 590px), #000 100%);
  mask-image: linear-gradient(to right,
    #000 0, #000 calc(50% - 590px),
    rgba(0, 0, 0, 0.3) calc(50% - 500px), rgba(0, 0, 0, 0.3) calc(50% + 500px),
    #000 calc(50% + 590px), #000 100%);
}

@media (max-width: 1320px) {
  #ambient .gutter { display: none; }
}

/* ============================================================
   READER
   ============================================================ */
.toolbar-btn {
  padding: var(--space-xs) var(--space-s);
  border: 1px solid var(--color-divider);
  border-radius: 2px;
  color: var(--color-text-muted);
  white-space: nowrap;   /* "拼 all" stays one line, even in tight navbars */
}

.toolbar-btn:hover { background: var(--color-surface-alt); }

.toolbar-btn[aria-pressed="true"] {
  border-color: var(--color-accent);
  color: var(--color-accent);
}

/* pinyin cycle, tone-colors step: fill the chip so the third state is
   unmistakable — even when the caption is dropped on tiny screens
   (off = outlined muted, pinyin = outlined accent, colors = filled accent) */
.navbar-tools #btn-pinyin.tones-on {
  background: var(--color-accent);
  border-color: var(--color-accent);
  color: var(--color-bg);
}
.navbar-tools #btn-pinyin.tones-on .tool-label { color: var(--color-bg); }

/* grayed, in place: a control whose precondition is off must not
   appear/disappear and shift the row */
.toolbar-btn:disabled {
  color: var(--color-divider);
  border-color: var(--color-divider);
  cursor: default;
}
.toolbar-btn:disabled:hover { background: none; }

/* icon buttons carry a permanent caption: hover tooltips alone would leave
   the hanzi icons opaque to learners, and touch has no hover at all */
.navbar-tools .toolbar-btn {
  display: inline-flex;
  flex-direction: column;
  align-items: center;
  gap: 2px;
}
.navbar-tools .tool-label {
  display: block;
  font-size: 0.55rem;
  line-height: 1;
  color: var(--color-text-muted);
}

/* two-column reading surface */
.reader {
  max-width: var(--content-width);
  margin-inline: auto;
  padding: var(--space-l);
}

.para-row {
  display: grid;
  grid-template-columns: 3fr 2fr;
  gap: var(--space-l);
  margin-block-end: var(--space-l);
}

/* the chapter subtitle-as-first-paragraph is a narrow-screen affordance;
   wide screens keep the subtitle in the header nav row instead */
.subtitle-row { display: none; }

/* story/chapter header: one flat row — story identity left, chapter nav right */
.story-head {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: var(--space-s) var(--space-xl);
  margin-block-end: var(--space-l);
  padding-block-end: var(--space-s);
  border-bottom: 1px solid var(--color-divider);
}

.story-head rt { display: none; }   /* titles stay clean; pinyin via dict lookup */

.story-head-title {
  font-family: var(--font-cjk);
  font-size: 1.8rem;
  color: var(--color-heading);
}

.story-head-tr {
  margin-inline-start: var(--space-s);
  font-family: 'Inter', sans-serif;
  font-size: var(--font-small);
  font-weight: 400;
  letter-spacing: 0;
  color: var(--color-text-muted);
}

.chapter-nav {
  display: flex;
  align-items: baseline;
  gap: var(--space-s);
}

.chapter-arrow {
  font-size: 1.3rem;
  line-height: 1;
  padding: 0 var(--space-s);
  color: var(--color-accent);
}
.chapter-arrow:hover { background: var(--color-surface-alt); border-radius: 3px; }
.chapter-arrow:disabled { color: var(--color-divider); cursor: default; background: none; }

.chapter-title {
  font-family: var(--font-cjk);
  font-size: 1.3rem;
  color: var(--color-heading);
}

.chapter-title-tr,
.chapter-no {
  font-size: var(--font-small);
  color: var(--color-text-muted);
}

/* subtitle folded into the body (narrow screens): heading color marks it as
   the chapter title while it flows and taps like an ordinary sentence */
.chapter-subtitle { color: var(--color-heading); }

.zh-col {
  font-family: var(--font-cjk);
  font-size: var(--zh-size, 1.375rem);
  line-height: 2.0;   /* room for ruby + finger/mouse targets, without the
                         pinyin-on rows feeling airy; also limits how far the
                         iOS ruby annotation can drift into the gap above */
}

/* translation column mirrors the zh paragraph: one flowing block, top-aligned */
.tr-col {
  font-size: calc(var(--zh-size, 1.375rem) * 0.69);   /* tracks A+/A- with the hanzi */
  color: var(--color-text-muted);
  line-height: 1.7;
}

/* sentences */
.sentence { cursor: pointer; border-radius: 3px; padding-bottom: 0.1em; }
.sentence:hover { background: var(--color-surface); }
.sentence.active { background: var(--color-surface-alt); }

/* with pinyin shown, trim the highlight's top: gap below the pinyin,
   but keep shading headroom above the hanzi */
.pinyin-all .sentence:hover {
  background: linear-gradient(transparent 0.15em, var(--color-surface) 0.15em);
}
.pinyin-all .sentence.active {
  background: linear-gradient(transparent 0.15em, var(--color-surface-alt) 0.15em);
}

.tr-sentence {
  border-radius: 3px;
  padding-inline: 0.15em;   /* breathing room for the active highlight */
}
.tr-sentence.veiled { filter: blur(4px); user-select: none; }
.tr-sentence.active { background: var(--color-surface-alt); color: var(--color-text); }

.tr-inline {   /* narrow viewports: translation under the tapped sentence */
  display: none;
  font-family: 'Inter', sans-serif;
  font-size: calc(var(--zh-size, 1.375rem) * 0.69);
  color: var(--color-text-muted);
  line-height: 1.7;
  background: var(--color-surface);
  border-left: 3px solid var(--color-accent);
  border-radius: 3px;
  padding: var(--space-s) var(--space-m);
  margin-block: var(--space-s);
}

/* culture note / expanded grammar hint: outlined box, clearly distinct
   from translation text */
.note {
  margin-block-start: var(--space-s);
  padding: var(--space-xs) var(--space-s);
  border: 1px solid var(--color-accent);
  border-radius: 3px;
  font-size: 0.92em;   /* scales with its column (tr-col / tr-inline) */
  color: var(--color-text-muted);
}

.note-label {
  display: block;
  font-size: 0.75em;
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: 0.06em;
  color: var(--color-accent);
}

/* generated grammar layer: dotted marks on rule-anchor tokens (visible
   while the toolbar grammar toggle is on), chip row under the active
   sentence, matched-token highlight while a chip is expanded */
.grammar-on .sentence .hint-panel-anchor {
  color: #00662c; 
  /* #004d21 */
}

.grammar-on .sentence .hint-chip-anchor {
  color: #00662c;
}

.hint-hl { background: rgba(0, 102, 44, 0.16); border-radius: 3px; }

.hint-chips {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-xs);
  margin-block-start: var(--space-s);
}

.hint-chip {
  font: inherit;
  font-size: 0.78em;
  padding: 0.1em 0.6em;
  border: 1px solid var(--color-accent);
  border-radius: 999px;
  background: none;
  color: var(--color-accent);
  cursor: pointer;
}
.hint-chip:not(.first) { opacity: 0.55; }   /* repeat within the chapter */
.hint-chip[aria-expanded="true"] {
  background: var(--color-accent);
  color: var(--color-bg);
}

/* end of the last chapter: the finish mark, centered under the text */
.story-end {
  display: flex;
  justify-content: center;
  margin-block-start: var(--space-xl);
}

/* Here the whole control is the stamp — glyph and label share one outline,
   rather than a framed glyph with the text loose beside it. */
.story-end-btn {
  display: inline-flex;
  align-items: center;
  gap: var(--space-s);
  padding: var(--space-s) var(--space-m);
  border: 1px solid currentcolor;
  border-radius: 3px;
  background: none;
  font: inherit;
  font-size: var(--font-small);
  color: var(--color-text-muted);
  cursor: pointer;
  opacity: 0.7;
  transition: opacity 0.12s, background-color 0.12s, color 0.12s;
}
.story-end-btn:hover { opacity: 1; }

/* The button carries the frame, so the glyph inside drops its own. `color`
   must be re-inherited explicitly: the base .read-seal sets a muted gray,
   which would otherwise beat inheritance and leave the glyph dark on the
   accent fill while the label went paper-white beside it. */
.story-end-btn .read-seal {
  width: auto;
  height: auto;
  border: none;
  background: none;
  color: inherit;
  opacity: 1;
  font-size: 1.2rem;
}

.story-end-btn[aria-pressed="true"] {
  background: var(--color-accent);
  border-color: var(--color-accent);
  color: var(--color-bg);
  opacity: 1;
}

/* provenance colophon: muted author/source/license at the end of the text —
   discoverable without competing with the reading surface */
.story-colophon {
  margin-block-start: var(--space-xl);
  border-top: 1px solid var(--color-divider);
  padding-block-start: var(--space-s);
  font-size: 0.78rem;
  line-height: 1.5;
  color: var(--color-text-muted);
}

.story-colophon .colophon-label {
  display: inline-block;
  min-width: 3.6rem;
  font-size: 0.68rem;
  text-transform: uppercase;
  letter-spacing: 0.06em;
}

.story-colophon a { text-decoration: underline; overflow-wrap: anywhere; }

/* both carry an xl top margin; stacked, one of them is enough */
.story-end + .story-colophon { margin-block-start: var(--space-l); }

/* words + pinyin ruby */
.w { cursor: pointer; }

/* touch: long-press on a word opens the dictionary, not the OS text selection */
@media (pointer: coarse) {
  .w {
    user-select: none;
    -webkit-user-select: none;
    -webkit-touch-callout: none;
  }
}
.w:hover ruby { color: var(--color-accent); }
.w.name ruby { border-bottom: 1px dotted var(--color-text-muted); }
.x { cursor: default; }

/* Native ruby is kept deliberately. Two rewrites that replaced it with a CSS
   stack (inline-flex column, then inline-block) each broke something ruby
   gives for free: the flex version lost the baseline (column flex synthesizes
   it from the box edge), so the sentence highlight landed on the pinyin
   instead of the hanzi; the inline-block version lost ruby's horizontal
   pairing, so the pinyin no longer centred over its character. Do not swap
   ruby layout out again without testing on a real iOS device.
   Known remaining limitation: on WebKit (every iOS browser) the annotation is
   drawn in the line's leading, so symmetric half-leading centres it between
   the two rows rather than hugging its own hanzi the way Chromium does.
   `padding-bottom` here must stay small — it pushes the pinyin further UP,
   off its hanzi and toward the row above (it used to be 0.6em, which is what
   made the drift so pronounced). Row-to-row gap = the .zh-col line-height. */
ruby rt {
  font-family: 'Inter', sans-serif;
  font-size: 0.5em;   /* relative to hanzi size, scales with the A+/A- controls */
  line-height: 1;
  color: var(--color-text-muted);
  letter-spacing: 0.02em;
  padding-bottom: 0.1em;   /* hair of air; must stay small (see note above) */
}

/* Touch devices are where WebKit's ruby placement bites (every iOS browser):
   it draws the annotation in the line's leading instead of growing the line
   box, so the pinyin floats mid-gap rather than hugging its own hanzi.
   Chromium already hugs the base, so this is scoped to coarse pointers to
   leave desktop — which renders correctly — alone.
   Calibration: the row gap is only ~0.85em at the .zh-col line-height and the
   pinyin is ~0.5em of it, so "centred" and "sitting on its hanzi" are barely
   0.15em apart; pulling the annotation down by about that much leaves the
   larger remaining clearance toward the row above.
   Tune: more negative = closer to its own hanzi. Back off if the pinyin
   starts crowding or touching the characters. */
@media (pointer: coarse) {
  ruby rt { margin-bottom: -0.18em; }
}

/* pinyin visibility modes */
.pinyin-off rt { display: none; }

/* tone coloring (opt-in via .tones on <body>) */
.tones .t1 { color: var(--tone-1); }
.tones .t2 { color: var(--tone-2); }
.tones .t3 { color: var(--tone-3); }
.tones .t4 { color: var(--tone-4); }
.tones .t0 { color: var(--tone-0); }

/* ============================================================
   DICTIONARY PANEL
   ============================================================ */
.dictpanel {
  position: fixed;
  z-index: 200;
  max-height: 60vh;
  overflow-y: auto;
  background: var(--color-bg);
  border: 1px solid var(--color-divider);
  border-radius: 4px;
  box-shadow: 0 2px 16px rgba(0, 0, 0, 0.12);
  padding: var(--space-m);
  font-size: var(--font-small);
}

.dictpanel.pinned {
  top: calc(var(--navbar-height) + var(--space-l)) !important;
  left: auto !important;
  right: var(--space-m);
  width: 320px !important;
  max-width: calc(100vw - 2 * var(--space-m));
  max-height: calc(100vh - var(--navbar-height) - 2 * var(--space-l));
  max-height: calc(100dvh - var(--navbar-height) - 2 * var(--space-l));
}

.dictpanel-head {
  display: flex;
  align-items: baseline;
  gap: var(--space-s);
  margin-block-end: var(--space-s);
}

.dictpanel-word {
  font-family: var(--font-cjk);
  font-size: 1.6rem;
  color: var(--color-heading);
}

.dictpanel-pinyin { color: var(--color-accent); }

.dictpanel-controls { margin-inline-start: auto; white-space: nowrap; }
.dictpanel-controls .toolbar-btn { border: none; }

.dictpanel-name {
  color: var(--color-text-muted);
  font-style: italic;
  margin-block-end: var(--space-xs);
}

.dictpanel-entry {
  border-top: 1px solid var(--color-divider);
  padding-block: var(--space-s);
}

.dictpanel-badges { margin-block-end: var(--space-xs); }
.dictpanel-badges .badge { margin-inline-end: var(--space-xs); }

.dictpanel-senses {
  margin: 0;
  padding-inline-start: 1.2em;
}

/* labeled fallback: the entry's only sense list is not in the preferred language */
.dictpanel-langtag {
  color: var(--color-text-muted);
  font-size: var(--font-small);
}

/* grammar/domain tag lifted from HanDeDict senses, e.g. S, Adj, Geo */
.tag {
  display: inline-block;
  padding: 0 var(--space-xs);
  border: 1px solid var(--color-divider);
  border-radius: 2px;
  color: var(--color-text-muted);
  font-size: var(--font-small);
}

/* labeled traditional form, secondary to the simplified headword */
.trad {
  color: var(--color-text-muted);
  font-size: var(--font-small);
  white-space: nowrap;
}

/* usage example beneath a sense: 男人 – Mann */
.dict-ex {
  margin-block-start: var(--space-xs);
  padding-inline-start: var(--space-s);
  font-size: var(--font-small);
}

/* classifier row: measure words: 个 gè */
.dict-cl {
  margin-block-start: var(--space-xs);
  color: var(--color-text-muted);
  font-size: var(--font-small);
}
.dict-cl .dictpanel-pinyin,
.dict-ex .zh,
.dict-cl .zh { font-size: 1rem; }

.dictpanel-empty { color: var(--color-text-muted); font-style: italic; }

/* grammar section: rule(s) the tapped word anchors in its sentence */
.dictpanel-grammar {
  border-top: 1px solid var(--color-divider);
  padding-block-start: var(--space-s);
  margin-block-start: var(--space-s);
  font-size: var(--font-small);
}
.dictpanel-grammar .note-label { margin-block-end: var(--space-xs); }
.dictpanel-grammar-rule { margin-block-end: var(--space-xs); }
.dictpanel-grammar-rule:last-child { margin-block-end: 0; }
.dictpanel-grammar-label { font-weight: 600; }
.dictpanel-grammar-label .badge { margin-inline-start: var(--space-xs); }

.dictpanel-chars {
  border-top: 1px solid var(--color-divider);
  padding-block-start: var(--space-s);
  margin-block-start: var(--space-s);
  color: var(--color-text-muted);
}
.dictpanel-chars a { color: var(--color-accent); font-size: 1.1rem; }

/* the way to the detail page, kept in the header: on a phone the panel is
   a bottom sheet, where anything below the first block needs scrolling */
.dictpanel-controls .dictpanel-detail {
  font-family: var(--font-cjk);
  color: var(--color-accent);
  text-decoration: none;
}

/* …and spelled out at the end of the panel, below the characters row where
   there is one — which already draws the divider between them */
.dictpanel-chars .dictpanel-detail { font-size: 1rem; }
.dictpanel-chars + .dictpanel-detail-row {
  border-top: none;
  padding-block-start: 0;
  margin-block-start: var(--space-xs);
}

.dictpanel-extend { margin-block-start: var(--space-s); }

/* ============================================================
   HOVER PREVIEW (desktop only, created by js/tip.js: reader
   words and character chips share it)
   ============================================================ */
.tip {
  position: fixed;
  z-index: 300;
  max-width: 300px;
  display: flex;
  align-items: baseline;
  gap: var(--space-s);
  pointer-events: none;   /* never intercepts the click gestures */
  background: var(--color-bg);
  border: 1px solid var(--color-divider);
  border-radius: 4px;
  box-shadow: 0 2px 12px rgba(0, 0, 0, 0.12);
  padding: var(--space-xs) var(--space-s);
  font-size: var(--font-small);
}

.tip-pinyin { color: var(--color-accent); white-space: nowrap; }

.tip-gloss {
  overflow: hidden;   /* a preview stays one line; the panel has the rest */
  text-overflow: ellipsis;
  white-space: nowrap;
}

.tip-grammar {
  color: var(--color-text-muted);
  font-style: italic;
  white-space: nowrap;
}

/* ============================================================
   HANZI HUB — link cards (catalog .link-card)
   ============================================================ */
.card-list {
  display: grid;
  gap: var(--space-m);
  max-width: var(--prose-measure);
  margin-inline: auto;
  padding-block: var(--space-l);
}

.link-card {
  display: block;
  background: var(--color-surface);
  border-left: 3px solid var(--color-accent);
  border-radius: 4px;
  padding: var(--space-m) var(--space-l);
  font-size: 0.95rem;
}

.link-card:hover { background: var(--color-surface-alt); }

.link-card .zh { font-size: 1.2rem; margin-inline-end: var(--space-s); }

.link-card .desc {
  display: block;
  font-size: var(--font-small);
  color: var(--color-text-muted);
}

/* ============================================================
   DICTIONARY & CHARTS — search block, chart grids, character detail.
   Charts keeps the library's sticky .ctrl bar; the Dictionary has none,
   its one control living in the content column instead.
   ============================================================ */
.hanzi-page { padding-block: var(--space-l); }

/* poster downloads, right-aligned in the chart control row */

/* --- the Dictionary's search block ------------------------------------
   One block in two sizes: the landing state is the page's whole purpose,
   so it stands large and alone; once something has been asked it shrinks
   above the answer and keeps the query in view. The state is decided
   before first paint by the inline classifier in dictionary.html
   (html.dict-result), never by a class flipped after render. */
.dict-search {
  display: flex;
  flex-direction: column;
  gap: var(--space-m);
  align-items: center;
  text-align: center;
  max-width: 34rem;
  margin: 0 auto;
  padding-block: clamp(var(--space-l), 9vh, 6rem) var(--space-xl);
}

.dict-search h1 { font-size: var(--font-h2); }

.dict-search h1 .zh {
  font-weight: 600;
  margin-inline-end: var(--space-s);
}

.dict-searchbar {
  display: flex;
  gap: var(--space-s);
  width: 100%;
}

.dict-searchbar input {
  flex: 1;
  min-width: 0;
  font: inherit;
  padding: var(--space-s) var(--space-m);
  background: var(--color-bg);
  border: 1px solid var(--color-divider);
  border-radius: 2px;
}
.dict-searchbar input:focus { outline: none; border-color: var(--color-accent); }

.dict-landing {
  color: var(--color-text-muted);
  font-size: var(--font-small);
}

/* --- type | draw ------------------------------------------------------
   A segmented pair, not a toolbar of buttons: the two are one choice, and
   the choice is which question you are asking, not what happens next. */
.dict-modes {
  display: flex;
  border: 1px solid var(--color-divider);
  border-radius: 2px;
  overflow: hidden;
}

.dict-mode {
  font: inherit;
  font-size: var(--font-small);
  padding: 2px var(--space-m);
  border: none;
  background: none;
  color: var(--color-text-muted);
  cursor: pointer;
}
.dict-mode + .dict-mode { border-inline-start: 1px solid var(--color-divider); }
.dict-mode:hover { background: var(--color-surface); }
.dict-mode[aria-selected="true"] {
  background: var(--color-surface-alt);
  color: var(--color-text);
}

/* --- the draw pad -----------------------------------------------------
   Sized to the writing hand, not to the column: a square that stays
   comfortably thumb-reachable on a phone and does not sprawl on a desktop.
   The canvas carries the ink colours as custom properties because a canvas
   context cannot read the palette any other way. */
.dict-pad {
  display: flex;
  flex-direction: column;
  gap: var(--space-s);
  align-items: center;
  width: 100%;
}

.hw-board {
  width: min(100%, 20rem);
  aspect-ratio: 1;
  background: var(--color-bg);
  border: 1px solid var(--color-divider);
  border-radius: 2px;
}

.hw-canvas {
  --hw-ink: var(--color-text);
  --hw-grid: var(--color-divider);
  display: block;
  width: 100%;
  height: 100%;
  /* without this the browser scrolls the page instead of drawing */
  touch-action: none;
  cursor: crosshair;
}

.hw-controls { display: flex; gap: var(--space-s); }

/* Wrong stroke order and a wrong stroke count both push the character down
   the list rather than off it, so this is a grid to scan, never one answer.
   Tap targets stay finger-sized: this is the mobile half of the feature. */
.hw-candidates {
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  gap: var(--space-xs);
  min-height: 2.75rem;
}

.hw-candidate {
  font-family: var(--font-cjk);
  font-size: 1.5rem;
  line-height: 1;
  min-width: 2.75rem;
  min-height: 2.75rem;
  padding: var(--space-xs);
  background: var(--color-bg);
  border: 1px solid var(--color-divider);
  border-radius: 2px;
  cursor: pointer;
}
.hw-candidate:hover { background: var(--color-surface-alt); }
.hw-candidate:first-child { border-color: var(--color-accent); }

/* nothing matched well: the list is still shown (a weak score is not proof
   the character is absent) but the accent that says "this is the one" is
   withdrawn, and the status line below says why. Subtle on purpose — the
   signal is a hint about a thin measured margin, not a verdict. */
.hw-unsure .hw-candidate:first-child { border-color: var(--color-divider); }
.hw-unsure .hw-candidate { opacity: 0.75; }

.hw-status {
  color: var(--color-text-muted);
  font-size: var(--font-small);
  text-align: center;
}

/* the landing line explains typing; the pad's own status line explains
   drawing, and one instruction at a time is enough */
.dict-draw .dict-landing { display: none; }

/* #draw-debug only: the lookup's working, and the captured strokes in a
   form that can be pasted somewhere and replayed. Monospace and unstyled
   on purpose — an instrument, not part of the page. */
.hw-debug {
  width: 100%;
  overflow-x: auto;
  font-size: 0.75rem;
  line-height: 1.5;
  color: var(--color-text-muted);
  white-space: pre;
}

.hw-debug-json {
  width: 100%;
  font-family: inherit;
  font-size: 0.7rem;
  color: var(--color-text-muted);
  background: var(--color-surface);
  border: 1px solid var(--color-divider);
  resize: vertical;
}

/* #draw-capture only: collecting real drawings as fixtures. Same posture as
   the debug panel — plain, English, clearly not part of the page. */
.hw-capture {
  width: 100%;
  margin-top: var(--space-m);
  padding-top: var(--space-s);
  border-top: 1px solid var(--color-divider);
  font-size: var(--font-small);
}

.hw-cap-head {
  display: flex;
  align-items: baseline;
  gap: var(--space-s);
}

.hw-cap-target {
  font-family: var(--font-cjk);
  font-size: 2rem;
  line-height: 1;
}

.hw-cap-why { color: var(--color-text-muted); }

.hw-cap-progress {
  margin-left: auto;
  color: var(--color-text-muted);
  font-variant-numeric: tabular-nums;
}

.hw-cap-result {
  display: block;
  min-height: 1.4em;
  color: var(--color-text-muted);
}

/* anything but a clean rank 1 is what this exercise is looking for */
.hw-cap-result.hw-cap-miss { color: var(--color-accent); }

.hw-cap-controls {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-s);
  margin-top: var(--space-s);
}

/* Answered, with the pad still open: keep drawing the next character, but
   give the answer the room. The candidates stay — the first pick is often
   not the right one, and re-drawing to correct it would be a punishment. */
.dict-result .dict-pad { flex: 1 1 100%; }
.dict-result .hw-board { width: min(100%, 11rem); }
.dict-result .hw-candidate { font-size: 1.25rem; }

/* asked and answered: the same block, out of the way */
.dict-result .dict-search {
  flex-direction: row;
  flex-wrap: wrap;   /* the pad takes a line of its own when it is open */
  align-items: baseline;
  gap: var(--space-m);
  max-width: none;
  margin: 0;
  padding-block: 0 var(--space-l);
  text-align: start;
}
.dict-result .dict-search h1 { font-size: 1.35rem; }
.dict-result .dict-searchbar { flex: 1 1 240px; max-width: 460px; }
.dict-result .dict-searchbar input {
  font-size: var(--font-small);
  padding: 1px var(--space-s);
}
.dict-result .dict-searchbar .toolbar-btn {
  padding: 1px var(--space-s);
  font-size: var(--font-small);
}
.dict-result .dict-landing { display: none; }

.search-head { margin-block-end: var(--space-m); }
.search-empty { color: var(--color-text-muted); }

/* Latin input is two questions at once ("sun" is a reading and a meaning),
   so each answer gets its own titled block. */
.search-section + .search-section { margin-block-start: var(--space-l); }
.search-section-head {
  margin-block-end: var(--space-s);
  color: var(--color-text-muted);
  font-size: var(--font-small);
  font-weight: normal;
  text-transform: lowercase;
}
.search-note { margin-inline-start: var(--space-s); font-style: italic; }

/* A meaning result must print its gloss: it is the answer, not a preview,
   so this is a row list rather than a chip flow. */
.meaning-list { display: flex; flex-direction: column; gap: var(--space-xs); }
.meaning-row {
  display: flex;
  align-items: baseline;
  flex-wrap: wrap;
  gap: var(--space-s);
}
.meaning-row .wordlink { flex: none; }
.meaning-pinyin { color: var(--color-accent); font-size: var(--font-small); }
.meaning-gloss { color: var(--color-text-muted); font-size: var(--font-small); }

.chart-head { text-align: center; margin-block-end: var(--space-l); }
.chart-head .sub { color: var(--color-text-muted); font-size: var(--font-small); }

/* fixed 10 columns: rank = row × 10 + column (5 on narrow viewports) */
.chart-grid {
  display: grid;
  grid-template-columns: repeat(10, 1fr);
  border-top: 1px solid var(--color-divider);
  border-left: 1px solid var(--color-divider);
}

.chart-cell {
  position: relative;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 2px;
  padding: var(--space-m) var(--space-xs) var(--space-s);
  border-right: 1px solid var(--color-divider);
  border-bottom: 1px solid var(--color-divider);
  text-align: center;
}
.chart-cell:hover { background: var(--color-surface); }

.chart-rank {
  position: absolute;
  top: 4px;
  left: 6px;
  font-size: 0.7rem;
  color: var(--color-accent);
}

.chart-variant {
  position: absolute;
  top: 4px;
  right: 6px;
  font-size: 0.9rem;
  color: var(--color-text-muted);
}

.chart-hanzi { font-size: 2.2rem; line-height: 1.2; color: var(--color-text); }
.chart-pinyin { color: var(--color-accent); font-weight: 600; font-size: var(--font-small); }
.chart-meaning { color: var(--color-text-muted); font-size: 0.75rem; }

.chart-source {
  margin-block-start: var(--space-l);
  text-align: center;
  color: var(--color-text-muted);
  font-size: var(--font-small);
}

/* chart index: one row per sheet, rendered chart below (see charts.html) */
.chart-index { margin-block-end: var(--space-xl); }

/* the heading moved out of the control bar into the page, and has to bring
   its own type with it (matches .dict-search h1) */
.chart-index h1 { font-size: var(--font-h2); }

.chart-index h1 .zh {
  font-weight: 600;
  margin-inline-end: var(--space-s);
}

.chart-format { margin-block-start: var(--space-l); }

.chart-format button[aria-pressed="true"] {
  border-color: var(--color-accent);
  color: var(--color-accent);
}

.chart-list {
  list-style: none;
  margin: var(--space-m) 0 0;
  padding: 0;
  display: grid;
  gap: var(--space-xs);
}

.chart-list li {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: var(--space-xs) var(--space-s);
  padding-block: var(--space-s);
  border-block-start: 1px solid var(--color-divider);
}

.chart-row-name { font-weight: 600; }

.chart-row-note {
  color: var(--color-text-muted);
  font-size: var(--font-small);
  /* push the buttons to the far edge on roomy viewports */
  margin-inline-end: auto;
}

/* the sheet currently on screen, so the list says which one that is */
.chart-show[aria-pressed="true"] {
  border-color: var(--color-accent);
  color: var(--color-accent);
}

.chart-index-note {
  margin-block-start: var(--space-l);
  color: var(--color-text-muted);
  font-size: var(--font-small);
}

/* character detail */
.hanzi-detail {
  display: grid;
  grid-template-columns: minmax(220px, 300px) 1fr;
  gap: var(--space-xl);
  max-width: var(--content-width);
  margin-inline: auto;
}

.strokes-box { width: 100%; }

.strokes-svg { width: 100%; height: auto; display: block; }
.strokes-grid path { stroke: var(--color-divider); stroke-dasharray: 8 8; }
.strokes-outline { fill: var(--color-surface-alt); }
.strokes-anim {
  fill: none;
  stroke: var(--color-text);
  stroke-width: 128;
  stroke-linecap: round;
  stroke-linejoin: round;
}

.stroke-count { color: var(--color-text-muted); font-size: var(--font-small); }

.hanzi-detail-glyph { font-size: 8rem; line-height: 1; }

.hanzi-def { color: var(--color-text); margin-block: var(--space-s); }

/* word detail: the character page's info column, without the stroke pane
   beside it (a word has no strokes of its own to animate) */
.word-detail { max-width: var(--content-width); margin-inline: auto; }

/* radical and decomposition read as sections like "component of" below:
   same heading face, chips on their own line under it */
.hanzi-facts {
  margin-block: var(--space-m);
  padding-block: var(--space-s);
  border-top: 1px solid var(--color-divider);
  border-bottom: 1px solid var(--color-divider);
}
.hanzi-facts dt {
  font-family: 'Cinzel', serif;
  font-size: 1rem;
  font-weight: 700;   /* a <dt> is normal weight where an <h3> is bold */
  letter-spacing: 0.02em;
  line-height: 1.2;
  color: var(--color-heading);
  margin-block: var(--space-m) var(--space-s);
}
.hanzi-facts dt:first-child { margin-block-start: 0; }
.hanzi-facts dd {
  margin: 0;
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-xs);
}
.hanzi-facts .ids { color: var(--color-text-muted); }

.hanzi-related h3 { font-size: 1rem; margin-block: var(--space-m) var(--space-s); }

.charflow { display: flex; flex-wrap: wrap; gap: var(--space-s); }

/* one chip style for every "tap to look up" element: search results,
   component-of / words flows, radical + decomposition facts, and the
   characters row inside the dictionary panel */
.charlink,
.wordlink {
  display: inline-block;
  font-size: 1.3rem;
  padding: 2px 6px;
  border: 1px solid var(--color-divider);
  border-radius: 2px;
  cursor: pointer;
  /* the chip's own gestures: no text selection under a double-click, no
     callout or double-tap zoom under a hold (see js/chips.js) */
  user-select: none;
  -webkit-user-select: none;
  -webkit-touch-callout: none;
  touch-action: manipulation;
}
.charlink:hover,
.wordlink:hover {
  background: var(--color-surface-alt);
  color: var(--color-accent);
}

/* held long enough to mean "open the page", shortly before it fires */
.charlink.arming,
.wordlink.arming {
  border-color: var(--color-accent);
  background: var(--color-surface-alt);
}

/* touch: finger-sized lookup chips */
@media (pointer: coarse) {
  .charlink,
  .wordlink { padding: 6px 10px; }
}

/* gloss mode: the chip becomes a chart cell — character, reading, meaning.
   The nodes are always in the DOM once fetched; only the toggle hides them */
.chip-gloss { display: none; }

.chips-glossed .charlink.glossed,
.chips-glossed .wordlink.glossed {
  display: inline-flex;
  flex-direction: column;
  align-items: center;
  max-width: 9rem;
  padding-inline: var(--space-xs);
  line-height: 1.25;
}
.chips-glossed .chip-gloss {
  display: block;
  max-width: 100%;
  color: var(--color-text-muted);
  font-family: 'Inter', sans-serif;   /* out of the chip's CJK face */
  font-size: 0.62rem;
  text-align: center;
}
.chip-gloss-pinyin { color: var(--color-accent); }
.chip-gloss-text {
  display: block;
  overflow: hidden;   /* one line per chip: the panel has the rest */
  text-overflow: ellipsis;
  white-space: nowrap;
}

.gloss-alt { color: var(--color-text-muted); font-size: 0.9em; }

/* foot of the chip views: gesture cheat sheet (mirrors the reader
   footer's), ending in the gloss toggle as an inline link */
.chip-hints {
  margin-block-start: var(--space-l);
  padding-block-start: var(--space-s);
  border-top: 1px solid var(--color-divider);
  color: var(--color-text-muted);
}
.gloss-toggle {
  font: inherit;
  padding: 0;
  border: none;
  background: none;
  color: var(--color-accent);
  text-decoration: underline;
  cursor: pointer;
}
.gloss-toggle:hover { text-decoration: none; }

/* ============================================================
   RESPONSIVE
   ============================================================ */
@media (max-width: 768px) {
  .container { padding-inline: var(--space-m); }

  .navbar { padding-inline: var(--space-m); }
  .navbar-brand { font-size: 1.2rem; }
  .navbar-links { gap: var(--space-m); }

  /* reader navbar collapses to a single row to reclaim vertical space for
     reading: a back button (left), the reading-tool chips, then the EN|DE
     switch. Brand and page links are dropped — the chips are what matter. */
  .reader-page { --navbar-height: 3.5rem; }
  .reader-page .navbar { flex-wrap: nowrap; gap: var(--space-s); }
  .reader-page .navbar-brand { display: none; }
  .reader-page .navbar-back { display: inline-flex; }
  .reader-page .navbar-right { display: contents; }   /* tools + lang become navbar children */
  .reader-page .navbar-tools {
    order: 1;
    flex: 1 1 auto;
    min-width: 0;
    overflow-x: auto;   /* the chips scroll if a narrow phone can't fit them */
    scrollbar-width: none;
  }
  /* keep only the EN|DE switch, styled as a chip so it sits in the row */
  .reader-page .navbar-links { order: 2; flex: 0 0 auto; gap: 0; }
  .reader-page .navbar-links li:not(.navbar-lang) { display: none; }
  .reader-page .navbar-lang {
    border: 1px solid var(--color-divider);
    border-radius: 2px;
  }
  .reader-page .navbar-lang button { padding: 6px 8px; }

  /* translations are inline-only on mobile (per tapped sentence), so the
     translation toggle is moot; the grammar toggle stays (chips + marks) */
  #btn-tr { display: none; }

  .ctrl-inner { padding-inline: var(--space-m); }

  /* navbar: drop the "Chinese Reader" wordmark, keep 读 as a compact logo so
     the page links + language switch fit one row on small screens */
  .brand-text { display: none; }

  /* Library: reclaim vertical space. The control bar is sticky, so every
     line it wraps to is subtracted from every screenful of stories, not just
     the first. Each filter is already a single dropdown at every width, so
     only the headline goes here — leaving three lines: search (alone in the
     h1's row once the h1 itself is hidden), then level + status, collection
     wrapping onto their own line(s). */
  .library-page .ctrl-inner h1 { display: none; }

  /* the collection select's options are self-describing ("All shelves (82)",
     "寓言与成语 · Fables & Idioms (24)"), so its label carries nothing;
     level and status keep theirs, their values being bare ("All", "HSK3") */
  .library-page label[for="coll-select"] { display: none; }

  /* three HSK percentages are the longest run on a card's stats line, and
     the level badge above already carries the headline difficulty */
  .entry-cov { display: none; }

  /* dropdowns are width-fit to their selected option (library.js); this caps
     a very long collection title so it can't overflow the narrowest phones */
  .ctrl select { max-width: 100%; }

  /* shelf head: stack the translated title under the Chinese one — side by
     side doesn't fit a phone; the count stays on the first line, top-right */
  .library-page .shelf-head { flex-wrap: wrap; gap: var(--space-xs) var(--space-s); }
  .library-page .shelf-en { order: 1; flex-basis: 100%; }

  .reader { padding: var(--space-m); }

  /* story head stacks: title row, then chapter nav as a full-width row */
  .story-head { gap: var(--space-s) var(--space-m); }
  .story-head-title { font-size: 1.5rem; }
  .chapter-nav {
    flex: 1 1 100%;
    align-items: center;
    justify-content: space-between;
  }
  .chapter-title { white-space: nowrap; }
  .chapter-title-tr { flex: 1 1 0; min-width: 0; }
  .chapter-no { white-space: nowrap; }
  .chapter-arrow { min-width: 44px; min-height: 44px; }   /* finger-sized targets */

  /* subtitled multi-chapter stories: the subtitle drops into the body as its
     first paragraph (reader.js), so hide the header's subtitle + nav row and
     reclaim its height — swipe still changes chapters */
  .has-chapter-subtitle .chapter-nav { display: none; }
  .subtitle-row { display: grid; }

  .para-row { grid-template-columns: 1fr; }

  /* narrow fallback: translation appears under the tapped sentence */
  .tr-col { display: none; }
  .tr-inline { display: block; }

  /* dictionary popover becomes a bottom sheet: predictable position,
     never clipped; pinning is a wide-screen affordance */
  .dictpanel, .dictpanel.pinned {
    left: 0 !important;
    right: 0 !important;
    top: auto !important;
    bottom: 0 !important;
    width: auto !important;
    max-width: none;
    max-height: 55vh;
    border-radius: 8px 8px 0 0;
    border-inline: none;
    border-bottom: none;
    box-shadow: 0 -4px 24px rgba(0, 0, 0, 0.18);
  }
  .dictpanel-pin { display: none; }

  .hanzi-detail { grid-template-columns: 1fr; gap: var(--space-l); }

  /* stroke animation: don't blow up to full phone width in the 1-col layout */
  .hanzi-detail-strokes { width: 100%; max-width: 320px; margin-inline: auto; }

  /* 16px floor: below that iOS Safari auto-zooms into the focused input
     (listed for both search states, so the compact one cannot win on
     specificity and drop back under the floor) */
  .dict-searchbar input,
  .dict-result .dict-searchbar input,
  .lib-searchbar input { font-size: 1rem; }

  /* the search takes the row the (hidden) headline vacated */
  .lib-searchbar { max-width: none; }

  /* half-decades: rank structure stays recoverable */
  .chart-grid { grid-template-columns: repeat(5, 1fr); }
}

@media (max-width: 480px) {
  /* brand + page links must share one row down to 320px-wide phones */
  .navbar-brand { font-size: 1.05rem; }

  /* legibility beats decade alignment on phones */
  .chart-grid { grid-template-columns: repeat(auto-fill, minmax(96px, 1fr)); }
}

@media (max-width: 360px) {
  /* smallest phones: drop the tool captions and keep the hanzi icons, so a
     control is never pushed off-screen. The icons + pressed/filled states
     still convey each toggle; the tooltips carry the full label. */
  .reader-page .navbar-tools .tool-label { display: none; }
  .reader-page .navbar-tools { gap: var(--space-xs); }
}

/* ============================================================
   PRINT — backs the hanzi chart "export PDF" button (print to
   PDF); strips all chrome so any page prints content-only
   ============================================================ */
@media print {
  .navbar, .footer, .ctrl, #ambient, .dictpanel, .tip, .chart-index,
  .chip-hints, .story-end, .dict-search { display: none; }

  body { display: block; background: #fff; }

  main { padding-block-start: 0; }

  .hanzi-page { padding-block: 0; }

  /* keep the on-screen sheet structure: rank = row × 10 + column
     (this section is last, so it wins over the narrow-viewport rules
     that would otherwise apply to the print page width) */
  .chart-grid { grid-template-columns: repeat(10, 1fr); }
  .chart-cell { break-inside: avoid; }
  .chart-hanzi { font-size: 1.9rem; }
}
