/* Nested ruby needs room for an annotation row above AND below every line, so
   the poem states its own line-height rather than inheriting whatever the host
   page uses — a fragment that overlaps its own furigana in someone else's
   <article> is not self-contained. row-gap does the same job for lines the
   flex container has wrapped. */
.poem {
  margin: 0 0 1rem;
  line-height: 2.6;
}
/* Flex, so that whitespace between our tags cannot become whitespace on the
   page: a run of child text that is only white space produces no flex item at
   all. That is what lets this fragment be pasted into a page and reformatted
   by an editor without gaps appearing between words. A deliberate gap is
   written as U+00A0, which is not collapsible white space and so survives as a
   real item. Wrapping happens between words rather than inside one, because
   each annotated word is a single item. */
.poem-line {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  row-gap: 0.5em;
  /* The gap between words. It has to come from CSS rather than from a space in
     the markup, because the markup's spaces are what the layout above
     deliberately discards. It also has to be wider than the gap between the
     characters within a word — which comes from the .txt's own token
     separator, see render() — or a binomial like 離離 looks more divided than
     the boundary between 原上 and 草 does. */
  column-gap: 0.5em;
}
.poem rt { font-size: 0.4em; font-weight: normal; }

/* ==========================================================================
   Grid mode (grid.js). Paragraph mode above is untouched by all of this.

   Every class here is prefixed .poem-grid* / .poem-word* / .poem-char /
   .poem-reading, deliberately: paragraph mode already owns the unscoped name
   .poem-line, and a fragment from either mode has to survive being pasted into
   a page that already contains the other. Nothing below needs scoping to a
   parent to be safe.

   Three nested grids. .poem-grid owns the COLUMNS (declared once, so every
   line shares one column model — that is the alignment guarantee).
   .poem-grid-line owns the ROWS. .poem-word subgrids both, which is what makes
   a word a real element rather than a span count.
   ========================================================================== */
.poem-grid {
  /* The container the fluid type reads. `container-type: inline-size` makes
     100cqw mean "the width of this poem", which is what lets the font size be
     derived from the layout instead of guessed at. Note that cqw cannot be
     used ON this element — that would be circular — so --hanzi is declared
     here but only ever consumed by descendants. */
  container-type: inline-size;
  width: 100%;
  display: grid;

  /* grid-template-columns and --poem-fr are emitted by grid.js as a style
     attribute, because only it knows how many syllables the line has and
     whether there is a caesura: one 1fr per character, 0.5fr per caesura, and
     --poem-fr is their sum. They are derived from one array in columnTracks()
     so they cannot disagree. */

  /* 100cqw / --poem-fr is exactly ONE COLUMN, and that — not the poem's width
     — is the right thing for a character to be a fraction of. A bare fraction
     of the poem would keep the hanzi the same size when a 7-syllable line made
     the columns narrower, and they would overflow.

     ===================== THE ONE KNOB =====================
     0.42 means "a hanzi fills 42% of its column". Turn it up until the
     characters stop looking lost in the width available. Nothing else needs
     touching: the reading and the annotation rows below are fractions of
     --hanzi, so they follow. It is scale-free — it means the same thing at
     46rem and at 22rem, at 5 columns and at 7.

     The clamp bounds only stop it getting silly at extremes, and they must
     stay clear of the values the knob actually produces or they will pin it
     and the knob will appear to do nothing. Five syllables across index.html's
     43rem of content give a 7.8rem column, so the knob's range there is about
     2rem (0.26) to 3.9rem (0.5); at seven syllables the column is 5.7rem and
     the same knob gives 2.4rem. A 3rem ceiling — the obvious-looking choice —
     silently clamps every one of those. If turning the knob stops changing
     anything, you have hit a bound, not a layout limit. */
  --hanzi: clamp(1rem, calc(100cqw / var(--poem-fr) * 0.42), 4.5rem);
  line-height: 1.9;
  margin: 0 0 1rem;
}
/* Assume couplets, matching paragraph mode's own nth-child couplet spacing. */
.poem-grid-line:nth-child(even) { margin-bottom: 0.9em; }

.poem-grid-line {
  grid-column: 1 / -1;
  display: grid;
  /* subgrid, so this line's columns ARE the poem's columns — not a matching
     set, the same set. That is why "position 3 of every line is one column"
     holds by construction rather than by the track lists coincidentally
     agreeing. */
  grid-template-columns: subgrid;

  /* THE ROW MAP. grid.js emits only the first two; the rest are yours, and are
     the reason this mode exists. Every line declares all of them, because an
     `auto` track with nothing in it is zero-high — so unused rows cost no
     space, no markup, and no class to toggle.

     Line names, not numbers, so a hand-written note says grid-row: charnote
     and never has to be renumbered. A subgrid inherits its parent's line
     names, which is why a note nested inside a .poem-word can still refer to
     rows declared here.

     DO NOT add row-gap here. Gaps are not collapsed for empty tracks, so a
     row-gap would turn all four unused annotation rows into visible dead space
     on every un-annotated line — losing exactly the property that makes this
     scheme free. Space notes with their own padding instead. */
  grid-template-rows:
    [text]     auto   /* the hanzi, with pinyin ruby      — emitted */
    [reading]  auto   /* the word's Japanese reading      — emitted */
    [charnote] auto   /* your note on ONE character                 */
    [wordnote] auto   /* your note on ONE word                      */
    [across]   auto   /* your note on ARBITRARY columns             */
    [whole]    auto   /* your note on the WHOLE line                */
    [end];
}

/* A word spans its characters and all the word-scoped rows, so that a note you
   nest inside it has somewhere to land. It stops short of the across/whole
   rows, which belong to the line. */
.poem-word {
  display: grid;
  grid-template-columns: subgrid;
  grid-template-rows: subgrid;
  grid-row: text / across;
  /* `1fr` is minmax(auto, 1fr), so a track's MINIMUM is the min-content width
     of its contents — one long unbreakable annotation word ("flourishing")
     silently widens its column past its 1fr share and unbalances the whole
     poem at narrow widths. min-width: 0 drops that automatic minimum so the
     columns stay strictly proportional; see .poem-note for the wrapping half
     of the fix. */
  min-width: 0;
}

/* The clickable word. One button per WORD rather than per character: a word is
   one thing to activate and one entry in a screen reader's control list.
   It covers text through reading and stops there, which is what keeps the
   hover highlight off the annotation rows underneath. */
.poem-word-body {
  display: grid;
  grid-template-columns: subgrid;
  grid-template-rows: subgrid;
  grid-column: 1 / -1;
  grid-row: text / charnote;
  font: inherit;
  color: inherit;
  background: none;
  border: 0;
  padding: 0;
  margin: 0;
  text-align: inherit;
  cursor: pointer;
  border-radius: 4px;
}
.poem-word-body:hover { background: #ffe9a8; }

.poem-char {
  grid-row: text;
  text-align: center;
  font-size: var(--hanzi);
}
/* Explicit, and not optional. index.html carries `ruby { ruby-position: under }`
   with `ruby ruby { ruby-position: over }` — a pair tuned for paragraph mode,
   whose ruby is NESTED. Grid mode's ruby is single-level, so it matches the
   `under` rule and the pinyin renders below the hanzi unless this says
   otherwise. */
.poem-char ruby { ruby-position: over; }
.poem-char rt { font-size: 0.35em; font-weight: normal; }

/* The word's kana reading, in its own row, spanning the characters it covers.
   It is what paragraph mode holds in the outer ruby; moving it to a row is
   what lets every character have its own column. */
.poem-reading {
  grid-row: reading;
  grid-column: 1 / -1;
  position: relative;
  text-align: center;
  font-size: calc(var(--hanzi) * 0.42);
  opacity: 0.75;
}
/* The word boundary needs its own signal here. Every character column is the
   same width, so 離離 sits exactly as far apart as the boundary between 上 and
   草 — the problem .poem-line's column-gap solves for paragraph mode by making
   the between-word gap wider than the within-word one. A grid cannot do that
   without breaking the columns, so the grouping is drawn instead: a short rule
   over each reading, spanning exactly that word.

   A positioned pseudo-element rather than a border, for two reasons: adjacent
   words' borders would meet and read as one continuous line, and — the reason
   it is not a margin either — a margin would notch the hover highlight, which
   has to stay an unbroken rectangle across the two rows. */
.poem-reading::before {
  content: "";
  position: absolute;
  top: 0;
  left: 0.35em;
  right: 0.35em;
  border-top: 1px solid currentColor;
  opacity: 0.5;
}

/* The caesura: an empty column whose whole job is to be a gap. */
.poem-mark { grid-row: text / across; }

/* ---- Annotation. None of this is emitted; it is here so that what you hand
   write stays short. Add classes freely — these are only defaults. ---- */
.poem-note {
  font-family: -apple-system, system-ui, sans-serif;
  font-size: calc(var(--hanzi) * 0.36);
  text-align: center;
  align-self: start;
  opacity: 0.85;
  /* The other half of the min-width: 0 on .poem-word: with the automatic
     minimum gone, a word longer than its column would overflow instead of
     widening it, so let it break. */
  min-width: 0;
  /* hyphens does the pretty breaks and overflow-wrap is the backstop for what
     it can't hyphenate (a URL, a romanized compound). hyphens is
     LANGUAGE-DRIVEN — it walks up to the nearest lang attribute, so a note
     only hyphenates if some ancestor declares one the UA has a dictionary
     for. index.html has <html lang="en">; a fragment pasted into a page that
     doesn't will silently fall back to overflow-wrap's mid-letter breaks. */
  hyphens: auto;
  hyphenate-limit-chars: 6 3 3;
  overflow-wrap: break-word;
}
/* on ONE character: inside a .poem-word, add grid-column: <n within the word>.
   The numbering is local to the word, so it reads as "the 2nd hanzi of this
   word" rather than requiring a count from the start of the line. */
.poem-note-char { grid-row: charnote; }
/* on ONE word: inside a .poem-word. No grid-column needed. */
.poem-note-word { grid-row: wordnote; grid-column: 1 / -1; }
/* on ARBITRARY columns: on the .poem-grid-line, with absolute grid-column,
   e.g. style="grid-column: 4 / 7" for the three columns after a 2+3 caesura. */
.poem-note-across { grid-row: across; position: relative; }
/* on the WHOLE line: on the .poem-grid-line. Usually a <div>, so it can hold
   arbitrary markup — a translation, a couple of sentences, a link. */
.poem-note-whole {
  grid-row: whole;
  grid-column: 1 / -1;
  text-align: left;
  line-height: 1.5;
  padding: 0.15rem 0 0.5rem;
}

/* The popover block grid.js emits after the poem. Deliberately an ordinary
   box with no `display` override: `display: contents` would promote every
   popover to a child of whatever contains this wrapper, and if the block were
   ever moved inside .poem-grid or a .poem-grid-line each popover would become
   a grid item and get auto-placed into the column tracks. A closed [popover]
   is display:none, so that would not show up immediately — it would surface as
   phantom rows later, which is the worst way to find a bug. */
.poem-glosses { margin: 0; }

/* ========================================================================== */

.gloss {
  display: inline;
  font: inherit;
  color: inherit;
  background: none;
  border: none;
  padding: 0;
  cursor: pointer;
}
.gloss:hover { background: #ffe9a8; }
.gloss-popover {
  /* Explicit, because the browser's [popover] rule would otherwise pin the text
     to CanvasText rather than letting it inherit the page's colour. */
  color: inherit;
  max-width: min(44rem, 90vw);
  max-height: 80vh;
  overflow-y: auto;
  padding: 1rem 1.25rem;
  border: 1px solid #999;
  border-radius: 8px;
  font-family: -apple-system, BlinkMacSystemFont, sans-serif;
  font-size: 0.95rem;
  line-height: 1.6;
}
/* The popover's contents are spans given block layout by CSS, so that the
   markup stays phrasing content and is valid wherever the popover sits. */
.gloss-popover-title {
  display: block;
  position: relative;
  margin-bottom: 0.75rem;
  padding-right: 2rem;
  font-size: 1.4rem;
  font-weight: bold;
  font-family: "Hiragino Mincho ProN", serif;
}
.gloss-popover-reading { font-size: 1rem; font-weight: normal; opacity: 0.8; }
.gloss-popover-close {
  position: absolute;
  top: 0px;
  right: 6px;
  background: none;
  border: none;
  font-size: 2.5rem;
  line-height: 1;
  font-family: -apple-system, BlinkMacSystemFont, sans-serif;
  font-weight: normal;
  color: inherit;
  opacity: 0.6;
  cursor: pointer;
}
.gloss-popover-close:hover { opacity: 1; }
.gloss-source {
  display: block;
  font-weight: bold;
  margin-top: 0.75rem;
}
/* Each line of a dictionary entry is its own block, rather than one text node
   relying on white-space: pre-wrap. Same reason as .poem-line: nothing should
   depend on the literal newlines between our tags. Blank lines separate the
   several JMdict entries a single spelling can have, so an empty one still
   needs to take up space. */
.gloss-definition { display: block; }
.gloss-line { display: block; }
.gloss-gap { height: 0.7em; }
.gloss-help {
  display: block;
  margin-top: 1rem;
  padding-top: 0.6rem;
  border-top: 1px solid #ddd;
  font-size: 0.85rem;
}
.gloss-source-ja { color: #1a5fb4; }
.gloss-source-zh { color: #a51d2d; }
.gloss-source a { color: inherit; }
@media (prefers-color-scheme: dark) {
  /* Both hovers share one dark value. The light-mode #ffe9a8 is a near-white
     wash that only reads as a highlight under dark text; on a dark page it
     inverts the contrast and the white text sits on near-white. */
  .gloss:hover,
  .poem-word-body:hover { background: #4a4020; }
  .gloss-popover { background: #262628; border-color: #555; }
  .gloss-help { border-top-color: #444; }
  .gloss-source-ja { color: #7fb3f5; }
  .gloss-source-zh { color: #f0a0a5; }
}
