/* Grammar section (GRAMMAR-CONTRACT.md, workstream D). Tokens only — the
   reader's palette, faces and spacing; the rubric stays the one accent (the
   current section, the target word, a lit rule). No cards: hairline lists,
   like the weeks menu and the panel stack. Everything is scoped to #grammar
   and to html[data-section] so the reader's own styles are never touched. */

/* ---------------------------------------------------- header switch */
/* The Read / Grammar control joins the header beside Passage / Sentence: on
   phones it shares the week row (the week title truncates; the two segmented
   controls tighten to --s-2), so the reader keeps its two rows; from 768px it
   sits in the first row beside the view switch. */
.bar__main { grid-template-columns: minmax(0, 1fr) auto auto auto; grid-template-areas: "week section view view" "tools tools tools settings"; }
.seg--section { grid-area: section; }
#settings-btn { grid-area: settings; }
@media (max-width: 767.98px) {
  /* On a phone the control shows the *other* section only ("Grammar" while reading, "Read" in grammar):
     one button, so the week title keeps its room and the reader stays at two header rows. */
  .seg--section .seg__btn[aria-pressed="true"] { display: none; }
  .seg--section .seg__btn { border-left: 0; }
  .seg--section .seg__btn, .seg--view .seg__btn { padding-inline: var(--s-2); }
  .bar__main { column-gap: var(--s-1); }
  .week__btn { padding-right: 1.4rem; background-position: calc(100% - 0.75rem) 50%, calc(100% - 0.45rem) 50%; }
}
@media (min-width: 768px) {
  .bar__main { grid-template-columns: minmax(0, 1fr) auto auto auto; grid-template-areas: "week section view settings" "tools tools tools tools"; }
}
html[data-section="grammar"] .seg--view, html[data-section="grammar"] .bar__tools, html[data-section="grammar"] #hint-translation { display: none; }
html[data-section="grammar"] .bar__main { grid-template-areas: "week week section settings" "tools tools tools tools"; }
@media (max-width: 767.98px) {
  html[data-section="grammar"] .bar__main { grid-template-columns: minmax(0, 1fr) auto auto; grid-template-areas: "week section settings"; column-gap: var(--s-2); }
}
html[data-section="grammar"] #transport { z-index: var(--z-sticky); }

/* ------------------------------------------------------------ shell */
#grammar { font-family: var(--face-ui); font-size: var(--ui-md); line-height: 1.5; color: var(--ink); }
.g { position: relative; max-width: 44rem; margin-inline: auto; padding: var(--s-4) var(--s-4) calc(var(--s-7) + env(safe-area-inset-bottom)); }
@media (min-width: 768px) { .g { padding: var(--s-5) var(--s-6) var(--s-7); } }
.g-loading { max-width: 44rem; margin: var(--s-6) auto; padding: 0 var(--s-4); color: var(--ink-3); font-family: var(--face-ui); }
.g-nav { display: flex; gap: var(--s-1); margin: 0 0 var(--s-5) calc(-1 * var(--s-2)); border-bottom: 1px solid var(--line); }
.g-nav__btn {
  min-height: var(--tap); padding: 0 var(--s-3); background: none; border: 0; border-bottom: 2px solid transparent; margin-bottom: -1px;
  color: var(--ink-2); font-size: var(--ui-md); border-radius: 0;
  transition: color var(--t-fast) var(--ease-out), border-color var(--t-fast) var(--ease-out);
}
.g-nav__btn:hover { color: var(--ink); }
.g-nav__btn[aria-current="page"] { color: var(--ink); border-bottom-color: var(--rubric); }
.g-nav__btn:focus-visible { outline-offset: -3px; border-radius: var(--radius-sm); }
.g-body { display: grid; gap: var(--s-5); }
.g-body > * { min-width: 0; }

.g-head { display: grid; gap: var(--s-2); }
.g-title { margin: 0; font-family: var(--face-serif); font-weight: 400; font-size: 1.75rem; line-height: 1.2; text-wrap: balance; outline: none; }
.g-title:focus-visible { outline: 2px solid var(--focus); outline-offset: 4px; border-radius: var(--radius-sm); }
.g-h2 { margin: 0 0 var(--s-2); font-family: var(--face-serif); font-weight: 400; font-size: 1.2rem; line-height: 1.3; }
.g-lede { margin: 0; color: var(--ink-2); max-width: 60ch; text-wrap: pretty; }
.g-note { margin: 0; padding: var(--s-2) var(--s-3); background: var(--bg-chrome); border-radius: var(--radius); color: var(--ink-2); font-size: var(--ui-sm); }
.g-kicker { margin: 0; font-size: var(--ui-sm); color: var(--ink-3); }
.g-summary { margin: var(--s-1) 0 0; max-width: 60ch; text-wrap: pretty; }
.g-quiet { margin: 0; color: var(--ink-3); font-size: var(--ui-sm); line-height: 1.5; text-wrap: pretty; }
.g-label { display: block; font-size: var(--ui-xs); font-weight: 600; letter-spacing: 0.02em; color: var(--ink-3); }
/* `text-align: inherit`: a button is inline-block, so a long label wrapping onto a
   second line would centre itself against the prose around it (a skill title on a phone). */
.g-link { background: none; border: 0; padding: 0; color: var(--rubric-ink); font: inherit; text-align: inherit; text-decoration: underline; text-underline-offset: 0.15em; cursor: pointer; border-radius: 2px; }
.g-link:hover { color: var(--ink); }
.g-acts { display: flex; flex-wrap: wrap; gap: var(--s-2); }
.g-back { align-self: start; justify-self: start; margin-left: calc(-1 * var(--s-3)); }
.g-danger { color: var(--danger); }
.g-danger:hover:not(:disabled) { color: var(--danger); }

/* ----------------------------- a label that explains itself (§23) */
/* The learner could not read two lines of a chapter row ("I don't kknow what
   these mean"), so the lines themselves open the explainer. The affordance is
   a shape, never a colour: the printed text, then a small "?" in a ring. The
   button inherits its font and colour from the line it replaces, so nothing
   about the page's look moves — and nothing in here changes size on hover or
   focus, because a control must not move under the hand reaching for it
   (§17.3). The 44px target is in the "touch last" section at the end of this
   file, which is the only place a coarse rule wins (§21.1). */
.g-why {
  display: inline-flex; align-items: center; gap: 0.3em;
  margin: 0; padding: 0; border: 0; border-radius: var(--radius-sm);
  background: none; color: inherit; font: inherit; text-align: inherit; cursor: help;
}
.g-why__q {
  flex: none; display: inline-flex; align-items: center; justify-content: center;
  width: 1.15em; height: 1.15em; border: 1px solid var(--line-strong); border-radius: 999px;
  color: var(--ink-3); font-family: var(--face-ui); font-size: 0.75em; line-height: 1;
}
.g-why:hover .g-why__q, .g-why[aria-expanded="true"] .g-why__q { border-color: var(--ink-3); color: var(--ink); }
.g-why:focus-visible { outline: 2px solid var(--focus); outline-offset: 1px; }
/* Beside a chip, which is a button already and cannot hold another: the "?" alone, and its own target. */
.g-why--bare { align-self: center; }
.g-chips__li { display: flex; align-items: center; gap: var(--s-1); }

/* ------------------------------------------------------------- map */
.g-today, .g-review { display: grid; gap: var(--s-1); }
.g-today__line { margin: 0; line-height: 1.6; }
.g-today__btn { margin-left: var(--s-1); vertical-align: middle; min-height: 36px; }
.g-chips { list-style: none; margin: var(--s-1) 0 0; padding: 0; display: flex; flex-wrap: wrap; gap: var(--s-2); }
.g-chip { min-height: 36px; padding: 0 var(--s-3); border: 1px solid var(--line-strong); border-radius: 999px; background: transparent; color: var(--ink); font-size: var(--ui-sm); text-align: left; }
.g-chip:hover { border-color: var(--ink-3); background: var(--bg-raised); }
.g-chip__state { color: var(--ink-3); }
.g-chip[data-state="lapsed"] .g-chip__state { color: var(--danger); }
.g-filter { display: flex; flex-wrap: wrap; gap: var(--s-1); margin-left: calc(-1 * var(--s-2)); }
.g-filter__btn { min-height: 36px; padding: 0 var(--s-3); border: 0; border-radius: 999px; background: transparent; color: var(--ink-2); font-size: var(--ui-sm); }
.g-filter__btn:hover { background: var(--bg-chrome); color: var(--ink); }
.g-filter__btn[aria-pressed="true"] { background: var(--ink); color: var(--bg); }
.g-bulk { display: flex; flex-wrap: wrap; gap: var(--s-1); margin: calc(-1 * var(--s-3)) 0 0 calc(-1 * var(--s-3)); }
.g-bulk .btn { min-height: 36px; font-size: var(--ui-sm); }

.g-chap { display: grid; gap: 0; }
.g-chap__h { display: flex; align-items: baseline; gap: var(--s-3); margin: 0; padding-bottom: var(--s-2); border-bottom: 1px solid var(--line-strong); font-family: var(--face-serif); font-weight: 400; font-size: 1.1rem; }
.g-chap__count { font-family: var(--face-ui); font-size: var(--ui-sm); color: var(--ink-3); font-variant-numeric: tabular-nums; }
.g-skills { list-style: none; margin: 0; padding: 0; }
.g-skill { display: grid; gap: var(--s-2); padding: var(--s-3) 0; border-bottom: 1px solid var(--line); }
/* The title and the actions share a line only while both fit. As a two-track grid (`minmax(0, 1fr) auto`) the
   action track took its max-content width and the title track computed to 0, so a skill with seven actions —
   every skill that has a generated bank — printed its name one word per line over the "Start as new" button
   (QA M-1). A wrapping flex row instead: the name keeps at least 20rem, and when the actions want more than
   what is left they drop to their own line under it and the name takes the full column. */
@media (min-width: 640px) {
  .g-skill { display: flex; flex-wrap: wrap; align-items: flex-start; column-gap: var(--s-4); }
  .g-skill__main { flex: 1 1 20rem; }
  .g-skill__acts { flex: 0 1 auto; max-width: 100%; }
}
.g-skill__main { display: grid; gap: 2px; min-width: 0; }
.g-skill__title { background: none; border: 0; padding: 0; text-align: left; font-family: var(--face-serif); font-size: 1.1rem; line-height: 1.3; color: var(--ink); cursor: pointer; border-radius: 2px; text-wrap: balance; }
.g-skill__title:hover { color: var(--rubric-ink); }
.g-skill__plain { margin: 0; color: var(--ink-2); font-size: var(--ui-sm); }
.g-skill__state { display: flex; align-items: center; gap: var(--s-2); margin: 0; color: var(--ink-3); font-size: var(--ui-sm); }
.g-dot { width: 8px; height: 8px; border-radius: 50%; border: 1.5px solid var(--ink-3); background: transparent; flex: none; }
.g-dot[data-state="learning"] { border-color: var(--ink); background: linear-gradient(90deg, var(--ink) 50%, transparent 50%); }
.g-dot[data-state="practising"] { border-color: var(--ink); background: var(--ink); }
.g-dot[data-state="mastered"] { border-color: var(--success); background: var(--success); }
.g-dot[data-state="lapsed"] { border-color: var(--danger); background: transparent; }
.g-dot[data-state="none"] { border-style: dashed; }
.g-skill[data-state="lapsed"] .g-skill__state { color: var(--danger); }
.g-skill[data-state="mastered"] .g-skill__state { color: var(--success); }
.g-skill__acts { display: flex; flex-wrap: wrap; gap: var(--s-1); align-items: center; }
.g-skill__acts .btn { min-height: 36px; font-size: var(--ui-sm); padding-inline: var(--s-3); }
.g-skill__reset { color: var(--ink-3); }

/* ------------------------------------ how much of a skill is done */
/* Three signals for one fact, so no reader depends on the one they have not
   got: the count ("3 of 6 parts" — the unit named, §22.1) in words, the ticks filled or hollow (a shape,
   which survives a greyscale print), and the colour. The colour ramp is the
   section's own — ink-3 while a skill is begun, full ink past halfway, green
   only when every part is done. Because "mastered" is one of the parts, green
   here means what green already means on the state dot beside it.
   Nothing in this block changes size on hover or focus: the meter must not
   move under the finger reaching for it (GRAMMAR-CONTRACT.md §17.3). */
.g-skill__state { flex-wrap: wrap; row-gap: 2px; }
.g-parts {
  display: inline-flex; align-items: center; gap: var(--s-1);
  margin: 0; padding: 2px var(--s-1); border: 0; border-radius: var(--radius-sm);
  background: none; color: var(--ink-3); font: inherit; cursor: pointer;
}
.g-parts:hover { background: var(--bg-chrome); color: var(--ink-2); }
.g-parts:focus-visible { outline: 2px solid var(--focus); outline-offset: 1px; }
.g-parts__ticks { display: inline-flex; align-items: flex-end; gap: 2px; height: 10px; }
/* Not-done parts are a short stub, done parts a full stroke: the height is the
   shape half of the signal, and it reads with the colour switched off. */
.g-parts__tick { width: 3px; height: 4px; border-radius: 1px; background: var(--line-strong); }
.g-parts__tick[data-done="1"] { height: 10px; background: var(--ink-3); }
.g-parts[data-level="most"] .g-parts__tick[data-done="1"] { background: var(--ink); }
.g-parts[data-level="all"] .g-parts__tick[data-done="1"] { background: var(--success); }
.g-parts__count { font-variant-numeric: tabular-nums; white-space: nowrap; }
.g-parts[data-level="most"] { color: var(--ink-2); }
.g-parts[data-level="all"] { color: var(--success); }

/* ------------------------- the panel every label opens (§23) */
/* One panel, shared by the parts meter, the chapter line and the state word:
   fixed, over the page, and appended to the body — so it can never reflow the
   row, the list or the page (§17.1), and so it works the same from the map,
   from the chapter spine, from a chapter page's grammar panel and from the
   weeks menu's Today card, three of which are mounted outside the section's
   own root. */
.g-tip {
  position: fixed; z-index: var(--z-tooltip); margin: 0; padding: var(--s-3);
  background: var(--bg-raised); border: 1px solid var(--line); border-radius: var(--radius);
  box-shadow: var(--shadow-pop); color: var(--ink-2);
  font-family: var(--face-ui); font-size: var(--ui-sm); line-height: 1.45;
  animation: pop var(--t-fast) var(--ease-out);
  /* It must fit the screen it is on. At 320x401 with no cap it ran 230px past the bottom of a phone in
     landscape — where it can *never* fit — and was clipped over a third of a 375x667 portrait screen, with
     no way to reach the rest. The placement code keeps it inside the viewport; these two lines are what
     let it, by making the panel able to be short and to scroll rather than simply be cut off. */
  max-height: min(60vh, 420px); overflow-y: auto; overscroll-behavior: contain;
}
/* Opened by the pointer alone: it takes no pointer, which is what keeps it from
   flickering as the mouse reaches it. Opened by a finger or the keyboard it does
   take one, so a tap on the panel is not a tap on the row underneath — where
   "Reset" is. Same rule as the word popover above. */
.g-tip[data-hover="1"] { pointer-events: none; }
.g-tip p { margin: 0; }
.g-tip__h { font-family: var(--face-serif); font-size: 1rem; color: var(--ink); text-wrap: balance; }
.g-tip .g-tip__sum { margin-top: 2px; color: var(--ink-3); font-size: var(--ui-xs); }
.g-tip__list { list-style: none; margin: var(--s-2) 0 0; padding: 0; display: grid; gap: var(--s-1); }
.g-tip__item { display: grid; grid-template-columns: 1em 1fr; gap: var(--s-2); align-items: baseline; }
.g-tip__mark { color: var(--ink-3); text-align: center; }
.g-tip__item[data-done="1"] .g-tip__mark { color: var(--success); }
.g-tip__what { display: block; min-width: 0; }
.g-tip__label { color: var(--ink-3); }
.g-tip__item[data-done="1"] .g-tip__label { color: var(--ink); }
.g-tip__item[data-here="1"] .g-tip__mark, .g-tip__item[data-here="1"] .g-tip__label { color: var(--ink); }
.g-tip__detail { display: block; color: var(--ink-3); font-size: var(--ui-xs); text-wrap: pretty; }
.g-tip__not { font-weight: 600; }
.g-tip .g-tip__foot { margin-top: var(--s-2); padding-top: var(--s-2); border-top: 1px solid var(--line); color: var(--ink-3); font-size: var(--ui-xs); text-wrap: pretty; }

/* ---------------------------------------------------------- lesson */
/* Lesson prose follows Settings → Type "Notes" (--note-scale), like the reader's notes; the Latin follows the reading size. */
.g-lesson { display: grid; gap: var(--s-4); max-width: 62ch; font-size: calc(var(--ui-lg) * var(--note-scale, 1)); line-height: 1.6; }
.g-lesson__p { margin: 0; text-wrap: pretty; }
.g-lesson i, .g-la, .g-ex__item { font-family: var(--face-reading); }
.g-lesson i { font-style: italic; }
.g-lesson__tag { margin: 0 0 var(--s-1); font-family: var(--face-ui); font-size: var(--ui-xs); font-weight: 600; letter-spacing: 0.02em; color: var(--ink-3); }
.g-lesson__english { margin: 0; padding: var(--s-3) var(--s-4); background: var(--bg-chrome); border-radius: var(--radius); font-size: var(--note-md); }
.g-lesson__english .g-lesson__tag { display: block; }
.g-lesson__rule { margin: 0; padding: var(--s-3) 0; border-top: 1px solid var(--line-strong); border-bottom: 1px solid var(--line-strong); font-family: var(--face-serif); font-size: 1.15em; text-wrap: balance; }
.g-lesson__rule.is-lit, .g-lesson__conf.is-lit { background: var(--underline); color: oklch(0.2 0.02 95); padding-inline: var(--s-3); border-radius: var(--radius-sm); border-color: transparent; }
.g-lesson__rule.is-lit b, .g-lesson__conf.is-lit b, .g-lesson__conf.is-lit .g-lesson__tag { color: inherit; }
@media (prefers-color-scheme: dark) { :root:not([data-theme="light"]) .g-lesson__rule.is-lit, :root:not([data-theme="light"]) .g-lesson__conf.is-lit { color: oklch(0.15 0.02 95); } }
:root[data-theme="dark"] .g-lesson__rule.is-lit, :root[data-theme="dark"] .g-lesson__conf.is-lit { color: oklch(0.15 0.02 95); }
.g-lesson__conf { display: grid; gap: var(--s-1); padding: var(--s-3) var(--s-4); background: var(--glow-bg); color: var(--glow-ink); border-radius: var(--radius); font-size: var(--note-md); }
.g-lesson__conf p { margin: 0; }
.g-lesson__conf .g-lesson__tag { color: var(--glow-ink); }
.g-lesson__pt .paradigm { max-width: 32rem; }
.g-lesson__pt .pt__scroll { --pt-bg: var(--bg); }
.g-more { border-top: 1px solid var(--line); padding-top: var(--s-2); display: grid; gap: var(--s-3); font-size: var(--note-md); }
.g-more__s { min-height: var(--tap); display: flex; align-items: center; cursor: pointer; list-style: none; color: var(--ink-2); font-weight: 600; font-family: var(--face-ui); font-size: var(--ui-sm); }
.g-more__s::-webkit-details-marker { display: none; }
.g-more__s::before { content: ""; width: 0.4em; height: 0.4em; margin-right: var(--s-2); border-right: 1.5px solid currentColor; border-bottom: 1.5px solid currentColor; transform: rotate(-45deg); transition: transform var(--t-fast) var(--ease-out); }
.g-more[open] .g-more__s::before { transform: rotate(45deg); }
.g-sources { margin: 0; font-size: var(--ui-xs); color: var(--ink-3); font-family: var(--face-ui); }
.g-ex { list-style: none; margin: 0; padding: 0; display: grid; gap: var(--s-3); }
.g-ex__item { display: grid; gap: var(--s-1); }
.g-ex__en { margin: 0; font-family: var(--face-ui); font-size: var(--note-md); color: var(--ink-2); }
.g-ex__ref { margin: 0; font-family: var(--face-ui); font-size: var(--ui-xs); color: var(--ink-3); }
.g-ex__parsed { margin: var(--s-1) 0 0; padding-left: 1.2em; font-family: var(--face-ui); font-size: var(--note-md); color: var(--ink-2); display: grid; gap: var(--s-1); }
.g-ex__parsed b { font-family: var(--face-reading); font-weight: 600; color: var(--ink); }
.g-ex--worked { gap: var(--s-5); }
/* The stepper above a lesson. A chapter set's two marks keep the CSS counter and their text; a lesson's
   steps carry their glyph in the DOM (`.g-steps__g`) instead, because a finished one is a tick and not a
   numeral and a finished one is also a button back to itself (§22.2). `.visually-hidden` never appears
   here — a mark that is not a control names itself with aria-label on its own <li>. */
.g-steps { display: flex; flex-wrap: wrap; gap: var(--s-2) var(--s-4); margin: 0; padding: 0; list-style: none; font-size: var(--ui-sm); color: var(--ink-3); counter-reset: step; }
.g-steps__s { display: flex; align-items: center; gap: var(--s-1); }
.g-steps__s::before { counter-increment: step; content: counter(step); width: 1.4em; height: 1.4em; display: inline-grid; place-items: center; border: 1px solid currentColor; border-radius: 50%; font-size: var(--ui-xs); }
.g-steps__s[aria-current="step"] { color: var(--ink); font-weight: 600; }
.g-steps__s[aria-current="step"]::before { background: var(--rubric); border-color: var(--rubric); color: var(--bg); }
.g-missed { display: grid; gap: var(--s-2); }
.g-missed__list { margin: 0; padding-left: 1.2em; display: grid; gap: var(--s-1); }
.g-missed__kind { font-size: var(--ui-xs); color: var(--ink-3); text-transform: none; }

/* Latin as tappable words */
.g-la { margin: 0; font-size: clamp(1.15rem, var(--reading-size, 1.35rem), 1.9rem); line-height: var(--reading-leading, 1.55); text-wrap: pretty; hanging-punctuation: first; }
.g-w__punct { pointer-events: none; }
/* The blank of a blank item is the target (G1-02): a marked gap, not a lit neighbour. */
.g-blank { display: inline-block; min-width: 3.2em; height: 0.15em; margin: 0 0.1em; vertical-align: baseline; border-bottom: 2px solid currentColor; background: var(--underline); box-shadow: 0 0 0 0.12em var(--underline); border-radius: 2px; }
.g-w { background: none; border: 0; padding: 0 0.04em; margin: 0 -0.04em; font: inherit; color: inherit; cursor: pointer; border-radius: 3px; line-height: inherit; }
.g-w:hover { background: var(--rubric-soft); }
.g-w:focus-visible { outline: 2px solid var(--focus); outline-offset: 1px; }
.g-w--target { background: var(--underline); color: oklch(0.2 0.02 95); box-shadow: 0 0 0 0.12em var(--underline); box-decoration-break: clone; -webkit-box-decoration-break: clone; }
@media (prefers-color-scheme: dark) { :root:not([data-theme="light"]) .g-w--target { color: oklch(0.15 0.02 95); } }
:root[data-theme="dark"] .g-w--target { color: oklch(0.15 0.02 95); }
.g-la--tap .g-w { padding: 0.05em 0.15em; margin: 0 0.02em; border: 1px dashed var(--line-strong); }
.g-la--tap .g-w:hover { border-color: var(--ink); border-style: solid; }
.g-la--tap .g-w.is-picked { border-style: solid; border-color: var(--ink); background: var(--bg-chrome); }
.g-la--tap .g-w:disabled { opacity: 1; cursor: default; }
/* The finger's dictionary (GRAMMAR-CONTRACT.md §17.4): a long press on a word asks the dictionary,
   so the browser's own long press must not answer over the top of it. The iOS callout is refused
   outright — the property is touch-only and a mouse never meets it — and selection is refused only
   while a finger is actually down on a word (`<html data-hold>`, set by hovergloss.js from a real
   pointer event), so selecting Latin with a mouse on a touchscreen laptop is exactly what it was. */
.g-w, .g-wx { -webkit-touch-callout: none; }
:root[data-hold="1"] .g-w, :root[data-hold="1"] .g-wx { user-select: none; -webkit-user-select: none; }

/* Word popover (tap-to-gloss) */
.g-pop {
  position: absolute; z-index: var(--z-popover); padding: var(--s-3) var(--tap) var(--s-3) var(--s-4);
  background: var(--bg-raised); border: 1px solid var(--line); border-radius: var(--radius-lg); box-shadow: var(--shadow-pop);
  animation: pop var(--t-base) var(--ease-out);
  /* A backstop, not the plan: the sense list is bounded (one reading, ≤ 4 senses) so the popup is
     meant to fit outright, and showGloss() flips it above the word rather than off the screen. This
     only stops a pathological entry from running past the viewport. A clicked popup can scroll to
     the rest; a hover one is `pointer-events: none` and cannot, which is why the bound comes first. */
  max-height: min(60vh, 420px); overflow-y: auto; overscroll-behavior: contain;
}
.g-pop p { margin: 0; }
/* Opened by the pointer crossing a word, not by a click: it behaves as a tooltip, so it never takes the
   pointer (which would make it flicker as the mouse entered it) and it has no close button to press —
   it goes when the pointer leaves the word. The padding loses the room the × had. */
.g-pop[data-hover="1"] { pointer-events: none; padding-inline-end: var(--s-4); }
.g-pop[data-hover="1"] .g-pop__close { display: none; }
.g-pop__form { font-family: var(--face-reading); font-size: 1.1rem; color: var(--ink-2); }
.g-pop__meaning { font-family: var(--face-reading); font-size: 1.25rem; line-height: 1.3; margin-top: 2px; }
.g-pop__miss { color: var(--ink-2); font-style: italic; font-size: 1rem; }
.g-pop__parse { margin-top: var(--s-1); font-size: var(--ui-sm); color: var(--ink-2); }
.g-pop__lemma { margin-top: var(--s-1); font-size: var(--ui-sm); color: var(--ink-2); }
/* What the word means, under the dictionary form — the same block the reader's panel prints as
   `.entry__senses` (panels.css), so the two surfaces read alike. Only the first reading gets one. */
.g-pop__senses { margin: var(--s-1) 0 0; padding-left: 1.25em; font-size: var(--ui-sm); line-height: 1.45; color: var(--ink-2); }
.g-pop__senses li::marker { color: var(--ink-3); font-size: var(--ui-xs); }
.g-pop__more { margin-top: var(--s-1); font-size: var(--ui-xs); color: var(--ink-3); }
.g-pop__entry--alt { margin-top: var(--s-2); padding-top: var(--s-2); border-top: 1px solid var(--line); }
.g-pop__entry--alt .g-pop__meaning { font-size: 1.05rem; }
.g-pop__close { position: absolute; top: var(--s-1); right: var(--s-1); width: 36px; height: 36px; border: 0; background: none; border-radius: var(--radius); color: var(--ink-2); font-size: 1.3rem; line-height: 1; }
.g-pop__close:hover { background: var(--bg-chrome); color: var(--ink); }
@media (prefers-reduced-motion: reduce) { .g-pop { animation: none; } }

/* ------------------------------------------------------------ setup */
.g-setup { display: grid; gap: var(--s-4); }
.g-setup__row { display: grid; gap: var(--s-2); }
.g-seg { display: inline-flex; border: 1px solid var(--line-strong); border-radius: var(--radius); overflow: hidden; background: var(--bg-raised); width: max-content; }
.g-seg__btn { min-height: 40px; min-width: 3.25rem; padding: 0 var(--s-3); background: transparent; border: 0; color: var(--ink-2); transition: background-color var(--t-fast) var(--ease-out), color var(--t-fast) var(--ease-out); }
.g-seg__btn + .g-seg__btn { border-left: 1px solid var(--line-strong); }
.g-seg__btn[aria-pressed="true"] { background: var(--ink); color: var(--bg); }
.g-seg__btn:focus-visible { outline-offset: -3px; border-radius: 0; }
.g-presets { display: grid; gap: 0; }
.g-preset { display: flex; align-items: flex-start; gap: var(--s-3); min-height: var(--tap); padding: var(--s-2) 0; cursor: pointer; border-top: 1px solid var(--line); }
.g-preset:first-child { border-top: 0; }
.g-preset input { width: 1.1rem; height: 1.1rem; margin: 0.2rem 0 0; accent-color: var(--ink); flex: none; }
.g-preset__text { display: grid; gap: 1px; }
.g-preset__text b { font-weight: 600; }
.g-preset__text small { color: var(--ink-3); font-size: var(--ui-xs); line-height: 1.4; }
.g-preset.is-disabled { color: var(--ink-3); cursor: default; }
.g-preset__pick { display: grid; gap: var(--s-1); padding-top: var(--s-2); }
/* "Missed items" is not a fifth mix over skills but a session over named items ("Redo what was wrong"),
   so it sits a little apart from the four — a rule, not a badge or a colour. */
.g-preset--missed { margin-top: var(--s-2); border-top-width: 2px; }
.g-select { min-height: 40px; max-width: 100%; padding: 0 var(--s-3); border: 1px solid var(--line-strong); border-radius: var(--radius); background: var(--bg-raised); color: var(--ink); font-size: var(--ui-md); }
/* "What goes in the mix": the four population toggles in the map's own filter pattern, with the All / None
   pair beside them. One flow line, so on a phone the pair wraps under the chips instead of squeezing them. */
.g-mix { display: flex; flex-wrap: wrap; align-items: center; gap: var(--s-2) var(--s-3); }
.g-mix .g-filter { flex: 0 1 auto; margin-left: calc(-1 * var(--s-2)); }
/* "All or none" is one control with two ends, not two buttons: neither end stays pressed, so it takes the
   segmented shell without the pressed state the size and view switches use. */
.g-seg--allnone { flex: none; }
.g-seg--allnone .g-seg__btn { min-height: 36px; min-width: 3rem; font-size: var(--ui-sm); }
.g-mix__empty { color: var(--danger); }

/* ------------------------------------------------------------ runner */
.g-run { display: grid; gap: var(--s-4); }
.g-page { display: grid; gap: var(--s-4); }
/* Back · "4 of 10" · forward. The position lives here, not on the item: a page is kept exactly as it was left
   when the learner steps back, so a number printed inside it would go stale as the session grows. */
.g-runnav { display: flex; align-items: center; justify-content: space-between; gap: var(--s-3); margin-bottom: calc(-1 * var(--s-2)); padding-bottom: var(--s-2); border-bottom: 1px solid var(--line); }
.g-runnav__b {
  min-width: var(--tap); min-height: var(--tap); display: flex; align-items: center; justify-content: center;
  border: 1px solid var(--line-strong); border-radius: var(--radius); background: var(--bg-raised); color: var(--ink); font-size: 1.05rem;
  transition: border-color var(--t-fast) var(--ease-out), color var(--t-fast) var(--ease-out);
}
.g-runnav__b:hover:not(:disabled) { border-color: var(--ink); }
.g-runnav__b:disabled { color: var(--ink-3); border-color: var(--line); }
.g-runnav__pos { font-size: var(--ui-sm); color: var(--ink-2); font-variant-numeric: tabular-nums; }
/* Stepping back is a reading, not a doing: the whole page reads back quieter, and nothing in it can be answered. */
.g-runnav[data-replay="true"] + .g-run .g-item { opacity: 0.86; }
.g-item { display: grid; gap: var(--s-3); }
.g-item__meta { display: flex; flex-wrap: wrap; align-items: baseline; gap: 0 0.35em; margin: 0; font-size: var(--ui-sm); color: var(--ink-3); }   /* the gap does the spacing: a flex item's own leading space is collapsed away */
.g-item__title { color: var(--ink-2); }
.g-item__note { margin-top: calc(-1 * var(--s-2)); }
.g-item__skill { margin: 0; font-family: var(--face-serif); font-size: 1.05rem; color: var(--ink-2); }
.g-q { margin: 0; font-size: var(--ui-lg); font-weight: 600; text-wrap: balance; outline: none; }
.g-q:focus-visible { outline: 2px solid var(--focus); outline-offset: 4px; border-radius: var(--radius-sm); }
.g-gloss { margin: calc(-1 * var(--s-2)) 0 0; color: var(--ink-2); font-size: var(--ui-md); }
.g-gloss [lang="la"] { font-family: var(--face-reading); font-size: 1.05em; }
.g-all-switch { margin-top: calc(-1 * var(--s-1)); min-height: 36px; }
.g-all-switch .switch__text { font-size: var(--ui-sm); color: var(--ink-2); }
.g-all { list-style: none; margin: calc(-1 * var(--s-2)) 0 0; padding: var(--s-2) var(--s-3); background: var(--bg-chrome); border-radius: var(--radius); display: grid; gap: 2px; font-size: var(--ui-sm); color: var(--ink-2); column-count: 1; }
@media (min-width: 640px) { .g-all { grid-template-columns: 1fr 1fr; column-gap: var(--s-4); } }
.g-all__la { font-family: var(--face-reading); font-size: 1.05em; color: var(--ink); }
.g-choices { display: grid; gap: var(--s-2); }
@media (min-width: 640px) { .g-choices { grid-template-columns: 1fr 1fr; } }
.g-choice {
  display: grid; grid-template-columns: auto minmax(0, 1fr); column-gap: var(--s-3); align-items: baseline; text-align: left;
  min-height: var(--tap); padding: var(--s-2) var(--s-3); background: var(--bg-raised); border: 1px solid var(--line-strong); border-radius: var(--radius); color: var(--ink);
  transition: border-color var(--t-fast) var(--ease-out), background-color var(--t-fast) var(--ease-out);
}
.g-choice:hover:not(:disabled) { border-color: var(--ink); }
.g-choice.is-picked { border-color: var(--ink); background: var(--bg-chrome); }
.g-choice:disabled { opacity: 1; color: var(--ink-2); }
.g-choice:disabled:not(.is-picked) { border-color: var(--line); }
.g-choice__n { grid-row: 1 / span 2; font-size: var(--ui-xs); color: var(--ink-3); font-variant-numeric: tabular-nums; }
.g-choice__label { font-size: var(--ui-md); }
.g-choice__label[lang="la"] { font-family: var(--face-reading); font-size: 1.1rem; }
.g-choice__plain { grid-column: 2; font-size: var(--ui-xs); color: var(--ink-3); }
.g-keys { margin: calc(-1 * var(--s-1)) 0 0; font-size: var(--ui-xs); color: var(--ink-3); }
/* The finger's half of the help line, said only once a finger has actually been used here
   (GRAMMAR-CONTRACT.md §17.4). `data-touch` is set by hovergloss.js off a real pointer event, never
   by a media query about the device (§17.2): on a mouse the gesture does not exist, and a line
   teaching a gesture the reader has no way to make is worse than no line. */
.g-keys__hold { display: none; }
:root[data-touch="1"] .g-keys__hold { display: inline; }
/* Hidden from sight on a touch device, but kept in the accessibility tree: a tablet with a Bluetooth keyboard would
   otherwise lose the only statement of the order / match key model (m12). It is the keyboard *half* that is clipped
   now, not the paragraph: the hold sentence beside it is the one part of this line a finger can act on, and hiding
   the whole line would have hidden it on exactly the machine it is for. */
.g-type { display: flex; flex-wrap: wrap; gap: var(--s-2); }
.g-input { min-height: var(--tap); flex: 1 1 14rem; padding: 0 var(--s-3); border: 1px solid var(--line-strong); border-radius: var(--radius); background: var(--bg-raised); color: var(--ink); font-size: var(--ui-lg); }
.g-input[lang="la"] { font-family: var(--face-reading); font-size: 1.15rem; }
.g-input::placeholder { color: var(--ink-3); }
.g-input:focus-visible { outline: 2px solid var(--focus); outline-offset: 0; border-color: var(--focus); }
.g-input:disabled { color: var(--ink-2); background: var(--bg-chrome); }
.g-chart { display: grid; gap: var(--s-3); }
.g-chart .pt__scroll { --pt-bg: var(--bg); padding-inline: 0; }
.g-chart__t { max-width: 32rem; }
.g-chart__in { padding-block: 2px; }
.g-input--cell { min-height: 36px; width: 100%; min-width: 6.5rem; flex: none; padding-inline: var(--s-2); font-size: 1.05rem; }
.g-chart__one { display: grid; gap: var(--s-1); max-width: 22rem; }
.g-chart__acts { display: flex; gap: var(--s-2); }
.g-hint { border-top: 1px solid var(--line); padding-top: var(--s-1); }
.g-hint__s { min-height: var(--tap); display: flex; align-items: center; gap: var(--s-2); cursor: pointer; list-style: none; color: var(--ink-2); font-size: var(--ui-sm); font-weight: 600; }
.g-hint__s::-webkit-details-marker { display: none; }
.g-hint__s::before { content: ""; width: 0.4em; height: 0.4em; border-right: 1.5px solid currentColor; border-bottom: 1.5px solid currentColor; transform: rotate(-45deg); transition: transform var(--t-fast) var(--ease-out); }
.g-hint[open] .g-hint__s::before { transform: rotate(45deg); }
.g-hint__rule { margin: 0 0 var(--s-2); color: var(--ink-2); font-size: var(--ui-md); text-wrap: pretty; max-width: 68ch; }
.g-hint__rule--deep { color: var(--ink); }
.g-hint__body { display: grid; }
.g-hint__more { font-size: var(--ui-sm); justify-self: start; min-height: 36px; }
.g-hint .paradigm { max-width: 32rem; }
.g-hint .pt__scroll { --pt-bg: var(--bg); }

/* Hints, one per answer box. The control sits beside its own box; the hint itself opens in the panel under the
   input, so it never covers the box or the sentence it belongs to (GRAMMAR-CONTRACT.md "Hints, per answer box"). */
.g-hintb {
  width: 1.6rem; height: 1.6rem; min-width: 1.6rem; display: inline-flex; align-items: center; justify-content: center; vertical-align: middle;
  margin-inline-start: 0.25em; padding: 0; border: 1px solid var(--line-strong); border-radius: 50%;
  background: var(--bg-raised); color: var(--ink-2); font-size: var(--ui-xs); font-weight: 700; line-height: 1;
  transition: border-color var(--t-fast) var(--ease-out), color var(--t-fast) var(--ease-out);
}
.g-hintb:hover:not(:disabled) { border-color: var(--ink); color: var(--ink); }
.g-hintb[aria-expanded="true"] { border-color: var(--ink); color: var(--ink); background: var(--bg-chrome); }
/* A cell the learner types into answers **in place**: the press shows that cell's form inside the box and the
   next press takes it away, so the hint never grows the page under someone who is reading a cell. The shown
   form is dressed so it cannot be mistaken for the learner's own writing — dashed edge, the hint's ground,
   the quieter ink — and it is never what gets graded (`valueOf`, ui.js). */
.g-hintb--peek[aria-pressed="true"] { border-color: var(--ink); color: var(--ink); background: var(--bg-chrome); }
.g-input.is-peek { border-style: dashed; border-color: var(--ink-3); background: var(--bg-chrome); color: var(--ink-2); }
.g-input.is-peek:focus-visible { border-color: var(--focus); }
/* A word chip's hint is a labelled button in a row under the input: a mark on the chip itself would fight the tap
   and the drag that place it (order), or the pairing tap (match). */
.g-hintb--wide { width: auto; min-width: 0; height: auto; min-height: 32px; gap: 0.4em; padding: 0 var(--s-2); border-radius: var(--radius); }
.g-hintb__w { font-family: var(--face-reading); font-weight: 400; font-size: var(--ui-sm); }
.g-hintrow { display: flex; flex-wrap: wrap; align-items: center; gap: var(--s-2); padding-top: var(--s-2); border-top: 1px solid var(--line); }
.g-hints { display: grid; gap: var(--s-2); }
.g-hints:not(:has(.g-hints__row:not([hidden]))) { display: none; }
.g-hints__row { display: grid; gap: var(--s-1); padding: var(--s-2) var(--s-3); background: var(--bg-chrome); border-radius: var(--radius); }
.g-hints__row[hidden] { display: none; }   /* an author `display` beats the UA sheet's [hidden] rule */
.g-hints__for { margin: 0; font-size: var(--ui-xs); color: var(--ink-3); text-transform: none; }
.g-hints__body { display: grid; }
.g-hints__body .g-hint__rule:last-child { margin-bottom: 0; }
/* A chart cell keeps its input and its hint side by side inside the cell. The input gives up its own
   width rule here: `flex: none; width: 100%` beside a button is an overflowing row, not a cell. */
.g-cellwrap { display: flex; align-items: center; gap: 0.15em; max-width: 100%; }
.g-cellwrap .g-input--cell { flex: 1 1 auto; width: auto; min-width: 0; }

/* Cell by cell (GRAMMAR-CONTRACT.md §3): a box goes green or red as the learner leaves it, and every
   box takes its colour when the item is graded. The hue is never the only signal — the box also carries
   a ✓ or a ✗, which holds up in greyscale and on a black-and-white print, and `aria-invalid` and the
   live region carry it to a screen reader. Red is a mark, not a barrier: nothing about the box changes
   except its colour, so it can be corrected, hinted, or left and graded with the rest. */
/* The whole cell takes the colour, not just its edge (learner, 2026-09-12: "I want hte red and the green
   to highlight the whole cell rather than just outline") — a filled box is legible at a glance down a
   twelve-cell table, where twelve coloured hairlines are not. A tint of the accent rather than the accent
   itself, so the learner's own writing stays the darkest thing in the cell and stays readable; the edge
   keeps the full-strength colour so the cell still has a definite boundary. Both accents are redefined
   for dark mode in tokens.css, and `oklch(from …)` follows them there without a second rule. */
.g-input.is-right, .g-pensum__slot.is-right { border-color: var(--success); background: oklch(from var(--success) l c h / 0.14); }
.g-input.is-wrong, .g-pensum__slot.is-wrong { border-color: var(--rubric); background: var(--rubric-soft); }
/* Grading disables the boxes, and that is exactly when the learner is reading the colours: `:disabled`
   must not wash the fill back out (it did — the old rule here reset it to the raised ground). */
.g-input.is-right:disabled { background: oklch(from var(--success) l c h / 0.14); color: var(--ink); }
.g-input.is-wrong:disabled { background: var(--rubric-soft); color: var(--ink); }
.g-cellmark { display: inline-flex; align-items: center; justify-content: center; min-width: 1em; font-size: var(--ui-sm); font-weight: 700; line-height: 1; }
.g-cellmark:empty { display: none; }
/* In a chart cell the mark keeps its place from the start. It used to appear only when the cell was judged,
   and the cell's own "?" — which sits after it — jumped 15px sideways the moment the learner left the box:
   pressing it straight from the cell you were typing in *missed*, because the blur judged the cell, the row
   reflowed between mousedown and mouseup, and the press landed beside the button. That press is now the way
   a cell shows its own form, so it may not dodge the pointer. The space reserved is the space the cell takes
   as soon as it is judged, so this also stops the table growing cell by cell as the learner fills it in. */
.g-cellwrap > .g-cellmark:empty, .g-pensum__blank > .g-cellmark:empty { display: inline-flex; }
.g-cellmark.is-ok { color: var(--success); }
.g-cellmark.is-bad { color: var(--rubric); }
/* A box whose hint was opened keeps a quiet dotted underline, the same mark the stats page uses for a
   hinted answer (`.g-tick.is-hinted`): the cell was right, and it was helped. */
.g-cellwrap[data-hinted="true"] .g-input--cell, .g-pensum__blank[data-hinted="true"] .g-input--end { border-bottom-style: dashed; }
.g-match__b.is-right { border-color: var(--success); }
.g-match__b.is-wrong { border-color: var(--rubric); }
/* Coarse pointers: the "?" reaches the 44 px target without growing the ring itself, and without taking the
   target off its own control. The ring is 1.6rem ≈ 25.6 px and a blank declension table puts twelve of them on
   screen, each a few pixels from a 230 px answer box, so this is the tightest pair of targets in the app. The
   old `inset: -50%` sized the target from the ring's *padding* box: 49 px, but reaching 6.5 px back over the
   right edge of the answer box, so a thumb aimed at the end of the box opened the hint instead. The target is
   written out instead — 44 px tall, and 25.6 + 4 + 15 ≈ 44.6 px wide, grown mostly rightwards into the cell
   gutter (2 × var(--s-2) of table-cell padding, so ~1 px short of the next cell's box) and only 4 px leftwards,
   which the cell's own gap then clears. The wide variant is a labelled button in a row of its own: it grows. */

/* Feedback: one line, then "Why" */
.g-fb { display: grid; gap: var(--s-2); padding: var(--s-3) var(--s-4); border-radius: var(--radius); background: var(--bg-chrome); animation: rise-in var(--t-base) var(--ease-out); }
.g-fb[data-ok="true"] { background: oklch(from var(--success) l c h / 0.1); }
.g-fb[data-ok="false"] { background: var(--rubric-soft); }
.g-fb__line { margin: 0; font-size: var(--ui-md); line-height: 1.5; text-wrap: pretty; }
/* Green right, red wrong — but the colour is never the only signal. The mark is a ✓ or a ✗ in a ring, which holds
   up in greyscale and on a black-and-white print where the two hues meet at the same lightness; the line says the
   word ("Right." / the answer); and a visually-hidden "Correct." / "Not right." opens the line for a screen reader.
   Both themes define --success and --rubric (app/css/tokens.css), so the pair follows the theme. */
.g-fb__mark { display: inline-flex; align-items: center; justify-content: center; width: 1.5em; height: 1.5em; border: 1.5px solid currentColor; border-radius: 50%; font-weight: 700; font-size: 0.85em; line-height: 1; vertical-align: -0.2em; }
.g-fb[data-ok="true"] .g-fb__mark { color: var(--success); }
.g-fb[data-ok="false"] .g-fb__mark { color: var(--rubric); }
.g-fb__again { margin: 0; font-size: var(--ui-sm); color: var(--ink-2); text-wrap: pretty; }
.g-fb__more { display: grid; gap: var(--s-2); }
.g-fb__more-s { min-height: 36px; display: flex; align-items: center; gap: var(--s-2); cursor: pointer; list-style: none; color: var(--ink-2); font-size: var(--ui-sm); font-weight: 600; }
.g-fb__more-s::-webkit-details-marker { display: none; }
.g-fb__more-s::before { content: ""; width: 0.4em; height: 0.4em; border-right: 1.5px solid currentColor; border-bottom: 1.5px solid currentColor; transform: rotate(-45deg); transition: transform var(--t-fast) var(--ease-out); }
.g-fb__more[open] .g-fb__more-s::before { transform: rotate(45deg); }
.g-fb__term { margin: 0; font-size: var(--ui-md); }
.g-fb__rule { display: grid; gap: var(--s-2); }
.g-fb__rule:empty { display: none; }
.g-fb__rule .g-lesson__rule { font-size: 1.05rem; }
.g-fb__pt .paradigm { background: var(--bg-raised); max-width: 32rem; }
.g-fb__pt .pt__scroll { --pt-bg: var(--bg-raised); }
.g-fb__ctx .g-la { font-size: 1.15rem; }
/* Where a vocabulary word's sentence lands once it has been found. `display: contents` for two reasons:
   the block it holds becomes a grid child of the feedback like every other, and an empty holder — no
   sentence uses the word, or none has arrived yet — takes no track and leaves no gap. Nothing is shown
   rather than an empty box. */
.g-fb__use { display: contents; }
.g-fb__ahead { margin: 0; font-size: var(--ui-sm); }
.g-fb__acts { display: flex; flex-wrap: wrap; gap: var(--s-2); padding-top: var(--s-1); }
.g-sub__note { margin: 0; padding: var(--s-2) var(--s-3); background: var(--glow-bg); color: var(--glow-ink); border-radius: var(--radius); font-size: var(--ui-sm); }
@keyframes rise-in { from { opacity: 0; transform: translateY(4px); } to { opacity: 1; transform: none; } }
@media (prefers-reduced-motion: reduce) { .g-fb { animation: none; } }

/* ------------------------------------------------------------ stats */
.g-stat { display: grid; gap: var(--s-2); }
.g-dl { display: grid; grid-template-columns: max-content minmax(0, 1fr); gap: var(--s-1) var(--s-4); margin: 0; font-size: var(--ui-md); }
.g-dl dt { color: var(--ink-2); }
.g-dl dd { margin: 0; font-variant-numeric: tabular-nums; }
/* The Stats history table. Kept to the element: `.g-table` is also on the catalogue's <li> rows, where a 24rem
   cap cut every row's hairline off short of the section rule above it (QA N-7). */
table.g-table { max-width: 24rem; }
.g-perskill { list-style: none; margin: 0; padding: 0; }
.g-perskill__row { display: grid; grid-template-columns: minmax(0, 1fr) auto; gap: var(--s-1) var(--s-3); align-items: center; padding: var(--s-2) 0; border-bottom: 1px solid var(--line); font-size: var(--ui-sm); }
.g-perskill__name { display: flex; align-items: center; gap: var(--s-2); color: var(--ink); }
.g-perskill__dots { display: flex; gap: 2px; grid-column: 1; }
.g-perskill__acc { grid-column: 2; grid-row: 1 / span 2; color: var(--ink-3); font-variant-numeric: tabular-nums; white-space: nowrap; }
.g-tick { width: 1.2em; height: 1.2em; display: inline-grid; place-items: center; border-radius: 3px; font-size: var(--ui-xs); color: var(--rubric); background: var(--rubric-soft); }
.g-tick.is-ok { color: var(--success); background: oklch(from var(--success) l c h / 0.12); }
.g-tick.is-hinted { outline: 1px dashed var(--ink-3); outline-offset: -1px; }
.g-conf { margin: 0; padding-left: 1.2em; display: grid; gap: var(--s-1); font-size: var(--ui-md); }

/* ------------------------------------------------ wave 2: chapter sets */
/* A chapter's sets (Questions · Vocabulary · Pensa) sit under its skills as a quieter sub-list: same rows, a small heading. */
.g-sets { margin-top: var(--s-2); }
.g-sets__h { margin: 0; padding: var(--s-2) 0 var(--s-1); font-family: var(--face-ui); font-size: var(--ui-xs); font-weight: 600; letter-spacing: 0.02em; color: var(--ink-3); }
.g-skill__title--set { cursor: default; font-size: 1rem; }
.g-skill__title--set:hover { color: var(--ink); }
.g-skills--sets .g-skill { padding-block: var(--s-2); }

/* ------------------------------------------------ wave 2: the Today plan */
/* One row per suggested session: label · detail · Start. Hairlines, no card; the map's Today section holds it bare, the weeks menu wraps it in a bordered block. */
.g-plan { display: grid; gap: var(--s-1); }
.g-plan--card { margin: 0 var(--s-3) var(--s-2); padding: var(--s-3) var(--s-4); border: 1px solid var(--line); border-radius: var(--radius); background: var(--bg); font-family: var(--face-ui); font-size: var(--ui-md); }
.g-plan__h { margin: 0 0 var(--s-1); font-family: var(--face-serif); font-weight: 400; font-size: 1.1rem; }
.g-plan__total { color: var(--ink-3); font-family: var(--face-ui); font-size: var(--ui-sm); }
.g-plan__list { list-style: none; margin: 0; padding: 0; }
.g-plan__row { display: grid; grid-template-columns: 6.5rem minmax(0, 1fr) auto; gap: var(--s-1) var(--s-3); align-items: center; padding: var(--s-2) 0; border-top: 1px solid var(--line); }
.g-plan__row:first-child { border-top: 0; }
.g-plan__label { font-weight: 600; }
.g-plan__detail { color: var(--ink-2); font-size: var(--ui-sm); line-height: 1.4; }
.g-plan__min { color: var(--ink-3); white-space: nowrap; }
.g-plan__go { min-height: 36px; font-size: var(--ui-sm); }
.g-plan__foot { margin: 0; font-size: var(--ui-sm); }
@media (max-width: 479.98px) {
  .g-plan__row { grid-template-columns: minmax(0, 1fr) auto; }
  .g-plan__detail { grid-column: 1 / -1; }
}

/* ------------------------------------------------ wave 2: inputs */
/* Order: the bank of words and the row they go into, the same dashed-box vocabulary as the tap items. */
.g-order, .g-match__form { display: grid; gap: var(--s-3); }
.g-order__row { display: flex; flex-wrap: wrap; align-items: center; gap: var(--s-2); min-height: calc(var(--tap) + var(--s-2)); padding: var(--s-2); border: 1px dashed var(--line-strong); border-radius: var(--radius); background: var(--bg-raised); transition: border-color var(--t-fast) var(--ease-out); }
.g-order__row.is-over { border-color: var(--ink); border-style: solid; }
.g-order__empty { color: var(--ink-3); font-size: var(--ui-sm); padding-inline: var(--s-1); }
.g-order__bank { display: flex; flex-wrap: wrap; gap: var(--s-2); }
.g-order__w {
  min-height: 36px; padding: 0.15em 0.5em; font-family: var(--face-reading); font-size: clamp(1.05rem, var(--reading-size, 1.25rem), 1.5rem); line-height: 1.3;
  background: var(--bg-raised); border: 1px solid var(--line-strong); border-radius: var(--radius-sm); color: var(--ink); cursor: pointer;
  transition: border-color var(--t-fast) var(--ease-out), background-color var(--t-fast) var(--ease-out), opacity var(--t-fast) var(--ease-out);
}
.g-order__w:hover:not(:disabled) { border-color: var(--ink); }
.g-order__w.is-placed { background: var(--bg-chrome); border-color: var(--ink-3); }
/* After Check every button in the item is disabled, and reader.css fades a disabled button to 45 %. The one input whose
   *answer is the arrangement* must stay readable while the feedback asks the learner to compare it (m7 / QA M7) — the
   bank's own "already used" fade is a different thing and keeps its 0.35. */
.g-order__w:disabled { opacity: 1; color: var(--ink-2); }
.g-order__w.is-used { opacity: 0.35; cursor: default; }
.g-order__w[draggable="true"] { cursor: grab; }
.g-order__acts { display: flex; flex-wrap: wrap; gap: var(--s-2); }
/* 44 px on both axes under a coarse pointer: `et`, `in` and `ā` are two-letter chips, and a Pensum A ending box is
   ~26 px tall without this (m15 / CR m1). */

/* Match: two columns, Latin left, meanings right; a pending pick is outlined, a pair filled. */
.g-match { display: grid; grid-template-columns: 1fr 1fr; gap: var(--s-2) var(--s-3); }
.g-match__col { display: grid; gap: var(--s-2); align-content: start; }
.g-match__b {
  min-height: var(--tap); padding: var(--s-2) var(--s-3); text-align: left; background: var(--bg-raised); border: 1px solid var(--line-strong); border-radius: var(--radius); color: var(--ink); font-size: var(--ui-md);
  transition: border-color var(--t-fast) var(--ease-out), background-color var(--t-fast) var(--ease-out);
}
.g-match__b--la { font-family: var(--face-reading); font-size: 1.15rem; }
.g-match__b:hover:not(:disabled) { border-color: var(--ink); }
.g-match__b.is-pending { border-color: var(--ink); box-shadow: inset 0 0 0 1px var(--ink); }
.g-match__b.is-paired { background: var(--bg-chrome); border-color: var(--ink-3); }
/* A paired button carries its pair's number on both sides of the table: four grey boxes alone never say which two go together. */
.g-match__b[data-n] { display: flex; align-items: baseline; gap: var(--s-2); }
.g-match__b[data-n]::before {
  content: attr(data-n);
  flex: none; min-width: 1.5em; padding: 0.05em 0.35em; border-radius: var(--radius-sm);
  background: var(--rubric-soft); color: var(--rubric-ink);
  font-family: var(--face-ui); font-size: var(--ui-xs); font-variant-numeric: tabular-nums; text-align: center;
}
.g-match__b:disabled { opacity: 1; color: var(--ink-2); }

/* Pensum A / B: inline endings and word slots inside the sentence. */
.g-pensum { line-height: 2; }
.g-pensum__blank { white-space: nowrap; }
.g-pensum__stem { border-bottom: 1px solid transparent; }
.g-input--end { display: inline-block; width: auto; min-width: 2.6em; max-width: 6em; min-height: 1.6em; margin: 0 0.1em 0 0; padding: 0 0.3em; font-size: 0.95em; vertical-align: baseline; border-radius: var(--radius-sm); }
.g-pensum__slot {
  display: inline-block; min-width: 4.5em; min-height: 1.6em; margin: 0 0.15em; padding: 0 0.4em; vertical-align: baseline; font: inherit; line-height: 1.5;
  background: var(--bg-raised); border: 1px dashed var(--line-strong); border-bottom: 2px solid var(--ink-3); border-radius: var(--radius-sm); color: var(--ink); cursor: pointer;
}
.g-pensum__slot.is-filled { border-style: solid; border-color: var(--ink-3); background: var(--bg-chrome); }
.g-pensum__slot:disabled { cursor: default; opacity: 1; }

/* Translate: the textarea, then the book's English and the three grades. */
.g-self { display: grid; gap: var(--s-3); }
.g-textarea { min-height: 3.4rem; padding: var(--s-2) var(--s-3); font-size: var(--ui-lg); line-height: 1.5; resize: vertical; font-family: var(--face-ui); }
.g-self__model { margin: 0; padding: var(--s-3) var(--s-4); background: var(--bg-chrome); border-radius: var(--radius); font-size: var(--ui-lg); line-height: 1.5; animation: rise-in var(--t-base) var(--ease-out); }
.g-self__model .g-lesson__tag { display: block; }
.g-self__grade { display: flex; flex-wrap: wrap; gap: var(--s-2); }
@media (prefers-reduced-motion: reduce) { .g-self__model { animation: none; } }

/* The right answer is marked in the list itself after an answer, not only in the prose below it (m13). */
.g-choice.is-answer { border-color: var(--success); box-shadow: inset 2px 0 0 var(--success); }
.g-choice.is-answer .g-choice__label { font-weight: 600; }

/* A chapter set's Learn pass walks the deck a batch at a time; the bar says how far through it is. */
.g-prog__wrap { margin: 0 0 var(--s-3); }
.g-prog { width: 100%; max-width: 26rem; height: 6px; appearance: none; border: 0; border-radius: 999px; background: var(--bg-chrome); }
.g-prog::-webkit-progress-bar { background: var(--bg-chrome); border-radius: 999px; }
.g-prog::-webkit-progress-value { background: var(--ink-3); border-radius: 999px; }
.g-prog::-moz-progress-bar { background: var(--ink-3); border-radius: 999px; }

/* Vocabulary: the word stands large and alone; the dictionary line arrives with the feedback. */
.g-vocab__word { font-size: clamp(1.4rem, calc(var(--reading-size, 1.35rem) * 1.3), 2.2rem); }
.g-fb__dict, .g-fb__pairs { margin: 0; font-size: var(--ui-md); }
.g-fb__pairs { padding-left: 1.2em; display: grid; gap: 2px; }
.g-fb[data-partial="true"] { background: var(--bg-chrome); }
.g-fb[data-partial="true"] .g-fb__mark { color: var(--ink-2); }
.g-q-en { font-size: var(--ui-sm); }
.g-q-en .g-hint__s { min-height: 36px; }
.g-q-en .g-hint__rule { margin: 0 0 var(--s-1); }

/* ------------------------------------------------ wave 2: the weeks-menu Today card */
/* The dialog's own slot for the card. Without a rule of its own it borrowed `.g-plan--card`'s margins, written for the
   skill map, and sat oddly against the week list (m10). */
.weeks__today { margin: 0 0 var(--s-2); }
.weeks__today .g-plan--card { margin: 0; }
.weeks__today .g-plan__row { padding-block: var(--s-2); }
.weeks__today .g-today__fallback { margin: 0; padding: var(--s-3) var(--s-4); border: 1px solid var(--line); border-radius: var(--radius); font-family: var(--face-ui); font-size: var(--ui-sm); color: var(--ink-3); }

/* A question's own words are tappable for their entry (plan §3), so the question is a `.g-la` as well as a `.g-q`:
   it keeps the question's size and weight and takes the reading face for the Latin. */
.g-q.g-q--la { font-family: var(--face-reading); }
.g-q.g-q--la .g-w { font: inherit; }

/* --------------------------------- wave 3: confusion analytics + history */
/* "What you mix up": one pair a block, hairline-separated like every other
   list here. The count is the only figure; nothing is scored or ranked aloud. */
.g-pairs { list-style: none; margin: 0; padding: 0; display: grid; gap: var(--s-3); }
.g-pair { display: grid; gap: var(--s-1); padding: var(--s-3) 0; border-bottom: 1px solid var(--line); }
.g-pair:last-child { border-bottom: 0; padding-bottom: 0; }
.g-pair__names { margin: 0; font-size: var(--ui-lg); line-height: 1.35; text-wrap: pretty; display: flex; flex-wrap: wrap; align-items: baseline; column-gap: var(--s-3); row-gap: 0.1rem; }
.g-pair__who { text-wrap: pretty; }
/* The two names read as the line's subject, not as four links in a row: ink, with
   the rubric kept for the underline that says they can be opened. The same for the
   per-skill list, which is 87 rows long and would otherwise be a wall of red. */
.g-pair__names .g-link,
.g-perskill__name .g-link { color: var(--ink); text-decoration-color: var(--rubric); text-decoration-thickness: 1px; }
.g-pair__names .g-link:hover,
.g-perskill__name .g-link:hover { color: var(--rubric-ink); }
/* Its own line-box: when the two names fill the row the count drops beneath them
   and reads as "5 times", never as a line beginning "· 5 times" (QA-B8). */
.g-pair__count { color: var(--ink-3); font-size: var(--ui-sm); font-variant-numeric: tabular-nums; white-space: nowrap; }
.g-pair__why { margin: 0; max-width: 62ch; color: var(--ink); text-wrap: pretty; }
.g-pair__dir { margin: 0; font-variant-numeric: tabular-nums; }
.g-pair__acts { display: flex; flex-wrap: wrap; align-items: center; gap: var(--s-2) var(--s-3); padding-top: var(--s-1); }

/* Items per day: one bar a day, the whole strip labelled. A bar is a reading,
   not a reward — no colour, no streak, no target line. */
.g-spark__wrap { display: grid; gap: var(--s-2); margin-top: var(--s-2); }
.g-spark { list-style: none; margin: 0; padding: 0; display: flex; align-items: flex-end; gap: 2px; height: 2.5rem; }
.g-spark__d { flex: 1 1 0; min-width: 3px; height: calc(var(--h, 0) * 100%); background: var(--ink-3); border-radius: 1px; }
.g-spark__d[data-empty] { height: 2px; background: var(--line); }

/* The stability curve, replayed from the attempts. Ink on nothing; a ring per miss. */
.g-trail__fig { margin: var(--s-3) 0 0; display: grid; gap: var(--s-2); }
.g-trail { width: 100%; height: 3.5rem; overflow: visible; }
.g-trail__line { fill: none; stroke: var(--ink-2); stroke-width: 1.25; stroke-linejoin: round; vector-effect: non-scaling-stroke; }
.g-trail__miss { stroke: var(--rubric); stroke-width: 1.25; stroke-linecap: round; vector-effect: non-scaling-stroke; }
.g-trail__steps { margin: var(--s-2) 0 0; padding-left: 1.2em; display: grid; gap: var(--s-1); font-size: var(--ui-sm); color: var(--ink-2); }

/* The last twenty items: a plain table that scrolls sideways on a phone rather
   than wrapping a Latin form onto two lines. */
/* The same edge shade the paradigm tables use (panels.css .pt__scroll): a fade
   at whichever end has more, and no shade once the end is reached. */
.g-hist__scroll {
  overflow-x: auto; max-width: 100%; margin-top: var(--s-2); overscroll-behavior-x: contain; -webkit-overflow-scrolling: touch;
  background:
    linear-gradient(to right, var(--bg) 40%, transparent) left / 2rem 100% no-repeat local,
    linear-gradient(to left, var(--bg) 40%, transparent) right / 2rem 100% no-repeat local,
    linear-gradient(to left, var(--scroll-shade), transparent) right / 1.25rem 100% no-repeat scroll,
    linear-gradient(to right, var(--scroll-shade), transparent) left / 1.25rem 100% no-repeat scroll;
}
.g-hist__table { max-width: none; min-width: 30rem; font-size: var(--ui-sm); }
.g-hist__table th[scope="row"] { white-space: nowrap; color: var(--ink-2); font-weight: 400; }
.g-hist__table tr.is-wrong .g-hist__given { color: var(--rubric-ink); }
.g-hist__kind { color: var(--ink-3); }
.g-hist__mode { color: var(--ink-3); font-style: italic; }
.g-hist__given, .g-hist__want { max-width: 16rem; }
.g-fb__hist { margin: var(--s-2) 0 0; font-size: var(--ui-sm); }

@media (min-width: 768px) {
  .g-hist__table { min-width: 0; }
}

/* --------------------------------------- the chapter spine (by chapter) */
/* Two ways of browsing the same grammar: By topic (the category map) and By
   chapter (the book's own spine). The switch sits under the page title, in the
   segmented control the practice setup already uses. */
.g-seg--views { margin-bottom: calc(-1 * var(--s-2)); }

/* A chapter of the spine: the numeral and title in the summary, its counts on
   the right, its skills and sets inside. Folded away by default — the chapter
   being read opens itself — so 34 chapters stay a page you can read down. */
.g-chap--spine { display: block; }
.g-chap__d { border-bottom: 1px solid var(--line); }
.g-chap__sum {
  display: flex; flex-wrap: wrap; align-items: baseline; gap: var(--s-1) var(--s-3);
  min-height: var(--tap); padding: var(--s-2) 0; cursor: pointer; list-style: none;
}
.g-chap__sum::-webkit-details-marker { display: none; }
.g-chap__sum::before {
  content: ""; flex: none; width: 0.4em; height: 0.4em; margin-right: var(--s-1); align-self: center;
  border-right: 1.5px solid var(--ink-3); border-bottom: 1.5px solid var(--ink-3); transform: rotate(-45deg);
  transition: transform var(--t-fast) var(--ease-out);
}
.g-chap__d[open] > .g-chap__sum::before { transform: rotate(45deg); }
.g-chap__sum:hover .g-chap__num { color: var(--rubric-ink); }
.g-chap__sum:focus-visible { outline: 2px solid var(--focus); outline-offset: -3px; border-radius: var(--radius-sm); }
.g-chap__h--sum { display: flex; flex-wrap: wrap; align-items: baseline; gap: var(--s-2); padding: 0; border: 0; font-size: 1.1rem; }
.g-chap__num { white-space: nowrap; }
.g-chap__title { color: var(--ink-2); font-size: 1rem; }
.g-chap__count { margin-left: auto; }
/* Wrapped onto its own line (a phone, a long title), the count reads better under the title than pinned right. */
@media (max-width: 559.98px) { .g-chap__count { margin-left: 0; } }
.g-chap__body { padding-bottom: var(--s-3); }
.g-chap__acts { display: grid; justify-items: start; gap: var(--s-1); padding-top: var(--s-3); }
.g-chap__acts .g-quiet { max-width: 60ch; }
@media (prefers-reduced-motion: reduce) { .g-chap__d[open] > .g-chap__sum::before, .g-chap__sum::before { transition: none; } }

/* The chapter page's own grammar panel (mountChapterGrammar): the same rows,
   in the shell's element, under an h2 — the page owns the chapter's title. */
.g-chapter { display: grid; gap: var(--s-2); font-family: var(--face-ui); font-size: var(--ui-md); line-height: 1.5; color: var(--ink); }
.g-chapter > * { min-width: 0; }
.g-chapter__head { display: grid; gap: 2px; padding-bottom: var(--s-2); }
.g-chapter__h { margin: 0; font-family: var(--face-serif); font-weight: 400; font-size: 1.3rem; line-height: 1.2; }
.g-chapter__sum { margin: 0; color: var(--ink-3); font-size: var(--ui-sm); }
.g-chapter .g-skills { border-top: 1px solid var(--line-strong); }

/* ------------------------------------------ the teaching steps (Learn) */
/* One step is one page of the run: its number, its title, one short paragraph, the thing it shows (a
   written sentence with its gloss, or the one or two cells it reveals), a worked example, and the check.
   Hairlines and the reading face, as everywhere; the stepper carries one small mark a step, and since
   §22.2 that mark also says whether the step is finished and is the way back to it. */
.g-stepwrap { display: contents; }
.g-steps--n { gap: var(--s-2); }
.g-steps--n .g-steps__s { gap: 0; position: relative; }
.g-steps--n .g-steps__s::before { display: none; }   /* the mark is in the DOM here, so it can be a tick and a control */
/* Three states, and the colour is the third signal and never the first (§3, §22.2): the glyph differs in
   SHAPE — a tick, a numeral, the word "Ten" — so the row reads in greyscale and on paper, and the whole
   fact is in the aria-label either way. Nothing here changes size between the states: the pip is a
   control now, and a control the learner is reaching for may not move (§17.3). */
.g-steps__go { display: inline-flex; align-items: center; justify-content: center; margin: 0; padding: 0; background: none; border: 0; border-radius: var(--radius-sm); color: inherit; font: inherit; }
button.g-steps__go { cursor: pointer; }
button.g-steps__go:hover .g-steps__g { border-color: var(--ink); color: var(--ink); }
.g-steps__go:focus-visible { outline: 2px solid var(--focus); outline-offset: 1px; }
.g-steps__g { width: 1.4em; height: 1.4em; display: inline-grid; place-items: center; border: 1px solid currentColor; border-radius: 50%; font-size: var(--ui-xs); }
.g-steps--n .g-steps__s:last-child .g-steps__g { width: auto; height: auto; border: 0; border-radius: 0; font-size: inherit; }   /* the ten is named, not numbered */
.g-steps__s[data-state="done"] { color: var(--success); }
.g-steps__s[data-state="now"] .g-steps__g { background: var(--rubric); border-color: var(--rubric); color: var(--bg); }
.g-step { display: grid; gap: var(--s-3); padding-bottom: var(--s-4); border-bottom: 1px solid var(--line); }
.g-step__title { margin: 0; font-family: var(--face-serif); font-weight: 400; font-size: 1.45rem; line-height: 1.25; text-wrap: balance; outline: none; }
.g-step__title:focus-visible { outline: 2px solid var(--focus); outline-offset: 4px; border-radius: var(--radius-sm); }
.g-step__say { margin: 0; max-width: 62ch; font-size: calc(var(--ui-lg) * var(--note-scale, 1)); line-height: 1.6; text-wrap: pretty; }
.g-step__say i { font-family: var(--face-reading); font-style: italic; }
.g-show { display: grid; gap: var(--s-2); }
/* The paradigm a step reveals sits on the page ground, not on a white card: `.pt__scroll` defaults --pt-bg to
   --bg-raised, which painted a white band beside the table on every teaching step (QA N-1). */
.g-show .pt__scroll { --pt-bg: var(--bg); }
.g-wgloss { list-style: none; margin: 0; padding: 0; display: flex; flex-wrap: wrap; gap: var(--s-1) var(--s-4); font-size: var(--ui-sm); color: var(--ink-2); }
.g-wgloss__la { font-family: var(--face-reading); font-size: 1.05em; color: var(--ink); }
.g-wgloss__en::before { content: "— "; color: var(--ink-3); }
/* The revealed cells: one row a cell, one column a stock word, the ending set apart. Never the whole table. */
.g-reveal__t { max-width: 32rem; }
.g-reveal__end { font-weight: 700; color: var(--rubric-ink); }
.g-reveal__note { margin: 0; color: var(--ink-2); font-size: var(--ui-md); }
.g-reveal__note b { font-family: var(--face-reading); font-weight: 700; color: var(--rubric-ink); }
/* The worked example: the sentence, then its features as a list — the given ones read, the asked ones
   filled in as the learner answers, each taking its green or red mark like an answer box. */
.g-worked { display: grid; gap: var(--s-2); padding: var(--s-3) var(--s-4); background: var(--bg-chrome); border-radius: var(--radius); }
.g-worked .g-la { font-size: 1.25rem; }
.g-worked__dl { display: grid; grid-template-columns: max-content minmax(0, 1fr); gap: var(--s-1) var(--s-3); margin: 0; font-size: var(--ui-md); }
.g-worked__dl:empty { display: none; }
.g-worked__row { display: contents; }
.g-worked__row dt { color: var(--ink-2); }
.g-worked__row dd { margin: 0; }
.g-worked__row.is-right dd b::after, .g-worked__row.is-wrong dd b::after { margin-left: 0.35em; font-size: var(--ui-sm); }
.g-worked__row.is-right dd b::after { content: "✓"; color: var(--success); }
.g-worked__row.is-wrong dd b::after { content: "✗ (your answer was not this)"; color: var(--rubric); font-weight: 400; }
.g-worked__plain { color: var(--ink-3); }
.g-worked__ask { display: grid; gap: var(--s-2); }
.g-worked__ask:empty { display: none; }
.g-worked__q { font-size: var(--ui-md); }
.g-worked__form { display: grid; gap: var(--s-2); }
.g-worked__why { min-height: 2.8rem; }
.g-worked__reason { display: grid; gap: var(--s-1); }
.g-worked__reasontext { margin: 0; max-width: 62ch; text-wrap: pretty; }
.g-worked__reasontext i { font-family: var(--face-reading); }
.g-worked__go { justify-self: start; margin-top: var(--s-1); }
.g-choices--worked .g-choice { background: var(--bg-raised); }
/* A chart check over several words: one box a word, each with its label and its own hint (§8). */
.g-chart__words { display: grid; gap: var(--s-2); max-width: 32rem; }
.g-chart__word { display: grid; grid-template-columns: minmax(7rem, 11rem) minmax(0, 1fr); align-items: center; gap: var(--s-2) var(--s-3); }
.g-chart__wlabel { display: grid; gap: 1px; font-size: var(--ui-sm); color: var(--ink-2); }
.g-chart__wlemma { font-family: var(--face-reading); font-size: 1.15rem; color: var(--ink); }
@media (max-width: 479.98px) { .g-chart__word { grid-template-columns: 1fr; gap: var(--s-1); } }
.g-hint__answer b { font-family: var(--face-reading); font-weight: 700; }
/* Decision 4: the unmet prerequisites, named on the first step and offered — a rule above the step, not a wall. */
.g-prereq { display: grid; gap: var(--s-2); padding: var(--s-3) var(--s-4); background: var(--glow-bg); color: var(--glow-ink); border-radius: var(--radius); font-size: var(--ui-md); }
.g-prereq__line { margin: 0; max-width: 62ch; text-wrap: pretty; }

/* ------------------------------------------ the noticing opener (§10) */
/* Two written sentences side by side, one question, the word tapped: a dashed tap box like every tap item,
   the found words lit like a target, a wrong tap marked in the rubric and left editable. */
.g-notice { display: grid; gap: var(--s-2); padding: var(--s-3) var(--s-4); background: var(--bg-chrome); border-radius: var(--radius); }
.g-notice__pair { display: grid; gap: var(--s-3); }
@media (min-width: 640px) { .g-notice__pair { grid-template-columns: 1fr 1fr; gap: var(--s-4); } }
.g-notice__col { display: grid; gap: var(--s-1); min-width: 0; }
.g-notice__la { font-size: clamp(1.1rem, var(--reading-size, 1.25rem), 1.6rem); }
.g-notice__q { font-size: var(--ui-md); }
.g-notice__line { margin: 0; min-height: 1.4em; font-size: var(--ui-md); color: var(--ink); text-wrap: pretty; }
.g-notice__line:empty { display: none; }
.g-la--tap .g-w.is-wrong { border-color: var(--rubric); border-style: solid; color: var(--rubric-ink); }
.g-la--tap .g-w.is-right { border-color: transparent; }
/* A right tap lights the whole construction, and the word under the finger must
   look exactly like its partner: `is-picked` ("this is the one you chose") is
   the more specific selector, so without this the tapped half went chrome-grey
   beside a gold partner and the pair still read as two different things (N-16). */
.g-la--tap .g-w.is-picked.g-w--target { background: var(--underline); }
.g-notice.is-done .g-la--tap .g-w { border-color: transparent; }
.g-notice.is-done .g-la--tap .g-w:hover { background: var(--rubric-soft); }
.g-step__body { display: grid; gap: var(--s-3); }
.g-step__body[hidden] { display: none; }

/* ------------------------------------------ "Just drill it": the pinned rule (§10) */
.g-pin { margin: 0; padding: var(--s-2) var(--s-3); border-top: 1px solid var(--line-strong); border-bottom: 1px solid var(--line-strong); font-family: var(--face-serif); font-size: 1.05rem; line-height: 1.45; text-wrap: pretty; max-width: 62ch; }
.g-pin .g-lesson__tag { display: inline; margin: 0 var(--s-1) 0 0; }
.g-pin i { font-family: var(--face-reading); font-style: italic; }

/* ------------------------------------------ after a sitting: the re-test and the tie-in */
.g-after { display: grid; gap: var(--s-3); }
.g-after:empty { display: none; }
.g-retest { display: grid; gap: var(--s-2); }
.g-retest__line { margin: 0; text-wrap: pretty; max-width: 62ch; }
.g-tiein { margin: 0; padding: var(--s-2) var(--s-3); background: var(--glow-bg); color: var(--glow-ink); border-radius: var(--radius); font-size: var(--ui-md); text-wrap: pretty; max-width: 62ch; }
.g-tiein .g-link { color: inherit; }

/* ------------------------------------------ scaffolded tables (§12) */
/* The switch sits on the table, above it: the same segmented control as the setup's, one tap, no confirmation. */
.g-scaffold { display: grid; gap: var(--s-1); justify-items: start; }
.g-seg--scaffold .g-seg__btn { min-width: 3rem; min-height: 36px; font-size: var(--ui-sm); }
/* Five levels abreast at 375: 36 px tall without this, the one control on the table page a thumb can miss. */
.g-scaffold__note { max-width: 60ch; }
/* A wide table scrolls inside its own box on a phone; a grid item's `min-width: auto` would otherwise let the
   box grow to the table and push the whole page sideways. */
.g-run, .g-page, .g-item, .g-chart, .g-chart .pt__scroll { min-width: 0; max-width: 100%; }
/* The other way a table pushed the page sideways (QA M-7). `.visually-hidden` (app/css/reader.css) is
   `position: absolute` with no offsets, so the label a given cell carries (" — genitive plural, given") was laid
   out at the static position it would have had — and inside a 637 px table scrolling in its own box that is past
   the right edge of a 375 px page, which widened the document by the overshoot. The utility itself is shared with
   the reader and stays as it is; inside the grammar section two guards make it inert. First, every box that can
   hold such a label becomes the containing block, so the label's overflow belongs to the box that scrolls rather
   than to the page. Second, the label is pinned to that box's own corner, so wherever one appears — a caption, a
   cell label, a verdict word — it can add nothing to any scrollable area. Both are layout-only: the box is still
   1 px, still clipped, still read aloud in document order. */
#grammar .pt__scroll, #grammar .g-cellwrap, #grammar .g-chart__word, #grammar .g-fb__line { position: relative; }
#grammar .visually-hidden { inset-block-start: 0; inset-inline-start: 0; }
/* A given cell: the form printed and greyed, not a box; its own hint beside it like a box's. */
.g-chart__given { padding-block: 2px; background: var(--bg-chrome); }
.g-cellwrap--given { color: var(--ink-2); }
.g-chart__givenform { font-family: var(--face-reading); font-size: 1.05rem; padding-inline: var(--s-2); }
.g-chart__givennote { max-width: 60ch; }

/* ------------------------------------------ the catalogue (§4) */
.g-cat__filters { display: grid; gap: var(--s-2); justify-items: start; }
.g-cat__sec { display: grid; gap: var(--s-2); padding-top: var(--s-3); border-top: 1px solid var(--line); }
.g-cat__sec .g-h2 { margin-bottom: 0; }
.g-table__ex { font-family: var(--face-reading); font-size: 1.05em; color: var(--ink); }
.g-words .g-chip[lang="la"], .g-words__results .g-chip [lang="la"], .g-cells .g-chip { font-family: var(--face-ui); }
.g-words .g-chip[lang="la"] { font-family: var(--face-reading); font-size: 1rem; }
.g-chip[aria-pressed="true"] { background: var(--ink); color: var(--bg); border-color: var(--ink); }
.g-chip[aria-pressed="true"] .g-chip__state { color: inherit; }
.g-words__search { max-width: 22rem; margin-top: var(--s-1); }
.g-words__results { margin: 0; }
.g-words__results[hidden] { display: none; }
.g-axis { display: grid; gap: var(--s-1); }
.g-tables .g-skill__acts .btn { min-height: 36px; }

/* ------------------------------ generated sentences (§11, §11b) */
/* The English and the word-by-word gloss of a generated item, on demand under the sentence; the same gloss on the feedback. */
.g-item__en { margin: 0; }
.g-item__en[open] .g-hint__s::before { transform: rotate(45deg); }
.g-item__en .g-hint__rule { margin: 0 0 var(--s-1); }
.g-item__en .g-wgloss, .g-fb__ctx .g-wgloss { margin-top: var(--s-2); }


/* ============================================================== touch last
   Every `pointer: coarse` rule in this file lives here, at the end, and that
   position is the whole of what makes it work. A media query adds **no**
   specificity: a coarse block placed above `.g-chip { min-height: 36px }`
   matches on a phone and then loses to the later plain rule, so the 44px
   target it declares never applies. Eleven of these were dead that way, and
   nothing caught it, because a narrowed desktop window never matches
   `pointer: coarse` at all — the rules were never even evaluated in testing.
   Keep new touch rules in this section. A coarse rule written next to the
   thing it overrides is a rule that does nothing. */

@media (pointer: coarse) {
  .g-today__btn, .g-chip, .g-filter__btn, .g-bulk .btn, .g-skill__acts .btn, .g-fb__more-s, .g-all-switch, .g-pop__close { min-height: var(--tap); }
  .g-pop__close { width: var(--tap); }
  .g-skill__title { min-height: var(--tap); display: flex; align-items: center; }
  /* Words stay inline (the line box is untouched); the hit area grows above and below the text. */
  .g-w { position: relative; }
  .g-w::after { content: ""; position: absolute; left: 0; right: 0; top: -0.45em; bottom: -0.45em; }
}

@media (pointer: coarse) {
  /* 32px was below the project's own --tap standard, and the row gives it no slack: "Start as new" sits
     8px under it, so a near miss starts a lesson instead of opening a panel. */
  .g-parts { min-height: var(--tap); }
  /* The same for the labels that explain themselves (§23). They sit in running text, so the target grows
     around the words rather than pushing the line about: the min-height does the work, and a bare "?"
     beside a chip gets a square one of its own. */
  .g-why { min-height: var(--tap); }
  .g-why--bare { min-width: var(--tap); justify-content: center; }
}

@media (pointer: coarse) { .g-seg--allnone .g-seg__btn { min-height: var(--tap); } }

@media (hover: none) and (pointer: coarse) {
  .g-keys:not(.g-keys--tap) .g-keys__main { position: absolute; width: 1px; height: 1px; margin: -1px; padding: 0; overflow: hidden; clip-path: inset(50%); white-space: nowrap; border: 0; }
}

@media (pointer: coarse) {
  .g-cellwrap { gap: var(--s-1); }
  .g-hintb { position: relative; margin-inline-start: var(--s-1); }
  .g-hintb:not(.g-hintb--wide)::after {
    content: ""; position: absolute; top: 50%; left: -4px; right: -15px; height: var(--tap); transform: translateY(-50%);
  }
  .g-hintb--wide { min-height: var(--tap); }
}

@media (pointer: coarse) {
  .g-order__w, .g-match__b, .g-pensum__slot, .g-plan__go, .g-self__grade .btn { min-height: var(--tap); }
  .g-order__w { min-width: var(--tap); }
  .g-input--end, .g-pensum__slot { min-height: var(--tap); min-width: 3.5em; }
  .g-pensum { line-height: 2.6; }
}

@media (pointer: coarse) { .g-seg--scaffold .g-seg__btn { min-height: var(--tap); } }

@media (pointer: coarse) {
  /* A pip is a control now (§22.2), so it needs a real target. The visible circle stays exactly the size
     it is on a mouse — this row sits above every lesson and is a status line, not a dashboard — and the
     target is grown around it instead: the full --tap in both directions, on every mark whether or not it
     is pressable, so the row does not re-lay itself out as steps are finished. Seven marks is the most a
     lesson can have (six steps and the ten) and 7 × 44 = 308px, which still sits on one row of a 375px
     screen. The gap goes to zero so no two targets overlap, the way a tab bar's do not. */
  .g-steps--n { gap: 0; }
  .g-steps--n .g-steps__go { min-height: var(--tap); min-width: var(--tap); }
}
