/* bm v4.0 · built 2026-08-26 02:38 · cc/app/css/app.css */
/* ══════════════════════════════════════════════════════════════════════
   בית המדרש · יד הלל — stage 1 shell

   MOBILE-FIRST ON PURPOSE. The base stylesheet is a plain scrolling
   document: no 100vh/svh/dvh, no visualViewport math, and — with exactly two
   named exceptions, the viewport overlays #toast and .sheet, each justified
   at its own rule — no position:fixed and
   no nested overflow:auto. The desktop shell is additive, behind
   @media(min-width:901px). If a media query is ever lost, the app
   degrades to the layout that works, not the one that failed nine times.
   See reference/FAILURE-LOG.md items 6-9.
   ══════════════════════════════════════════════════════════════════════ */

*,*::before,*::after{box-sizing:border-box;margin:0;padding:0}

/* ══ TOKENS ═══════════════════════════════════════════════════════════
   Light is the default and is the site's own learning surface
   (viewer.html). Dark is the site's own night language (css/gate.css) —
   not an invention. Three states: bare :root = light; prefers-color-scheme
   supplies dark unless the learner explicitly chose light; the data-theme
   attribute wins over both, in either direction. */
/* כתב סת״ם for verses — Shlomo Stam, Shlomo Orbach, OFL. Chosen by the owner
   2026-09-16 after seeing it against Stam Ashkenaz and Stam Sefarad, whose 11
   vowel glyphs are all EMPTY outlines: they map the niqqud and draw nothing, and
   because the glyph exists the browser never falls back, so the vowels vanish.
   This face carries ink in all 11, plus mark positioning, geresh and gershayim.
   The weight RANGE 400 500 is deliberate: .l-biblical asks for 500 and this is a
   single-weight font, so without it the browser synthesises a bold and thickens a
   scribal hand that was never drawn bold. The file lives under css/ because the
   build enumerates cc/app/css recursively; cc/app/fonts would be dropped with no
   error at all. */
@font-face {
  font-family: "Shlomo Stam";
  src: url("fonts/ShlomoStam.ttf") format("truetype");
  font-weight: 400 500;
  font-style: normal;
  font-display: swap;
}
:root{
  /* color-scheme is the PROPERTY, and it was never declared — only the
     prefers-color-scheme media QUERY was, which is a different thing and
     reads almost the same. Without it the browser paints every form
     control, scrollbar and caret from its light-mode defaults on our dark
     ground. Measured on the served page with the phone in night mode: the
     composer's placeholder came back rgb(117,117,117) — Chromium's grey —
     at 3.61:1. The TYPED text was fine at 13.89:1, because it inherits a
     token; only what the browser chooses for us was wrong. */
  color-scheme:light;
  /* the mobile tab band's height. #tabs is given it, and it is the toast's
     FALLBACK for the first frame before app.js measures the real sticky
     block into --top-h. A fixed number could not be right: the sticky
     block loses the tabs on desktop, the brand row under 900px, and two
     rows more whenever the state card collapses. */
  --nav-h:56px;
  --paper:#fbf8f1; --paper-deep:#f3eee1; --panel:#f7f2e6; --panel2:#fbf8f1;
  --ink:#1d2430; --ink-soft:#4a4436; --muted:#6b6455;
  --line:#d9d0bc; --thread:#c9bfa6; --thread-hl:#b08d2e;
  --gold:#6d5514; --gold-soft:#f0e5c3; --gold-hi:#8a6d1f;
  --tannaic-bg:#fdf2b8; --tannaic-edge:#e8d873;
  --comment:#1d7a3d; --conclusion:#a31515; --sage-line:#5b708f; --ref:#7a3e9d;
  --shadow:rgba(90,70,20,.13);
  --sp:16px;
  --ui:"Heebo",system-ui,-apple-system,"Segoe UI",sans-serif;
  --book:"Frank Ruhl Libre","Times New Roman",Georgia,serif;
  --verse:"Shlomo Stam","David Libre","Frank Ruhl Libre",serif;
  --display:"Suez One","Frank Ruhl Libre",Georgia,serif;
  --fx:cubic-bezier(.4,0,.2,1);
}
@media (prefers-color-scheme:dark){ :root:not([data-theme="light"]){
  color-scheme:dark;
  --paper:#141a2e; --paper-deep:#111629; --panel:#1b2340; --panel2:#161d36;
  --ink:#f4ead2; --ink-soft:#cfc7b4; --muted:#8e98b8;
  --line:#2f3a5e; --thread:#4a5680; --thread-hl:#f0d489;
  --gold:#e8c56a; --gold-soft:#2a2f4d; --gold-hi:#c9a227;
  --tannaic-bg:#3a3410; --tannaic-edge:#7a6a1e;
  --comment:#5cc98a; --conclusion:#ef6b6b; --sage-line:#93a9cf; --ref:#c79bea;
  --shadow:rgba(0,0,0,.45);
}}
:root[data-theme="dark"]{
  color-scheme:dark;
  --paper:#141a2e; --paper-deep:#111629; --panel:#1b2340; --panel2:#161d36;
  --ink:#f4ead2; --ink-soft:#cfc7b4; --muted:#8e98b8;
  --line:#2f3a5e; --thread:#4a5680; --thread-hl:#f0d489;
  --gold:#e8c56a; --gold-soft:#2a2f4d; --gold-hi:#c9a227;
  --tannaic-bg:#3a3410; --tannaic-edge:#7a6a1e;
  --comment:#5cc98a; --conclusion:#ef6b6b; --sage-line:#93a9cf; --ref:#c79bea;
  --shadow:rgba(0,0,0,.45);
}

/* NOT overflow-x:hidden on body. That makes body a scroll container, so
   position:sticky inside it resolves against body's scrollport — which
   never scrolls — and the header, the nav tabs and the state card all
   scroll away on a long pane. Measured: #top at -1500px on הדף.
   `clip` on the root clips without creating a scroll container. */
html{overflow-x:clip}
html,body{height:auto}
body{
  background:var(--paper);color:var(--ink);font-family:var(--ui);
  font-size:16px;line-height:1.5;-webkit-text-size-adjust:100%;
}
button{font:inherit;color:inherit;background:none;border:0;cursor:pointer;text-align:inherit}
textarea{font:inherit;color:inherit}
@media (prefers-reduced-motion:reduce){*{animation:none!important;transition:none!important}}

/* ── the sticky top block: one sticky element, no top arithmetic ───── */
#top{position:sticky;top:0;z-index:20;background:var(--paper)}

/* failure #2: the diagnostic must never be coverable by a pane */
#bootError{
  background:var(--conclusion);color:#fff;padding:10px var(--sp);
  font-size:14px;line-height:1.45;font-weight:600;
}
#bootError[hidden]{display:none}

header{border-bottom:1px solid var(--line);background:var(--paper-deep)}
.hrow{display:flex;align-items:center;gap:10px;padding:9px var(--sp)}
.brand{font-family:var(--book);font-weight:500;font-size:18px;white-space:nowrap}
.brand em{font-style:normal;color:var(--gold)}
.loc{font-size:12.5px;color:var(--muted);white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
.spacer{flex:1}
.hbtn{
  padding:12px 14px;border:1px solid var(--line);border-radius:99px;
  font-size:13.5px;color:var(--ink-soft);white-space:nowrap;transition:.16s var(--fx);
}
.hbtn:hover{border-color:var(--gold);color:var(--ink)}
.hbtn .n{font-variant-numeric:tabular-nums;opacity:.85;margin-inline-start:4px}

#tabs{display:flex;gap:6px;padding:0 var(--sp) 8px;min-height:var(--nav-h);box-sizing:border-box}
#tabs .tabset{flex:none;padding:13px 12px;border:1px solid var(--line);border-radius:9px;
  font-size:13.5px;color:var(--muted);background:var(--panel2)}
#tabs button{
  flex:1;text-align:center;padding:13px 4px;border:1px solid var(--line);border-radius:9px;
  font-size:13.5px;color:var(--muted);background:var(--panel2);transition:.16s var(--fx);
}
#tabs button.on{background:var(--gold);color:var(--paper);border-color:var(--gold);font-weight:700}

/* The brand row costs 52px on every phone screen and repeats what the
   הדף column already prints. Desktop keeps the masthead. */
@media (max-width:900px){ .hrow{display:none} }
/* The middle column's heading duplicates its own gold tab. */
@media (max-width:900px){ #rav .pane-head{display:none} }

/* ── state card: where we are and what is open ─────────────────────── */
#state{padding:10px var(--sp) 11px;border-bottom:1px solid var(--line);background:var(--panel);
       cursor:pointer;transition:padding .16s var(--fx)}
/* Collapsed still shows position, progress and what is owed — the two
   questions spec 09 says the card exists to answer. A collapsed thing that
   shows nothing is just gone. */
body.card-min #state{padding-top:7px;padding-bottom:7px}
body.card-min #stQ,body.card-min #stHold{display:none}
body.card-min .st-bar{margin-top:6px}
#state::after{
  content:"";position:absolute;inset-inline-start:var(--sp);top:12px;
  width:20px;height:2px;border-radius:2px;background:var(--line);
}
#state{position:relative}
.st-row{display:flex;align-items:baseline;gap:10px}
.st-where{font-size:13px;color:var(--muted);white-space:nowrap}
.st-now{color:var(--gold);font-weight:700;font-variant-numeric:tabular-nums}
.lvl{
  font-size:12px;padding:5px 11px;border:1px solid var(--gold);border-radius:99px;
  color:var(--gold);background:color-mix(in srgb,var(--gold) 9%,transparent);
  white-space:nowrap;font-weight:600;line-height:1.2;
}
.lvl:hover{background:color-mix(in srgb,var(--gold) 18%,transparent)}
.lvl-pick{display:flex;flex-wrap:wrap;gap:8px;margin-top:4px}
.lvl-pick button{
  flex:1 1 auto;min-width:96px;min-height:46px;padding:11px 13px;
  border:1px solid var(--line);border-radius:10px;background:var(--panel2);
  color:var(--ink-soft);font-size:14px;text-align:center;
}
.lvl-pick button.on{background:var(--gold);color:var(--paper);border-color:var(--gold);font-weight:700}
.lvl-note{font-size:12px;color:var(--muted);line-height:1.6;margin-top:10px}
.st-open{font-size:12.5px;color:var(--ref);margin-inline-start:auto;white-space:nowrap}
.st-q{
  font-family:var(--book);font-size:15px;line-height:1.45;color:var(--ink);margin-top:4px;
  /* three lines, not two: the driving question is the card's one persistent
     line and clipping it at two was cutting most questions mid-thought */
  display:-webkit-box;-webkit-line-clamp:3;-webkit-box-orient:vertical;overflow:hidden;
  overflow-wrap:break-word;
}
/* seeded by מה_להחזיק_בראש — what the learner should not have to hold
   in his head, and what the teacher therefore never has to restate */
.st-hold{
  font-size:12px;color:var(--muted);margin-top:5px;line-height:1.5;
  /* מה_להחזיק_בראש is a LIST. Clamping it mid-item cut a thought in half;
     each item now gets its own line and the list simply ends. */
  display:flex;flex-direction:column;gap:2px;
}
.st-hold:empty{display:none}
.st-hold span{display:block;overflow-wrap:break-word}
.st-hold i{display:none}
.st-bar{display:flex;gap:3px;margin-top:8px}
.st-bar i{flex:1;height:3px;border-radius:2px;background:var(--line)}
.st-bar i.d{background:var(--gold)}
.st-bar i.c{background:var(--gold)}

/* the station FRAME — what this stop is in the argument, above what it
   says. spec/14 ended one field doing two jobs; this is the first half,
   and it reads as a label rather than as a sentence so the eye separates
   orientation from teaching without effort. */
.st-frame{display:block;font-size:11px;letter-spacing:.02em;color:var(--gold);
  opacity:.85;margin-bottom:2px}

/* ── the card uses the WIDTH it already has ─────────────────────────────
   MEASURED at 1440x900 on the served site: #top was 268px of a 900px
   viewport — 30% of the screen before one line of Gemara — and #state was
   148 of those 268. Its four parts were stacked in a narrow column at the
   start edge, so a 1440px-wide band carried a 26px row, a 22px question and
   a 58px hold-list one under the other, with roughly two thirds of the width
   empty beside them.

   Stacking is right on a phone, where there is no width to spend. On a
   desktop it spends HEIGHT — the scarce thing — to leave WIDTH unused, and
   every column below is that much shorter. So at the desktop breakpoint the
   same four parts sit side by side and the bar spans them.

   Nothing is hidden and nothing is clamped harder: the question keeps three
   lines, the hold list keeps every item. Only the direction changes. */
@media (min-width:901px){
  #state{
    display:grid; align-items:baseline; column-gap:20px;
    grid-template-columns:auto minmax(0,1.3fr) minmax(0,1fr);
    grid-template-areas:"row q hold" "bar bar bar";
  }
  #state .st-row{grid-area:row}
  #state .st-q{grid-area:q; margin-top:0}
  /* the hold list runs across instead of down; `:empty` still removes it and
     the grid column simply collapses, because the track is minmax(0,1fr) */
  #state .st-hold{grid-area:hold; margin-top:0; flex-direction:row; flex-wrap:wrap; gap:2px 14px}
  #state .st-bar{grid-area:bar}
  /* the collapse toggle keeps working: the two parts it hides are grid items
     like any other, and `display:none` removes them from their tracks */
  body.card-min #state{grid-template-columns:auto 1fr; grid-template-areas:"row row" "bar bar"}
}

/* On a short viewport the sticky block is the scarce resource: at 460px it
   was eating 47% of the screen, which is what pushes a tall bubble's head
   out of view. Trim the optional lines of the state card there. This is a
   media QUERY, not a viewport unit in a layout property: if it misfires the
   cost is a slightly denser header, never a broken shell. */
@media (max-height:700px){
  .st-hold{display:none}
  .st-q{-webkit-line-clamp:1}
  #state{padding-top:8px;padding-bottom:8px}
  .hrow{padding-top:7px;padding-bottom:7px}
}

/* ── panes: one at a time on mobile ────────────────────────────────── */
/* spec 10 rollback: the atlas pane can be switched off without a deploy.
   The tab goes with it, so the learner never taps into an empty pane. */
body.no-atlas #kav,body.no-atlas #tabs button[data-c="kav"]{display:none}

/* ── a sector the learner turned off, and its tab with it ───────────────
   OUTSIDE the desktop query on purpose. Below 901px the three panes are
   TABS, not columns, and «turn off the map» has to mean the same thing to a
   learner on a phone as on a laptop: the map is gone and so is the way to
   it. Hiding the pane and leaving its tab is a control that leads nowhere,
   which is the shape FAILURE-LOG calls a stranded learner. */
body.off-kav #kav,body.off-kav #tabs button[data-c="kav"],
body.off-rav #rav,body.off-rav #tabs button[data-c="rav"],
body.off-daf #daf,body.off-daf #tabs button[data-c="daf"]{display:none}
main{display:block}
.col{display:none}
.col.show{display:block}
.pane-head{padding:11px var(--sp) 9px;border-bottom:1px solid var(--line);background:var(--paper-deep)}
/* every column is labelled, on desktop as well as on the phone */
.col-h{display:flex;align-items:center;gap:8px;flex-wrap:wrap;
  font-family:var(--ui);font-size:12px;font-weight:600;letter-spacing:.5px;
  color:var(--ink-soft);text-transform:none;margin-bottom:2px;
  display:flex;align-items:center;gap:8px;flex-wrap:wrap;
}
.seg-toggle{display:inline-flex;border:1px solid var(--line);border-radius:8px;overflow:hidden;margin-inline-start:auto}
.seg-toggle button{
  font-size:13px;padding:11px 13px;color:var(--muted);background:var(--panel2);
  border-inline-start:1px solid var(--line);
}
.seg-toggle button:first-child{border-inline-start:0}
.seg-toggle button.on{background:var(--gold);color:var(--paper);font-weight:600}

/* ══ ביאור שטיינזלץ, under the line it explains ════════════════════════
   THE OWNER: «בקשתי מבונה האתר להכניס אפשרות לראות את זה מתחת הטקסט שלנו».

   Drawn like the learner's own note — a sibling after the unit — but it must
   not be MISTAKEN for one, and it must not be mistaken for the Gemara. Three
   things separate it: the UI face rather than the book face, a quieter ink,
   and a rule down the start edge in the reference colour instead of the
   notebook's gold.

   The commentary's own <b> is what makes it readable: שטיינזלץ prints the
   Talmud's own words in bold inside his paraphrase, which is also what made
   the alignment possible at all. That bolding is kept and given the BOOK
   face — it is the Gemara — while the explanation around it stays UI. */
.u-biur{
  margin:4px 0 10px;padding:8px 12px;
  border-inline-start:2px solid var(--ref);
  background:color-mix(in srgb,var(--ref) 5%,transparent);
  border-radius:0 8px 8px 0;
}
.u-biur-t{
  font-family:var(--ui);font-size:calc(13px * var(--zoom,1));line-height:1.75;
  color:var(--ink-soft);
}
.u-biur-t b{font-family:var(--book);font-weight:600;color:var(--ink)}
/* <big> opens each of his sections (א משנה · ב גמרא) — a label, not emphasis */
.u-biur-t big{
  display:inline-block;font-size:11px;letter-spacing:.04em;
  color:var(--gold);margin-inline-end:6px;
}
.u-biur-c{display:block;margin-top:6px;font-size:10.5px;color:var(--muted);text-decoration:none}
.u-biur-c:hover{color:var(--ref);text-decoration:underline}
/* the toggle sits BESIDE the source switch and not inside it: it adds to our
   text rather than replacing it, which is what דף וילנא does */
.biur-btn{
  margin-inline-start:8px;padding:4px 11px;border:1px solid var(--line);
  border-radius:99px;background:var(--panel2);color:var(--ink-soft);
  font-size:12px;min-height:30px;white-space:nowrap;
}
.biur-btn:hover{border-color:var(--ref);color:var(--ink)}
.biur-btn.on{
  border-color:var(--ref);color:var(--ref);font-weight:600;
  background:color-mix(in srgb,var(--ref) 12%,transparent);
}
.pane-body{padding:14px var(--sp) 28px}

/* ── הקו — the sugya as a transit line ─────────────────────────────── */
.kav-t{font-family:var(--book);font-size:15px;line-height:1.4;color:var(--ink)}
.kav-q{font-size:13px;color:var(--ink-soft);margin-top:6px;line-height:1.45}
/* the name is a label, and a label is small */
.kav-name{font-size:11px;color:var(--muted);margin-top:7px;opacity:.75;letter-spacing:.02em}
.sug{margin-bottom:14px}
.sug-name{
  display:block;width:100%;padding:12px 12px;border-radius:8px;font-size:13.5px;
  color:var(--ink-soft);background:var(--panel2);border:1px solid var(--line);
  transition:.16s var(--fx);
}
.sug-name.on{color:var(--gold);border-color:var(--gold);font-weight:700;background:color-mix(in srgb,var(--gold) 10%,var(--panel2))}
.sug-name .cnt{color:var(--muted);font-weight:400;font-size:12px;margin-inline-start:6px}

.stops{margin-top:10px}
.stop{display:flex;gap:10px;align-items:stretch;width:100%;text-align:start}
.dotcol{display:flex;flex-direction:column;align-items:center;width:16px;flex:none}
.seg{width:2px;flex:1;min-height:7px;background:color-mix(in srgb,var(--gold) 70%,var(--paper))}
.seg.done{width:3px;background:var(--gold)}
.dot{
  width:11px;height:11px;border-radius:50%;flex:none;margin:1px 0;
  background:var(--paper);border:2px solid var(--line);transition:.16s var(--fx);
}
.stop[data-s="נאמר"] .dot{border-color:var(--gold);background:color-mix(in srgb,var(--gold) 30%,var(--paper))}
.stop[data-s="נבדק"] .dot{border-color:var(--gold);background:var(--gold)}
.stop[data-s="הוחזק"] .dot{border-color:var(--gold);background:var(--gold);
  box-shadow:0 0 0 3px color-mix(in srgb,var(--gold) 30%,transparent)}
.stop.cur .dot{border-color:var(--gold);background:var(--gold);
               box-shadow:0 0 0 4px color-mix(in srgb,var(--gold) 26%,transparent)}
/* a תיקו ending terminates in an OPEN circle, not a cap */
.stop.open .dot{background:var(--paper);border-color:var(--ink-soft);border-style:dashed}
.stop.dead .stop-lab{text-decoration:line-through;opacity:.45}
.stop-b{flex:1;min-width:0;padding:2px 0 12px}
.stop-lab{font-size:13.5px;line-height:1.45;color:var(--ink-soft)}
.stop.cur .stop-lab{color:var(--ink)}
.tag{
  display:inline-block;font-size:11px;padding:1px 7px;border-radius:99px;
  margin-inline-end:6px;vertical-align:1px;white-space:nowrap;
  border:1px solid currentColor;
}
/* ══ הקו station colours — OPTION 1 ═══════════════════════════════════
   Built from the live viewer's own hues so the map reads as native to the
   site rather than imported. Still two families, so the ת4.0 מהלך/שכבה
   split stays a rename. */
/* מהלך — rhetorical move */
.t-kushya{color:var(--conclusion)}
.t-chakira{color:var(--conclusion);opacity:.7}
.t-teiruts{color:var(--comment)}
.t-raaya{color:var(--comment);opacity:.72}
.t-dechiya{color:var(--ref)}
.t-haamada{color:var(--ref);opacity:.7}
/* שכבה — historical stratum */
.t-mishna{color:var(--thread-hl)}
.t-braita{color:var(--thread-hl);opacity:.75}
.t-memra{color:var(--sage-line)}
.t-stama{color:var(--sage-line);opacity:.72}
.t-maskana{color:var(--ink)}
.t-other{color:var(--muted)}

/* ── הרב — chat, chips, composer ───────────────────────────────────── */
/* Keeps the composer near the bottom of a phone screen on first load
   instead of floating mid-page. A CONTENT min-height in px, deliberately
   not 100vh/svh/dvh: it cannot be wrong about the viewport because it
   never asks. Short screens simply scroll. */
/* min-height reserves the space so the composer keeps ONE position instead of
   climbing up the screen as the lesson grows — measured: without it the send
   button starts mid-screen and moves on every turn.

   flex-end is the other half, and without it the reservation is visible as a
   void: on first load one opening bubble left 338px of empty pane above the
   chips. Anchoring the column to the bottom puts the newest message just
   above the chips, which is where a reader looks, and the empty space goes
   above the text where nobody reads.

   460px is a constant and that is a compromise, stated rather than hidden:
   the file's own law forbids 100svh/dvh on mobile (FAILURE-LOG 6-9), so the
   reservation cannot be derived from the viewport. It is a floor, not a
   height — a taller screen simply grows past it. */
/* ── AND NOT justify-content:flex-end. ────────────────────────────────
   That is a CSS trap and it cost the owner the whole conversation: a flex
   column that packs to the END puts its OVERFLOW at the TOP, and the top of a
   scroll container is unreachable, because scrollTop cannot go negative.

   Measured on the desktop shell, twelve messages in the pane:
     #chat  top 429  height 295      scrollHeight 295 == clientHeight 295
     the browser reports NOTHING to scroll, and shows no scrollbar
     the first message's top is -112 — outside the box, behind #ravStage,
     whose bottom edge is 429, exactly the pane's top

   His words: "אני בכלל לא רואה אפשרות לגלול אחורה, הטקסט העליון פשוט מכוסה
   תחת חלון המורה וזהו." He was describing this exactly, and he was right that
   there was no option: the text was not scrolled away, it was outside the
   scrollable area entirely.

   `margin-block-start:auto` on the first child does what flex-end was wanted
   for — a short conversation still sits against the chips — and does not do
   what flex-end also did: when the content overflows, an auto margin resolves
   to 0 and the container scrolls normally, from the top, with a scrollbar. */
#chat{min-height:460px;padding-bottom:6px;display:flex;flex-direction:column}
#chat > :first-child{margin-block-start:auto}
/* ── and the reservation must know what is ABOVE it ────────────────────
   The 460px was measured when nothing sat between the header and the chat.
   The avatar stage arrived later and takes 260px, and the floor went on
   reserving as though it did not exist. MEASURED on the served site, at
   scroll 0, 375x812, one opening bubble:

     #top 0-204 · #ravStage 204-464 · #chat 464-924 · #chips 924-1080 ·
     #compose 1080-1150      viewport ends at 812

   The chips began 112px below the fold and the composer 268px below it, with
   332px of empty pane above the single bubble. On a 375x667 phone the composer
   was 420px below. That is the exact failure this project exists to stop —
   CLAUDE.md's own acceptance question is "can you see the composer? the answer
   chips?" and on the live site the answer was no to both.

   The fix is not a smaller constant, it is putting the floor where it belongs:
   when the stage is showing it has ALREADY paid the reservation, so the chat
   needs no floor of its own. flex-end still parks the newest bubble against the
   chips. When the avatar is off, the original 460px stands untouched — the case
   it was measured for. */
#ravStage:not([hidden]) + #chat{min-height:0}
.msg{margin-bottom:14px;display:flex}
.msg.me{flex-direction:row-reverse}
/* --zoom was applied at five sites, every one of them daf text, and the
   conversation was not among them: the learner could enlarge the gemara
   and not the teacher. app.js already resolves --zoom globally, so the
   existing 0.9/1/1.15/1.35 ladder reaches the bubble the moment it is
   referenced here. */
.bub{max-width:min(92%,640px);padding:11px 14px;border-radius:14px;font-size:calc(15.5px * var(--zoom,1));line-height:1.62}
/* measured: --line on --panel is 1.45:1 in light and 1.55:1 in dark, so the
   turns carrying most of the words read as unbounded running text. --muted
   is a token already in this file: 5.53:1 light, 6.03:1 dark. */
.msg.rav .bub{background:var(--panel);border:1px solid var(--muted);border-start-start-radius:5px}
.msg.me .bub{background:color-mix(in srgb,var(--gold) 13%,var(--paper));
             border:1px solid color-mix(in srgb,var(--gold) 32%,var(--line));
             border-start-end-radius:5px;color:var(--ink)}
.who{font-size:11.5px;color:var(--muted);margin-bottom:4px;font-weight:600}
.bub p{margin-bottom:9px}
.bub p:last-child{margin-bottom:0}
/* v5.1: a reply that is still arriving. The text grows sentence by sentence
   as each one passes the check; the caret says "more is coming" without a
   spinner and without moving anything else on the page. */
/* a single glyph; the softening it bought was not load-bearing and cost it
   2.68:1 in light theme, under the 3:1 a status indicator needs */
.msg.rav.live .bub p::after{content:"▍";color:var(--gold);opacity:1;margin-inline-start:2px}
/* the sentence being spoken, lit while it is heard — the sentence, not the
   word (owner's ruling 2026-09-02). A soft ground, no motion, nothing moves. */
.bub .s{border-radius:4px;transition:background-color .25s ease}
/* the sentence lit WHILE IT IS SPOKEN, at 1.34:1 against its own bubble.
   Only the ring is strengthened, not the ground: full --gold on the ring is
   6.35:1 light / 9.28:1 dark, while the text keeps reading against the same
   soft wash it always did. */
/* `var(--gold)arent)` — debris from a truncated edit, and the whole
   declaration was therefore invalid and dropped. The ring is the part that
   carried the highlight (6.35:1 light, 9.28:1 dark, as the comment above
   says); without it the sentence being spoken was a 1.34:1 wash, which is
   not a highlight at all. It shipped, and no test could see it: a CSS
   parser discards the bad declaration silently and the element still has
   a class. */
.bub .s.on{background:color-mix(in srgb,var(--gold) 20%,transparent);box-shadow:0 0 0 2px var(--gold)}
/* the governing daf text inside the lesson — it must read as the daf,
   not as a caption about it */
.bub .src{font-family:var(--book);color:var(--ink);font-size:calc(18px * var(--zoom,1));
          line-height:1.85;background:color-mix(in srgb,var(--gold) 8%,transparent);
          border-inline-start:3px solid var(--gold);border-radius:6px;padding:7px 10px;margin:2px 0 9px}

.divider{
  text-align:center;font-size:12px;color:var(--muted);margin:6px 0 16px;
  padding-top:12px;border-top:1px solid var(--line);
}
/* the resume line is composed by code from the יומן, never by a model */
.msg.resume .bub{border-color:var(--gold);background:color-mix(in srgb,var(--gold) 8%,var(--panel))}
.msg.resume .who{color:var(--gold)}
/* the governing text inside the lesson: the daf's own rendering, set into
   the bubble rather than paraphrased beside it */
.src-daf{
  background:color-mix(in srgb,var(--gold) 7%,transparent);
  border-inline-start:3px solid var(--gold);
  border-radius:8px;padding:8px 12px;margin:2px 0 10px;
}
.src-daf .u{margin:0 0 2px;padding:0;font-size:calc(18px * var(--zoom,1));line-height:1.62}
.src-daf .u:last-child{margin-bottom:0}
.src-daf .u.sel{background:color-mix(in srgb,var(--sage-line) 16%,transparent)}
.zoomer{display:inline-flex;gap:5px;margin-inline-start:auto}
.zoomer button{
  min-width:44px;min-height:44px;padding:11px 10px;border:1px solid var(--line);border-radius:9px;
  background:var(--panel2);color:var(--ui);font-size:14px;font-weight:600;line-height:1;
}
.zoomer button:hover{border-color:var(--gold);color:var(--ink)}
/* the rest of the amud, folded — present and countable, never deleted */
.fold{
  display:block;width:100%;margin:14px 0;padding:11px 12px;
  border:1px dashed var(--line);border-radius:10px;background:transparent;
  color:var(--muted);font-size:12.5px;font-family:var(--ui);text-align:center;
}
.fold:hover{border-color:var(--gold);color:var(--ink)}
/* the perek map: what is in this perek, and where we are */
.pmap{display:flex;flex-direction:column;gap:6px;margin-bottom:16px}
.pstop{
  display:flex;gap:10px;align-items:baseline;width:100%;text-align:start;
  padding:10px 11px;border:1px solid var(--line);border-radius:10px;background:var(--panel2);
}
.pstop.cur{border-color:var(--gold);background:color-mix(in srgb,var(--gold) 10%,transparent)}
.pstop[data-s="נבדק"] .pnum,.pstop[data-s="הוחזק"] .pnum{background:var(--gold);color:var(--paper)}
.pnum{
  flex:none;min-width:26px;height:26px;border-radius:99px;border:1px solid var(--gold);
  color:var(--gold);font-size:12.5px;font-weight:700;display:inline-flex;
  align-items:center;justify-content:center;
}
.plab{font-size:13px;color:var(--ink-soft);line-height:1.45}
/* the perek's real text */
.mtext{margin:0 0 18px;padding:0 0 14px;border-bottom:1px solid var(--line)}
.mtext:last-child{border-bottom:0}
.mtext.here{background:color-mix(in srgb,var(--gold) 7%,transparent);
  border-radius:10px;padding:10px 12px;margin-inline:-12px}
/* the perek title. It is a heading statement addressed to the perek, and it
   used to render as a line INSIDE משנה א — where it read as mishnah text. */
.mperek{font-family:var(--book);font-size:19px;font-weight:600;color:var(--gold);
  text-align:center;margin:0 0 14px;padding-bottom:10px;border-bottom:1px solid var(--line)}
.mtext-h{font-family:var(--ui);font-size:12px;font-weight:600;color:var(--gold);margin-bottom:6px}
.mline{font-family:var(--book);font-size:calc(19px * var(--zoom,1));line-height:1.62;margin-bottom:2px}
.mempty{font-size:13px;color:var(--muted);padding:12px}
/* The legacy viewer shows each layer as a little chip that LOOKS like the
   layer — a yellow swatch for tannaitic, a blue-underlined one for a sage
   name, the verse in David Libre. A row of text labels tells you the name
   of a thing you have never seen; the chip tells you what will change. */
.lchip{
  display:inline-flex;align-items:center;justify-content:center;
  min-width:34px;height:26px;padding:0 8px;border-radius:7px;flex:none;
  font-family:var(--book);font-size:14px;color:var(--ink);background:var(--panel2);
  border:1px solid var(--line);
}
.lchip.tannaic{background:var(--tannaic-bg);border-color:var(--tannaic-edge)}
.lchip.sage{background:transparent;border:0;border-bottom:3px solid var(--sage-line);border-radius:0}
.lchip.biblical{font-family:var(--verse);font-weight:500;background:transparent}
.lchip.conclusion{color:var(--conclusion);font-weight:700;background:transparent;border-color:var(--conclusion)}
.lchip.commentary{color:var(--comment);font-size:12px;background:transparent;border-color:var(--comment)}
.lchip.ref{color:var(--ref);font-size:12px;background:transparent;border-color:var(--ref)}
.set-row{display:flex;align-items:center;gap:10px}
.set-row .lbl{display:flex;align-items:center;gap:9px;min-width:0}
/* the glosses, beside the words they explain — MISHNAH_CHARTER's step (ג) */
.mterms{list-style:none;margin:10px 0 0;padding:10px 0 0;border-top:1px dashed var(--line)}
.mterms li{font-family:var(--ui);font-size:13.5px;line-height:1.65;margin-bottom:5px;
  display:flex;gap:8px;align-items:baseline;flex-wrap:wrap}
.mterms b{font-family:var(--book);font-size:16px;color:var(--gold);flex:none}
.mterms span{color:var(--ink-soft)}
/* -- the answer chips ------------------------------------------------
   OFF by default, at the owner's word: he called them bulky and asked for
   them gone, and said they may come back smaller and more precise.

   Hidden, not deleted. app.js still writes them and its click handler still
   works, so nothing over there changes and bringing them back is ONE
   attribute on the root, not a rebuild. That matters because of the second
   half of what he said.

   What it buys, measured: the chip row was 156px of a 844px screen (18%)
   and 157 of 568 on a small phone (28%). That space goes to the
   conversation and the daf, which he asked for in the same breath.

   What it costs, and it should be said: a seven-year-old who cannot type
   yet had one way to answer and it was these. The microphone covers part
   of that now, and the composer the rest. */
.chips{display:none;flex-wrap:wrap;gap:7px;padding:0 var(--sp) 10px}
:root[data-chips="on"] .chips{
  display:flex;flex-wrap:nowrap;overflow-x:auto;overscroll-behavior-x:contain;
  scrollbar-width:none;gap:6px;padding:0 var(--sp) 7px;
}
:root[data-chips="on"] .chips::-webkit-scrollbar{display:none}
/* "smaller and more precise", if he wants them back. ONE row that scrolls
   sideways instead of a block that wraps to four: the bulk was never the
   buttons, it was wrap+flex-wrap turning four short Hebrew words into 156px
   of a 844px screen. Nowrap plus a scroll makes the same four controls 34px.

   They are not decoration, which is why the switch exists at all: מוכן, מכיר,
   מה זה? and נמשיך run guide.js through G.chip, and typing runs the model
   through modelTurn. With the chips off, a learner who cannot type has no way
   to advance a station and the deterministic floor is unreachable. */
:root[data-chips="on"] .chips button{
  flex:none;padding:8px 13px;font-size:13px;min-height:34px;white-space:nowrap;
}
.chips:empty{display:none}
.chips button{
  padding:11px 15px;border:1px solid var(--gold);border-radius:99px;
  font-size:13.5px;color:var(--gold);background:color-mix(in srgb,var(--gold) 9%,transparent);transition:.16s var(--fx);
}
.chips button:hover{background:color-mix(in srgb,var(--gold) 18%,transparent)}

#compose{
  display:flex;gap:8px;align-items:flex-end;
  padding:10px var(--sp) calc(14px + env(safe-area-inset-bottom));
  border-top:1px solid var(--line);background:var(--panel);
}
#inp{
  flex:1;min-width:0;resize:none;max-height:110px;
  padding:10px 12px;border:1px solid var(--line);border-radius:11px;
  background:var(--panel2);font-size:16px;line-height:1.45;
}
#inp:focus{outline:none;border-color:var(--gold)}
/* --muted is 5.81:1 on the composer ground in dark and tracks both themes;
   opacity:1 because Firefox dims a placeholder by default and would undo it. */
#inp::placeholder{color:var(--muted);opacity:1}
#send{
  padding:10px 18px;border-radius:11px;background:var(--gold);color:var(--paper);
  font-weight:700;font-size:14.5px;flex:none;
}
#send:disabled{opacity:.45;cursor:default}

/* ── הדף ───────────────────────────────────────────────────────────── */
.daf-h{font-family:var(--book);font-size:15px;color:var(--ink-soft)}
/* failure #10: tap targets on a scrollable region must not eat the gesture */
/* pan-y ALONE also disables pinch-zoom and double-tap zoom. FAILURE-LOG #10
   was about a nested scroller, and at <=900px there is no nested scroller —
   so restricting here buys nothing and takes zoom away from a 7-year-old
   reading 19px pointed text. */
#dafB{touch-action:pan-y pinch-zoom}
/* The amud boundary is WORD-EXACT and lives on the segment. Measured on the
   live API: ברכות carries 124 boundary segments and 68 fall MID-STATEMENT.
   So the mark sits INLINE, in the flow, at the exact word — never as a rule
   drawn between blocks, which is only ever right by luck. */
.dafmark{
  display:inline-block;font-family:var(--ui);font-size:.62em;font-weight:600;
  background:var(--gold-soft);color:var(--gold);border:1px solid var(--tannaic-edge);
  border-radius:20px;padding:1px 10px;margin:0 6px;vertical-align:2px;
  user-select:none;white-space:nowrap;
}
/* the label for the amud the pane opens on — not a boundary, an address */
.daf-open{
  font-family:var(--ui);font-size:12.5px;color:var(--gold);
  border-bottom:1px solid var(--line);padding-bottom:4px;margin:0 0 12px;
}
.u{
  font-family:var(--book);font-size:calc(19px * var(--zoom,1));line-height:1.62;
  padding:3px 8px;margin:0 -8px 1px;border-radius:6px;
  color:var(--ink-soft);cursor:pointer;
  transition:background .16s var(--fx),color .16s var(--fx);
}
.u b{font-weight:700;color:var(--ink)}
.u.taught{color:var(--ink)}
.u.here{background:color-mix(in srgb,var(--gold) 12%,transparent);color:var(--ink);
        box-shadow:inset -3px 0 0 var(--gold)}
.u.sel{background:color-mix(in srgb,var(--sage-line) 16%,transparent)}

/* ══ the text layers ══════════════════════════════════════════════════
   Distinguished by DEVICE, not colour alone — a highlight, a book face, an
   underline — so they survive colour-blindness and read at a glance. Each
   can be switched off from the settings panel. */
.l-tannaic{background:var(--tannaic-bg);box-shadow:0 1px 0 var(--tannaic-edge);border-radius:2px}
.l-biblical{font-family:var(--verse);font-weight:500;font-size:1.06em}
.l-sage{text-decoration:underline;text-decoration-color:var(--sage-line);
        text-decoration-thickness:1.5px;text-underline-offset:4px}
.l-conclusion{color:var(--conclusion);font-weight:700}
.l-commentary{color:var(--comment);font-size:.82em}
.l-ref{color:var(--ref);font-size:max(.82em,15px)}
body.off-tannaic .l-tannaic{background:none;box-shadow:none}
body.off-biblical .l-biblical{font-family:inherit;font-weight:inherit;font-size:inherit}
body.off-sage .l-sage{text-decoration:none}
body.off-conclusion .l-conclusion{color:inherit;font-weight:inherit}
body.hide-commentary .l-commentary{display:none}
body.hide-ref .l-ref{display:none}

/* ══ דף וילנא ══════════════════════════════════════════════════════════
   THE SCAN CARRIES NO FILTER. Not `filter`, not `mix-blend-mode`, not an
   `opacity` under a tinted ground — a CSS change to what he sees is a change
   to what he sees, exactly like an image-processing one. The owner ruled it
   on 2026-09-08: «ללא הלבנה וללא שינוי אחר בקובץ», and spec/16 carries it.
   The page is bright on a dark theme because the page IS bright: it is paper.
   Everything below styles the FRAME around the image and never the image. */
.vilna-msg{
  padding:26px 18px;text-align:center;color:var(--muted);font-size:14px;
  border:1px dashed var(--line);border-radius:10px;line-height:1.8;
}
/* TWO DAF LABELS, FORTY PIXELS APART, DISAGREEING. `.daf-h` names the
   lesson's page — right for our text, which shows the whole run from it —
   while the scan names the amud actually on screen, and the two differ for
   every sugya that crosses a page. A learner reads that as a fault. The
   scan's own bar is the one that matches what he is looking at, so it is
   the one that stays. (Seen on חולין קיז,ב→קיח,א, not reasoned about.) */
body.vilna .daf-h{display:none}
/* ביאור hangs UNDER the unit it explains, and a scan has no units — so over
   the image the button is a control that does nothing when pressed. For a
   learner who is here because a crowded page needs guidance, a dead control
   is worse than an absent one. It comes back with our text. */
body.vilna .biur-btn,body.vilna .biur-credit{display:none}
.vilna-wrap{display:flex;flex-direction:column;gap:10px}
.vilna-bar{
  display:flex;align-items:center;justify-content:center;gap:14px;
  font-family:var(--book);font-size:15px;color:var(--ink-soft);
}
.vilna-at{min-width:9ch;text-align:center}
.vilna-step{
  width:34px;height:34px;flex:none;border:1px solid var(--line);border-radius:9px;
  background:var(--panel2);color:var(--ink-soft);font-size:19px;line-height:1;
}
.vilna-step:hover:not(:disabled){border-color:var(--gold);color:var(--ink)}
.vilna-step:disabled{opacity:.32}
/* א+ / א− already means "make it bigger" to this learner, and over a scan
   there is nothing else it could mean. It is the same --zoom the daf text
   uses, so the control keeps working and nothing new had to be taught.
   Scaling is a SIZE, not a filter: the pixels are untouched, and the request
   is already at twice the pane width so 1.35 stays sharp. */
.vilna-scroll{overflow-x:auto;overflow-y:hidden;touch-action:pan-x pan-y}
#vilnaImg{display:block;width:calc(100% * var(--zoom,1));height:auto;border-radius:6px}
.vilna-credit{
  display:block;text-align:center;font-size:12px;color:var(--muted);
  text-decoration:none;padding-bottom:6px;
}
.vilna-credit:hover{text-decoration:underline}
.trm{border-bottom:1px dashed var(--gold);cursor:pointer;color:inherit}
.trm:hover{border-bottom-color:var(--gold);background:color-mix(in srgb,var(--gold) 11%,transparent)}
.acts{display:flex;flex-wrap:wrap;gap:7px;margin:4px 0 12px}
.acts button{
  padding:7px 12px;border:1px solid var(--line);border-radius:9px;
  font-size:13px;color:var(--ink-soft);background:var(--panel2);
}
.acts button:hover{border-color:var(--gold);color:var(--ink)}
.pop{
  background:var(--panel);border:1px solid var(--gold);border-radius:10px;
  padding:10px 12px;margin:5px 0 10px;font-size:14px;line-height:1.55;font-family:var(--ui);
}
.pop b{display:block;font-family:var(--book);font-size:17px;color:var(--gold);margin-bottom:4px}
.pop i{display:block;font-style:normal;color:var(--muted);font-size:12.5px;margin-top:5px}

/* ── משנה panels (frozen reader, spec 15) ────────────────── */
.mblock{margin:0 0 18px}
.mblock h3{
  font-family:var(--book);font-size:13px;font-weight:500;color:var(--gold);
  letter-spacing:.4px;margin-bottom:8px;padding-bottom:5px;border-bottom:1px solid var(--line);
}
.mrow{display:flex;gap:8px;font-size:13.5px;line-height:1.55;margin-bottom:5px}
.mrow b{flex:none;color:var(--muted);font-weight:600;min-width:74px}
.mrow span{color:var(--ink-soft);min-width:0}
.mitem{
  padding:9px 11px;margin-bottom:8px;border:1px solid var(--line);
  border-radius:9px;background:var(--panel2);font-size:13.5px;line-height:1.55;
}
.mitem>b{display:block;color:var(--ink);font-family:var(--book);font-size:15.5px;margin-bottom:4px}
.mitem p{color:var(--ink-soft);margin-bottom:5px}
.mitem ul{margin:4px 0 0;padding-inline-start:18px;color:var(--ink-soft)}
.mitem ul.warn li{color:var(--conclusion)}
.mblock p{font-size:14px;line-height:1.65;color:var(--ink-soft);margin-bottom:8px}
.mish{
  padding:12px 0 14px;border-bottom:1px solid var(--line);
}
.mish:last-child{border-bottom:0}
.mish-h{
  font-family:var(--book);font-size:12.5px;color:var(--gold);margin-bottom:6px;
}
.mish p{font-family:var(--book);font-size:17px;line-height:1.85;color:var(--ink);margin-bottom:7px}
.terms{list-style:none;margin:8px 0 0;padding:0}
.terms li{
  font-size:13px;line-height:1.5;color:var(--ink-soft);
  padding:5px 0;border-top:1px dashed var(--line);
}
.terms li b{font-family:var(--book);font-size:15px;color:var(--gold);margin-inline-end:4px}
.hints{margin:5px 0 0;padding-inline-start:18px;font-size:12.5px;color:var(--muted)}
.mtags{display:flex;flex-wrap:wrap;gap:5px;margin:5px 0}

/* ── toast: TOP-anchored. the bottom of the viewport lies. ───────────
   top:10px put it straight on top of the nav tabs on a phone; clearing the
   tabs alone then put it on top of the state card — the same collision one
   element along. It clears the whole sticky block now, and that height is
   measured by app.js into --top-h rather than written here as a guess. */
#toast{
  position:fixed;top:calc(var(--top-h, var(--nav-h)) + 8px);inset-inline:0;margin-inline:auto;width:max-content;max-width:88vw;
  padding:9px 16px;border-radius:99px;background:var(--panel);border:1px solid var(--gold);
  color:var(--ink);font-size:13.5px;z-index:60;
  opacity:0;transform:translateY(-8px);pointer-events:none;transition:.2s var(--fx);
}
#toast.on{opacity:1;transform:none}

/* ── sheet ─────────────────────────────────────────────────────────────
   On mobile the OVERLAY scrolls, from the top. Not bottom-anchored, no vh
   max-height, no nested scroller — a sheet pinned to the bottom of a
   fixed overlay is failure 6-8 wearing a different hat. Desktop gets the
   centred card, where viewport units are honest. */
.sheet{
  position:fixed;inset:0;background:color-mix(in srgb,var(--ink) 55%,transparent);z-index:70;
  overflow-y:auto;-webkit-overflow-scrolling:touch;overscroll-behavior:contain;
}
.sheet[hidden]{display:none}
.sheet-in{
  width:100%;background:var(--panel);
  border-bottom:1px solid var(--line);border-radius:0 0 16px 16px;margin-bottom:44px;
}
.sheet-h{
  position:sticky;top:0;z-index:1;background:var(--panel);
  display:flex;align-items:center;justify-content:space-between;gap:10px;
  padding:14px var(--sp);border-bottom:1px solid var(--line);font-weight:700;
}
.sheet-h button{color:var(--muted);font-size:13.5px}
.sheet-b{padding:var(--sp);padding-bottom:calc(var(--sp) + env(safe-area-inset-bottom))}
.set-grp{margin-bottom:18px}
.set-grp h4{font-size:11.5px;font-weight:600;letter-spacing:.5px;color:var(--muted);
  margin-bottom:8px;padding-bottom:5px;border-bottom:1px solid var(--line)}
.set-row{display:flex;align-items:center;gap:10px;padding:7px 0;font-size:13.5px;color:var(--ink-soft)}
.set-row>span:first-child{flex:1;min-width:0}
/* MEASURED at 28px tall, every switch in the settings sheet — the learner
   switcher, the teacher's figure, the conversation archive and now the
   sectors. This project's own floor everywhere else is 42-46px
   (.game-mark 42, .lvl-pick 46), and 28 is a target a seven-year-old's
   thumb misses on a tablet. It was never mine and it was never measured;
   it is one line and it fixes every switch at once. */
.sw-btn{padding:4px 12px;border:1px solid var(--line);border-radius:99px;font-size:12px;
  min-height:44px;color:var(--muted);background:var(--panel2);white-space:nowrap}
.sw-btn.on{border-color:var(--gold);color:var(--gold);background:color-mix(in srgb,var(--gold) 10%,transparent)}
.reset-btn{width:100%;padding:10px;border:1px solid var(--line);border-radius:10px;
  font-size:13.5px;color:var(--ink-soft);background:var(--panel2)}
.reset-btn:hover{border-color:var(--conclusion);color:var(--conclusion)}
.set-note{font-size:11.5px;color:var(--muted);margin-top:6px;line-height:1.6;text-align:center}
.trow{padding:10px 0;border-bottom:1px solid var(--line)}
.trow:last-child{border-bottom:0}
.trow b{font-family:var(--book);font-size:17px;color:var(--ink)}
.trow .g{font-size:13.5px;color:var(--ink-soft);line-height:1.5;margin-top:3px}
.trow .w{font-size:12px;color:var(--muted);margin-top:3px}

/* ══════════════════════════════════════════════════════════════════════
   DESKTOP ONLY — the three-column shell, additive.
   min-height:0 at every level of every scroll chain (failure #9).
   ══════════════════════════════════════════════════════════════════════ */
@media (min-width:901px){
  #app{height:100vh;height:100dvh;display:flex;flex-direction:column;overflow:hidden}
  #top{position:static;flex:none}
  #tabs{display:none}
  :root{--nav-h:0px}   /* first-frame fallback only; --top-h still measures #top */

  /* RTL: the first grid column is the rightmost — הקו · הרב · הדף */
  /* WHY THE DAF OUTWEIGHS THE TEACHER. The tracks were 1fr for הרב against
     .95fr for הדף, so at 1440 the conversation column measured 600px and the
     Gemara 570 — the frame around the text was wider than the text. And the
     column that won was the one with nothing in it: #chat measured 599x397
     with 83px of content and 315px of empty, 79% of its own height.

     1.62fr against 1fr gives the daf 720 and the teacher 450 at 1440, and it
     scales: at 1920 the daf takes 1017 instead of 803. The teacher's column
     loses nothing that matters — a chat line at 599px is already past the
     width where Hebrew reads comfortably, and the avatar is 180px tall, not
     wide.

     It stays a RATIO and not a fixed width on purpose: panes.js writes
     --w-daf in px when the learner drags the divider, and the moment it does
     the middle track absorbs the remainder exactly as before. This changes
     where the shell STARTS, not what the learner can do to it. */
  /* the widths are TOKENS so a control can move them. clamp() keeps each
     column inside a sane range whatever is stored, so a bad saved value
     degrades to a narrow-or-wide column and never to a broken shell. */
  main{flex:1;min-height:0;display:grid;
    grid-template-columns:clamp(180px,var(--w-kav,270px),40vw) minmax(360px,1fr) minmax(0,var(--w-daf,1.62fr))}
  /* atlas:off takes #kav out of the grid entirely; without this the two
     survivors auto-place into the 270px and 1fr tracks and the .95fr track
     stays empty — the rollback lever producing a broken desktop. */
  /* the second track list must move in lockstep — its own comment records
     that this rule already produced a broken desktop once */
  body.no-atlas main{grid-template-columns:minmax(360px,1fr) minmax(0,var(--w-daf,1.62fr))}

  /* ══ SECTORS THE LEARNER TURNS OFF ═══════════════════════════════════
     THE OWNER: «כיבוי והדלקה של הסקטורים», on top of a default that already
     uses the room well — he was explicit about the order: «קודם כל תעשה הכי
     יעיל שאפשר כברירת מחדל, על גבי זה תן את האפשרות לשנות».

     One track list per surviving combination, written out rather than
     computed, and the reason is written above this block: the FIRST version
     of `no-atlas` moved one rule and not its partner, the survivors
     auto-placed into the wrong tracks, and the lever meant to make the
     desktop safer produced a broken one. Three panes give six ways to hide
     at least one and keep at least one, and each is one line that can be
     read against the row it replaces.

     `--w-kav` and `--w-daf` still carry the learner's dragged widths, so a
     pane that comes back comes back the width he left it. */
  body.off-kav main{grid-template-columns:minmax(360px,1fr) minmax(0,var(--w-daf,1.62fr))}
  body.off-rav main{grid-template-columns:clamp(180px,var(--w-kav,270px),40vw) minmax(0,1fr)}
  body.off-daf main{grid-template-columns:clamp(180px,var(--w-kav,270px),40vw) minmax(360px,1fr)}
  body.off-kav.off-rav main,
  body.off-kav.off-daf main,
  body.off-rav.off-daf main{grid-template-columns:minmax(0,1fr)}
  .col,.col.show{
    display:flex;flex-direction:column;min-height:0;min-width:0;overflow:hidden;
    border-inline-start:1px solid var(--line);
  }
  #kav{border-inline-start:0;background:var(--paper-deep)}
  .pane-head{flex:none}
  .pane-body{
    flex:1;min-height:0;overflow-y:auto;overscroll-behavior:contain;touch-action:pan-y;
    -webkit-overflow-scrolling:touch;
  }
  .chips,#compose{flex:none}
  #chat{min-height:0}
  .sheet{display:flex;align-items:center;justify-content:center;overflow:hidden}
  .sheet-in{
    max-width:640px;margin-bottom:0;border-radius:16px;border:1px solid var(--line);
    max-height:80vh;display:flex;flex-direction:column;min-height:0;
  }
  .sheet-h{flex:none}
  .sheet-b{overflow-y:auto;min-height:0;overscroll-behavior:contain}
  .u{font-size:calc(20px * var(--zoom,1))}
}

/* the build stamp, readable without a console — a stale copy has twice been
   run and produced a bug report for something already fixed */
.set-stamp{font-family:var(--ui);font-size:11px;color:var(--ink-soft);
  text-align:center;padding:10px 0 2px;opacity:.75;letter-spacing:.02em}

/* ── one product, one display face ────────────────────────────────────
   Owner ruling 2026-08-28: the gate loads Suez One and the app did not, so
   the front door and the study room read as two different sites. The app
   adopts it — dropping it from the gate would be a change to the live site,
   which does not move without an explicit go, while adopting it is additive
   and reversible.

   Display headings ONLY: the sugya title and the perek heading, the two
   places that are already display type. Never running text, where a heavy
   display face fights the Gemara. */
#kavT{font-family:var(--display);letter-spacing:-.01em}
.mperek{font-family:var(--display)}

/* ── הרב — the avatar stage (spec 10 stage 8) ──────────────────────────
   Height in PIXELS, never vh. FAILURE-LOG 6-9 is about viewport units in
   the app shell, and while a decorative box is not the shell, the same
   trap applies on a phone: the URL bar resizes the viewport mid-scroll and
   a vh-sized element jumps under the reader's thumb.

   `hidden` collapses it to nothing, which is what an avatar that failed to
   load must cost: zero. app.js re-hides the stage whenever mount() does not
   reach "ready", so a missing GLB leaves the lesson exactly as it was.

   It sits ABOVE the transcript and never overlays it — spec 09 records the
   figure defaulting to 42% precisely so the conversation cannot vanish
   behind it. Here it takes its own space instead, which cannot overlap. */
#ravStage{
  height:220px; overflow:hidden; position:relative;
  background:var(--panel2); border-bottom:1px solid var(--line);
}
/* A phone had a BIGGER avatar than a desktop — 260px against 180px — which is
   backwards: the small screen was giving the face more room than the large one.
   Measured at 375x812 with the floor fixed: at 260 the composer stayed 19px
   below the fold, at 220 it clears with 21px to spare. */
#ravStage[hidden]{ display:none }
/* the learner turned the figure off (settings-extra.js). An ATTRIBUTE on the
   root, not a second writer to #ravStage.hidden: app.js mounts the avatar
   asynchronously and sets `hidden` itself when the mount fails, so two writers
   to one property would race and the loser would be whichever finished last.
   This also applies before #ravStage exists, so there is no frame in which the
   figure appears and is then taken away. */
:root[data-rav="off"] #ravStage{ display:none }
#ravStage canvas{ display:block; width:100%!important; height:100%!important }
/* measured on a 900px screen: the avatar held 320px against the transcript's
   153px — 6.1 lines of conversation. At 180px the transcript becomes 293px,
   11.7 lines, and the avatar is still larger than its 260px phone size. */
/* A SHORT phone — 375x667 is still the commonest small screen — cannot hold a
   204px header, a face, a conversation, four chips and a composer at once.
   Measured there with the floor already fixed: at 220 the composer was 61px
   below the fold, at 180 21px below, at 150 it clears. The avatar is an
   addition; the lesson is the product, so the avatar is what gives way.
   A height media query is a breakpoint, not a viewport-derived SIZE — it is
   what FAILURE-LOG 6-9 permits, and this file already uses one below. */
@media (max-height:740px){ #ravStage{ height:150px } }
/* last, so a wide desktop that happens to be short keeps the desktop figure */
/* On desktop the figure GROWS INTO the empty room instead of leaving it.
   Measured at 1440x900 with the shell as it stood: #chat was 397px tall and
   held 83px of conversation — 312px of nothing between the teacher and the
   one message. The canvas fills its box exactly (446x179 of a 446x180 stage)
   and its backing store is resized with it, so a taller stage is a larger
   teacher and not a larger empty rectangle; that was checked on the page
   before this rule was written.

   clamp, not a number: at 900px of height the stage becomes 306px and the
   hole drops to about 190; on a 1200px screen it stops at 360 rather than
   growing without end; on a short laptop it never goes below the 180 it had.
   The conversation keeps its own flex:1 and its own scroll, so a long
   lesson still pushes the stage back to the floor. */
@media (min-width:901px){ #ravStage{ height:clamp(180px,34vh,360px) } }
/* A short phone has no room for a face AND a lesson. The lesson wins — it is
   the product; the avatar is an addition to it.

   520px was written for landscape and let a 320x568 phone through, where the
   whole lesson then failed. MEASURED there, at scroll 0:
     #top 137 · #ravStage 150 · #chat 142 · #chips 157 · #compose 70 = 656
     against a 568 viewport — the chips AND the composer below the fold, which
     is the failure this project exists to stop.
   Removing the stage leaves 61px of headroom, so the avatar is exactly the
   89px of overflow and nothing else has to give.

   600 and not higher: at 375x667 the stage stays at 150 and everything fits
   (measured: document 667, viewport 667). That fit has NO margin, and if the
   state card grows a line — it is 204px tall on the live site against 137
   here — 667 overflows too. That is a known limit, measured and not fixed by
   this rule; the lever for it is the card, not the avatar. */
@media (max-height:600px){ #ravStage{ display:none } }

/* ── the way home ─────────────────────────────────────────────────────
   The brand is an <a> now, so it needs to keep looking like a masthead
   rather than a link. Until this existed the app had ZERO anchors: a
   learner who entered a world could not reach the door, another world, or
   anything else — measured on the live site, not assumed. */
a.brand{ text-decoration:none; color:inherit; display:inline-block }
a.brand:hover em{ text-decoration:underline }
/* the same exit inside the settings sheet, because .hrow is display:none
   under 900px and a phone would otherwise still have no way out */
.set-row a.hbtn{ text-decoration:none; display:inline-flex; align-items:center }

/* ── הערה מחקרית ──────────────────────────────────────────────────────
   Scholar-gated content at the foot of the map. Set apart from the stops
   so it reads as apparatus rather than as another station. */
.kav-note{ margin:18px 4px 8px; padding:12px 14px; border-radius:10px;
  background:var(--card,rgba(255,255,255,.04)); border:1px solid var(--line,rgba(255,255,255,.10)) }
.kav-note h4{ margin:0 0 6px; font-size:.82rem; letter-spacing:.02em;
  color:var(--dim,#9aa4b2); font-weight:600 }
.kav-note p{ margin:0; font-size:.92rem; line-height:1.7 }

/* ── the sugya's moves, in the margin of the text ──────────────────────
   The owner asked to see קושיה · תירוץ · מסקנה by name, with the symbols
   drawn for them, at the junctions in the text itself and not only in a
   diagram. Painted by daf.js from the brief's own `סוג`, which every one
   of the corpus's 1,343 map nodes already carries.

   The mark sits ABOVE its unit rather than inline: a 34px glyph inside a
   line of pointed Aramaic pushes the line height around and the niqqud is
   what suffers. Above the line it costs one row and disturbs nothing. */
.u.opens{ margin-top:14px }
.flowmark{
  display:flex; align-items:center; gap:6px; margin:0 0 3px;
  line-height:1; user-select:none;
}
.flowmark svg{ width:26px; height:26px; flex:none }
.flowmark .fl{
  font-family:var(--ui); font-size:.74rem; font-weight:600;
  color:var(--fc); letter-spacing:.02em;
}
/* the active station's mark is the one the learner is standing on */
.u.here .flowmark svg{ filter:drop-shadow(0 0 3px var(--fc)) }
.u.here .flowmark .fl{ text-decoration:underline; text-underline-offset:3px }
@media (max-width:900px){
  .flowmark svg{ width:22px; height:22px }
  .flowmark .fl{ font-size:.7rem }
}

/* ── the move label's colour, in both themes, by measurement ───────────
   The move palette is the LIVE SITE's, and the live site is one dark page,
   so every colour in it is a dark stroke on a pale fill. Used as small text
   here it fails at both ends, and both were measured across all 18 moves
   rather than judged by eye:

     on dark (#141a2e), native      worst 3.52:1   ·  under AA
     on light (#fbf8f1), native     worst 3.43:1   ·  under AA, 3 moves:
                                    ברייתא · דחייה · שיטה חולקת

   So the ORIGINAL palette does not meet AA as small text on either ground —
   it was drawn to fill shapes, not to letter them. Only the text is adjusted;
   the icon keeps the exact colour, because a pale badge with a dark stroke
   reads on both grounds and it is the icon that carries the identity.

   The two numbers are the smallest that clear 4.5 while keeping the moves
   TELLING APART — lifting harder passes more easily and collapses the hues
   toward one pale cream:

     dark  · +45% white   worst 5.80   13 of 18 hues still distinct
                          (+40% also passes but keeps only 10)
     light · +15% black   worst 4.56   zero failures

   `color:var(--fc)` stays first so that where color-mix is unsupported the
   line below is dropped and the label falls back to the live site's own
   colour — the behaviour we had, not a blank. */
.flowmark .fl{ color:var(--fc); color:color-mix(in srgb, var(--fc), #000 15%) }
@media (prefers-color-scheme:dark){ :root:not([data-theme="light"]) .flowmark .fl{
  color:color-mix(in srgb, var(--fc), #fff 45%);
}}
:root[data-theme="dark"] .flowmark .fl{ color:color-mix(in srgb, var(--fc), #fff 45%) }

/* ══ המשחק בתוך השיעור ═════════════════════════════════════════════════
   Drawn into #chat as a turn, not as a pane: PROMPT_SITE_GAMES §5 forbids
   a second path to the learner. Everything below is inside .bub, so the
   bubble's own max-width, padding and radius still govern — the board
   cannot outgrow the conversation it sits in.

   NOTHING HERE IS position:fixed AND NOTHING USES A VIEWPORT UNIT. Nine of
   eleven previous shells were broken on the owner's phone and
   reference/FAILURE-LOG.md records that every single failure was layout.
   The board flows; a missed rule leaves a plain document, not a broken
   shell. */
.game-board{margin:2px 0 10px;line-height:2.5}
.game-seg{margin-bottom:6px;font-family:var(--book);
  font-size:calc(16.5px * var(--zoom,1));word-spacing:.5px}
.game-word{white-space:nowrap}

/* THE GAP IS A TAP TARGET FIRST AND A GLYPH SECOND. 26px wide and 30px tall
   is above the 24px floor a thumb needs; the inline-block keeps it in the
   text flow so the line wraps like prose instead of like a toolbar. */
.game-gap{
  display:inline-block;min-width:26px;height:30px;vertical-align:-8px;
  margin:0 1px;padding:0 2px;border:1px dashed var(--thread);border-radius:7px;
  background:transparent;color:var(--gold);font-family:var(--ui);
  font-size:calc(15px * var(--zoom,1));line-height:28px;
  transition:background-color .14s var(--fx),border-color .14s var(--fx);
  touch-action:manipulation;
}
.game-gap:hover{background:color-mix(in srgb,var(--gold) 10%,transparent)}
.game-gap:focus-visible{outline:2px solid var(--gold);outline-offset:1px}
.game-gap.has{
  border-style:solid;border-color:var(--gold);
  background:color-mix(in srgb,var(--gold) 16%,transparent);
  color:var(--ink);font-weight:600;
}

/* the palette flows and wraps — it is part of the bubble, never anchored to
   an edge. A bottom-anchored control is failure #6-8 in the log. */
.game-marks{display:flex;flex-wrap:wrap;gap:7px;margin:8px 0 4px}
.game-mark{
  min-width:42px;height:42px;border:1px solid var(--line);border-radius:11px;
  background:var(--panel2);color:var(--ink);
  font-family:var(--book);font-size:19px;line-height:40px;
  transition:.14s var(--fx);touch-action:manipulation;
}
.game-mark:hover{border-color:var(--gold)}
.game-mark.on{
  border-color:var(--gold);background:color-mix(in srgb,var(--gold) 20%,transparent);
  box-shadow:0 0 0 2px var(--gold-soft);font-weight:700;
}
.game-mark:focus-visible{outline:2px solid var(--gold);outline-offset:2px}

.game-send{
  margin-top:8px;padding:11px 20px;border:1px solid var(--gold);border-radius:99px;
  background:color-mix(in srgb,var(--gold) 12%,transparent);color:var(--gold);
  font-size:14.5px;font-family:var(--ui);touch-action:manipulation;
}
.game-send:hover{background:color-mix(in srgb,var(--gold) 20%,transparent)}
.game-send[disabled]{opacity:.45;border-color:var(--line);color:var(--muted)}

/* the three states, and the colour is the ONLY thing that differs between
   them — no size, no weight, no celebration. §7: nothing is a score. */
.game-result{margin-top:10px;padding-top:9px;border-top:1px solid var(--line)}
.game-verdict{display:flex;align-items:baseline;gap:8px;margin-bottom:6px}
.game-tick{font-size:19px;line-height:1}
.game-state{font-family:var(--ui);font-size:14.5px;letter-spacing:.2px}
.game-result.state-נכון .game-tick{color:var(--comment)}
.game-result.state-מתקבל .game-tick{color:var(--gold-hi)}
.game-result.state-טעות .game-tick{color:var(--conclusion)}
.game-line{margin-bottom:7px}
.game-line:last-child{margin-bottom:0}

/* what the learner could not have answered — quieter than the verdict, and
   never in the colour of a mistake, because it is not one of his */
.game-cannot{
  margin-top:8px;padding:7px 10px;border-radius:9px;
  background:color-mix(in srgb,var(--muted) 10%,transparent);
  color:var(--muted);font-size:13.5px;line-height:1.55;
}
.game-empty{color:var(--muted);font-size:13.5px}

@media (prefers-reduced-motion:reduce){
  .game-gap,.game-mark,.game-send{transition:none}
}

/* the factory's own material, marked so a reader can tell a quotation from
   the runner's sentence — same distinction renderResult already draws */
.game-line[data-source="מפעל"]{
  padding-inline-start:10px;border-inline-start:2px solid var(--thread);
  color:var(--ink-soft);
}

/* ══ לוח ההורה ══════════════════════════════════════════════════════════
   Drawn inside the settings sheet, which already scrolls and already has
   the frame's padding — so this styles TEXT and nothing else. No card, no
   grid, no progress bar: charter §7 says nothing is counted, and a bar is
   a number drawn sideways. */
/* A MEASURE, because a parent reads this. At 1440 the lines ran the full
   width of the sheet — far past the ~65 characters running text is
   comfortable at, and a plant row then puts its skill and its word at
   opposite ends of the screen with nothing between them. Seen at the desktop
   breakpoint; the phone was already fine, which is why looking at both is
   the rule and not looking at one. */
.parent-board{
  line-height:1.65;font-size:calc(14.5px * var(--zoom,1));
  max-width:46em;
}
.pb-head{
  margin:16px 0 7px;font-family:var(--ui);font-size:13px;letter-spacing:.3px;
  color:var(--gold);font-weight:600;
}
.pb-head:first-child{margin-top:2px}
.pb-intro{margin-bottom:10px;color:var(--ink-soft)}

/* a plant is one line: the skill and the word for where it stands. The
   word is the whole report — parent.js decides it and this only draws it. */
.pb-plant{
  padding:7px 0;border-bottom:1px solid var(--line);
}
.pb-plant:last-of-type{border-bottom:0}

.pb-list{margin:0 0 6px;padding-inline-start:18px}
.pb-learned,.pb-point{margin-bottom:5px}
.pb-point{color:var(--ink-soft)}

/* the invitation is the one line that asks the parent to DO something,
   so it is the one line set apart */
.pb-invite{
  margin-top:14px;padding:10px 12px;border-radius:10px;
  background:color-mix(in srgb,var(--gold) 10%,transparent);
  border:1px solid var(--gold-soft);
}

/* what is missing from the board, in the colour of a note and never of a
   fault — it is the board's own gap, not the learner's */
.pb-problems{margin-top:16px;padding-top:10px;border-top:1px solid var(--line)}
.pb-problem{color:var(--muted);font-size:13px;line-height:1.5}

/* פסוקים — whole segments, each a toggle. Blocks and not inline, because the
   unit of the answer is a whole clause and a tap target that wraps mid-
   sentence is a target a thumb misses. */
.verse-board{display:flex;flex-direction:column;gap:7px;line-height:1.75}
.verse-seg{
  display:block;width:100%;text-align:start;
  padding:9px 12px;border:1px dashed var(--thread);border-radius:10px;
  background:transparent;color:var(--ink);
  font-family:var(--book);font-size:calc(16px * var(--zoom,1));line-height:1.7;
  transition:background-color .14s var(--fx),border-color .14s var(--fx);
  touch-action:manipulation;
}
.verse-seg:hover{border-color:var(--gold)}
.verse-seg:focus-visible{outline:2px solid var(--gold);outline-offset:1px}
.verse-seg.on{
  border-style:solid;border-color:var(--gold);
  background:color-mix(in srgb,var(--gold) 14%,transparent);
}
@media (prefers-reduced-motion:reduce){ .verse-seg{transition:none} }

/* the ביאור attribution, beside its toggle and only while it is on */
.biur-credit{font-size:11px;opacity:.62;text-decoration:none;color:inherit;
  margin-inline-start:6px;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;max-width:15em}
.biur-credit:hover{opacity:.9;text-decoration:underline}

/* a section heading in the daf. It is still a .u — every mechanism that
   mounts on a unit keeps working — but it reads as a title, which is what
   the corpus marked it as and what the old viewer showed. */
/* TWO LEVELS, AND THEY ARE NOT THE SAME THING. Measured over the four
   shipped perakim: h=2 is 7 units and 7 of 7 open with "פרק" -- a CHAPTER
   heading. h=3 is 32 units and 0 of 32 do; they quote the opening words of
   the mishna beneath them -- a MISHNA heading. The owner corrected me on
   exactly this, and he is right: styling them alike made a mishna label look
   like a chapter title, and made it read as a duplicate of the line below.
   Shas-wide the counts are 313 and 2,100, and 313 is the number of perakim
   in the Talmud, which is the check that the reading is right. */
.u.u-h{font-family:var(--ui);color:var(--gold);text-align:center}

/* a chapter opens: it gets room and weight */
.u.u-h2{font-size:1.16em;font-weight:700;letter-spacing:.02em;
  margin:26px 0 10px;padding-bottom:6px;
  border-bottom:1px solid var(--gold-soft);opacity:.95}

/* a mishna is LABELLED: quieter, tighter, and sitting close to the text it
   names rather than floating above it like a title */
.u.u-h3{font-size:.86em;font-weight:600;letter-spacing:.06em;
  margin:16px 0 2px;opacity:.66;border-bottom:none}

.u.u-h .dafmark{vertical-align:1px}

/* the layer, beside the step type and deliberately quieter than it */
#kavB .stop .lay{display:inline-block;margin-inline-start:5px;padding:0 6px;
  border-radius:99px;font-size:10px;line-height:1.55;opacity:.5;
  border:1px solid currentColor;vertical-align:1px}

/* A unit that continues the sentence the page cut in half. It stays its own
   .u element -- five mechanisms mount on that -- but the PARAGRAPH gap above
   it closes, so the two halves read as one passage with the daf pill sitting
   between them, which is what the corpus already encodes and what the owner
   asked to see. Applies to exactly 2 places in the shipped corpus. */
.u.u-cont{margin-top:0;padding-top:0}
.u.u-cont::before{content:"";display:block;height:0}
.u:has(+ .u-cont){margin-bottom:0;padding-bottom:0}
