/* Visual system ported from Gate of Noor's study-desk layout (tokens and component
   rules kept structurally identical). Light study-desk look: white panels, cream
   ruled paper. No dark theme.
   Variable names are kept from the Gate of Noor port (--teal, --rose, etc.) to
   minimise the diff against the rest of this file; their VALUES are set per title by
   js/title.js from title.palette, so nothing here assumes a colour — --teal is simply
   the title's accent slot, whatever hue that accent is. The five --text-font /
   --gloss-font / --bg / --accent / --accent-soft variables are also set there. */

:root {
  /* Neutral study-desk defaults, deliberately not any one title's palette. js/title.js
     overwrites every one of these (including the -rgb triplets, which the alpha-composited
     rules below need) from title.json's palette before the first paint; two titles on one
     origin never share colours. These values only show if title.json is missing or broken,
     and then the page should read as a plain desk rather than as somebody's title. */
  --ink: #2B2724;
  --muted: #6B6259;
  --line: #D8D2C8;
  --paper: #FAF7F0;
  --paper-deep: #F1EADC;
  --panel: #ffffff;
  --teal: #55606E;
  --teal-soft: #E4E7EB;
  --gold: #B08C4F;
  --rose: #A4564C;
  --ink-rgb: 43, 39, 36;
  --muted-rgb: 107, 98, 89;
  --line-rgb: 216, 210, 200;
  --paper-rgb: 250, 247, 240;
  --panel-rgb: 255, 255, 255;
  --accent-rgb: 85, 96, 110;
  --gold-rgb: 176, 140, 79;
  --rose-rgb: 164, 86, 76;
  /* Ruled-paper rule (the horizontal line the writing sits on). */
  --rule-rgb: 150, 140, 120;
  /* Semantic status colours, deliberately independent of the title palette so "on
     track" reads the same everywhere: a calm green, and a strengthened accent for a
     pressed primary button. */
  --ok: #4F7A55;
  --accent-strong: #414B57;
  --gold-light: #EAE0C8;
  /* Paper tints used for panels that must sit a half-step off the page colour. */
  --paper-a: #F4EFE6;
  --paper-b: #F6F2E9;
  --paper-c: #FCFAF5;
  --paper-tint: #F7F3EA;
  --paper-deep-2: #E8E0D1;
  --shadow: 0 18px 45px rgba(var(--ink-rgb), .14);
}

* { box-sizing: border-box; }
html, body { margin: 0; padding: 0; max-width: 100%; }

/* Horizontal overflow is still clipped at the viewport — but the BODY must not be a scroll
   container of its own. With `overflow-x: hidden` on the body the body becomes one, and every
   `position: sticky` inside it then resolves against a scrollport that never scrolls: measured in
   real Chromium, the approved wide preview column rode 400px up the page with the passage instead
   of staying in view. `clip` removes the same horizontal overflow WITHOUT creating a scroll
   container, so the sticky column really sticks; an engine that does not know `clip` falls back
   to the html rule below, which already clips the page horizontally. JOB-20261015321. */
html { overflow-x: hidden; }
body { overflow-x: clip; }
html { scroll-behavior: smooth; }

/* NO TEXT SELECTION IN THE APP CHROME.
   Owner, Oct 3 2026: "there is something annoying with all these apps is that when u are
   writing it seems to select the entore screenand it lights up in blue" - the same defect he
   reported in Sacred Gita the same day: "while u write the text turns green like u tried to
   select the entire line ... press and slide selects text usually with the pen as ure already
   trying to write by press and slide, it fucks things up in this app."
   The guide glyph is painted on the canvas and every label is chrome, but the DOM text (the
   translation chip, the group titles, the nav, the verse list) stayed selectable, so a finger
   or stylus press-and-slide started the platform's native selection: the run of text
   highlights in the system tint colour and the selection drag competes with the stroke.
   touch-action:none plus preventDefault() on pointerdown do NOT suppress that; only
   user-select and -webkit-touch-callout do. Measured 2026-10-03 in Chromium and WebKit
   against the built site: the identical drag over a heading selected 4 characters before this
   block and 0 after it. Every scripture-write title inherits this file, so the fix belongs
   here once. */
html, body {
  -webkit-user-select: none;
  -moz-user-select: none;
  user-select: none;
  -webkit-touch-callout: none;
  /* The platform tap flash is a translucent black wash (Chromium rgba(0,0,0,.18), WebKit
     rgba(0,0,0,.4)) over the paper on every tap. The deliberate press state at the end of
     this block replaces it. */
  -webkit-tap-highlight-color: transparent;
}
/* Anything the reader types into keeps normal editing and selection behaviour. */
input, textarea, [contenteditable="true"] {
  -webkit-user-select: text;
  -moz-user-select: text;
  user-select: text;
  -webkit-touch-callout: default;
}
/* The read-only reference and legal pages are meant to be copied from. */
.legal-page, .legal-page *, .m-story, .m-story * {
  -webkit-user-select: text;
  -moz-user-select: text;
  user-select: text;
  -webkit-touch-callout: default;
}
/* With the platform flash off, give every control a small deliberate press state. The engine
   had no :active rules at all, so the flash was the only touch feedback. brightness() is
   paint-only: it changes no layout and creates no containing block for the buttons' children. */
button:active, [role="button"]:active {
  filter: brightness(.94);
}
body {
  min-height: 100vh;
  font-family: system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, ui-sans-serif, sans-serif;
  color: var(--ink);
  background:
    radial-gradient(circle at 18% 8%, rgba(var(--gold-rgb), .16), transparent 30%),
    linear-gradient(135deg, var(--paper-a) 0%, var(--paper-b) 50%, var(--paper-c) 100%);
}
img, canvas { max-width: 100%; }
button, select, input { font: inherit; }
h1, h2, h3, p { margin: 0; }
[hidden] { display: none !important; }

*:focus-visible {
  outline: 2px solid var(--teal);
  outline-offset: 2px;
}

/* ══════════════════════════════════════════════
   APP SHELL — sidebar + main workspace
═══════════════════════════════════════════════ */
.app {
  min-height: 100vh;
  display: grid;
  grid-template-columns: 320px minmax(0, 1fr);
  gap: 18px;
  padding: 18px;
}

.sidebar, .workspace, .bottom-panel, .journey-panel, .sheet {
  background: rgba(var(--panel-rgb), .86);
  border: 1px solid rgba(var(--ink-rgb), .1);
  box-shadow: var(--shadow);
  backdrop-filter: blur(12px);
}

.sidebar {
  border-radius: 8px;
  padding: 18px;
  display: flex;
  flex-direction: column;
  gap: 18px;
  align-self: start;
}
/* The sidebar itself only becomes a scroll container on a short viewport (laptop lid
   half-open, a landscape phone, etc) — everything above 760px tall fits without it, so
   Journey/Account/About and the Today+Routine block never depend on scrolling to be
   seen. Below 760px, the sidebar scrolls as a whole so the controls further down are
   still reachable without the page itself growing past the viewport. */
@media (max-height: 759.98px) {
  .sidebar {
    max-height: calc(100vh - 36px); /* 36px = .app's 18px padding, top + bottom */
    overflow-y: auto;
  }
}

.brand { display: flex; align-items: center; gap: 12px; }
.mark {
  width: 46px; height: 46px;
  border-radius: 8px;
  display: grid;
  place-items: center;
  background: var(--teal);
  color: var(--gold-light);
  font-family: var(--text-font);
  font-size: 26px;
  font-weight: 800;
  flex-shrink: 0;
}
h1 { font-size: 1.35rem; line-height: 1.12; }
.subtitle { color: var(--muted); font-size: .92rem; line-height: 1.45; margin-top: 4px; }

.group { display: grid; gap: 10px; }
.group-title {
  font-size: .82rem;
  text-transform: uppercase;
  letter-spacing: .08em;
  color: var(--muted);
  font-weight: 800;
}

/* ── Today verse list ── */
/* Each Today card is one intrinsic row: `grid-auto-rows: max-content` stops the row from being
   stretched or clipped by the scrolling panel, and `minmax(0, 1fr)` stops a long verse name from
   widening the column past the sidebar (which at large text sizes used to cut the preview off).
   JOB-007, Step 5. */
.verse-list {
  display: grid;
  grid-template-columns: minmax(0, 1fr);
  grid-auto-rows: max-content;
  gap: 8px;
  max-height: 34vh;
  overflow-y: auto;
}
.verse-btn, .tool-btn, .chip {
  min-height: 44px;
  border: 1px solid rgba(var(--accent-rgb), .16);
  border-radius: 8px;
  background: #fff;
  color: var(--ink);
  cursor: pointer;
  touch-action: manipulation;
}
.verse-btn {
  width: 100%;
  padding: 10px 12px;
  text-align: left;
  display: grid;
  gap: 4px;
}
.verse-btn[aria-pressed="true"] { border-color: var(--teal); background: var(--teal-soft); }
.verse-name { font-weight: 800; font-size: .96rem; }
.verse-meta { color: var(--muted); font-size: .82rem; }

/* ── generic outline button (also used standalone: Change chapter, sheet close, etc) ── */
.tool-btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 7px;
  padding: 0 12px;
  font-weight: 750;
  width: 100%;
}
.tool-btn:hover { border-color: var(--teal); }
.tool-btn.primary { background: var(--teal); color: #fff; border-color: var(--teal); }
.tool-btn.primary:hover { background: var(--accent-strong); }
.tool-btn.warn { color: var(--rose); border-color: rgba(var(--rose-rgb), .28); }
.tool-btn.warn:hover { background: rgba(var(--rose-rgb), .06); }
.tool-btn:disabled { opacity: .55; cursor: not-allowed; }
.field-btn { margin-top: 12px; }

.link-btn {
  display: block;
  width: 100%;
  min-height: 40px;
  background: none;
  border: none;
  color: var(--teal);
  font-weight: 700;
  font-size: .88rem;
  text-align: left;
  text-decoration: underline;
  cursor: pointer;
  padding: 2px 0;
}

/* ── Home / Journey / Account / About — small pills right under the brand block, always
   visible without scrolling (see the .sidebar media query above) ── */
.sidebar-pills { display: grid; grid-template-columns: repeat(3, 1fr); gap: 8px; }
.sidebar-pills-4 { grid-template-columns: repeat(4, 1fr); }

/* JOB-10116: Home re-opens this title's own welcome page, so it spans the whole row above the
   four interface pills instead of squeezing a fifth column into them. */
.sidebar-pills .pill-home { grid-column: 1 / -1; }
.pill-btn {
  min-height: 36px;
  border-radius: 999px;
  border: 1px solid rgba(var(--accent-rgb), .18);
  background: #fff;
  color: var(--teal);
  font-weight: 700;
  font-size: .82rem;
  cursor: pointer;
  touch-action: manipulation;
  padding: 0 6px;
  /* .pill-btn is also used on the <a> Museum link — same look as the <button> pills. */
  display: flex;
  align-items: center;
  justify-content: center;
  text-decoration: none;
  text-align: center;
}
.pill-btn:hover { border-color: var(--teal); background: var(--teal-soft); }

/* ── controls (ink / stroke / guide / writing mode) ── */
.controls { display: grid; gap: 12px; }
.control-row { display: grid; gap: 6px; }
label { font-size: .88rem; font-weight: 750; color: var(--muted); }
select, input[type="range"] { width: 100%; }
select {
  min-height: 42px;
  border-radius: 8px;
  border: 1px solid rgba(var(--accent-rgb), .18);
  background: #fff;
  color: var(--ink);
  padding: 0 10px;
}
input[type="range"] { accent-color: var(--teal); }

.chips { display: grid; grid-template-columns: repeat(3, 1fr); gap: 8px; }
.chips.chips-2 { grid-template-columns: repeat(2, 1fr); }
.level-exhausted { display: grid; gap: 8px; margin-top: 4px; }
.chip { display: grid; place-items: center; padding: 8px; font-weight: 700; }
.chip[aria-pressed="true"] { background: var(--teal); color: #fff; border-color: var(--teal); }

.progress, .goal-panel {
  display: grid;
  gap: 8px;
  padding: 12px;
  border-radius: 8px;
  background: var(--paper-tint);
  border: 1px solid rgba(var(--accent-rgb), .12);
}
.meter { height: 10px; border-radius: 999px; background: var(--paper-deep-2); overflow: hidden; }
.meter span {
  display: block;
  height: 100%;
  width: 0%;
  background: linear-gradient(90deg, var(--teal), var(--gold));
  transition: width .25s ease;
}

/* ── one-year goal panel (sidebar, replaces the old Routine box) ── */
.goal-line { font-size: .88rem; color: var(--ink); line-height: 1.4; }
.goal-meter span { background: linear-gradient(90deg, var(--gold), var(--teal)); } /* gold -> accent, whole-text progress */
.goal-status { font-size: .84rem; font-weight: 750; margin-top: 2px !important; }
.goal-status.on-track { color: var(--ok); } /* calm earthy green */
.goal-status.behind { color: var(--rose); }

/* Same four lines, reused atop the Journey screen for members. */
.journey-goal-lines { display: grid; gap: 4px; margin-bottom: 4px; font-size: .88rem; color: var(--ink); }
.journey-goal-status { font-weight: 750; margin-top: 2px; }
.journey-goal-status.on-track { color: var(--ok); }
.journey-goal-status.behind { color: var(--rose); }

/* ══════════════════════════════════════════════
   MAIN WORKSPACE
═══════════════════════════════════════════════ */
.main {
  min-width: 0;
  display: grid;
  grid-template-rows: minmax(0, 1fr) auto;
  gap: 18px;
}

.workspace {
  position: relative;
  border-radius: 8px;
  padding: 16px;
  display: grid;
  grid-template-rows: auto minmax(0, 1fr);
  gap: 12px;
  min-height: 0;
}

.topbar {
  display: flex;
  align-items: center;
  justify-content: space-between;
  flex-wrap: wrap;
  gap: 12px;
}
.topbar h2 { font-size: 1.05rem; }

.actions { display: flex; flex-wrap: wrap; gap: 8px; justify-content: flex-end; align-items: center; }
.actions .tool-btn { width: auto; padding: 0 14px; min-height: 44px; }

.zoom-group {
  display: flex;
  align-items: center;
  gap: 4px;
  border: 1px solid rgba(var(--accent-rgb), .16);
  border-radius: 8px;
  overflow: hidden;
}
.zoom-group .zoom-btn {
  width: 40px;
  min-width: 40px;
  min-height: 44px;
  border: none;
  border-radius: 0;
  font-size: 1.1rem;
}
.zoom-value {
  min-width: 46px;
  text-align: center;
  font-size: .82rem;
  font-weight: 700;
  color: var(--muted);
}

/* ── the writing paper ── */
/* ── art plate + paper: always stacked, painting first, full width, at every size —
   no side column, no sticky. A painting on every verse, directly above the paper it
   belongs to. ── */
.paper-row {
  display: flex;
  flex-direction: column;
  gap: 16px;
  min-height: 0;
}
.paper-row.no-plate .art-plate-frame { display: none; }

/* The paper and the two quiet lines under it. On a wide viewport this is an ordinary container:
   the paper keeps its own box and the ONE document scrollbar carries the passage exactly as
   before. On a narrow viewport it becomes the paper's OWN scroll region (see the reserved writing
   screen at the end of this file), so a long passage scrolls inside the paper while the reserved
   preview stays where it is instead of being carried off the top of the screen. JOB-20261015321. */
.paper-scroll {
  display: flex;
  flex-direction: column;
  gap: 16px;
  min-width: 0;
}

/* Paintings are NOT all one shape, though this collection runs mostly
   wide (roughly 1.85:1 landscape). The frame hugs each painting's OWN real aspect
   ratio (app.js sets --plate-aspect from the loaded image's naturalWidth/naturalHeight)
   so object-fit: contain below fills it edge to edge with no crop AND no letterboxed
   cream bands. 1.85 is only the fallback before an image has loaded once.

   JOB-006, item 8 (work126's device runs): a PORTRAIT painting used to push the writing
   guide off the screen — on a phone the guide began at y 812 of 852, and on a 1440x900
   desktop the plate ran 151..837 with the canvas at 855. The frame is therefore capped at
   about half the viewport (min(52vh, 560px); 46vh on phones, below) and centred on the
   paper colour. The whole painting still shows: object-fit: contain never crops, it just
   leaves paper-coloured room on the sides of a portrait painting instead of showing cream
   bands. Tapping the plate still opens the full painting. */
.art-plate-frame {
  position: relative;
  display: block;
  width: 100%;
  min-height: 0;
  max-height: min(52vh, 560px);
  margin-inline: auto;
  padding: 0;
  border: 1px solid var(--line);
  border-radius: 14px;
  overflow: hidden;
  background: var(--paper-deep);
  cursor: pointer;
  aspect-ratio: var(--plate-aspect, 1.85);
}
.art-plate-img {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: contain; /* the WHOLE painting, never cropped */
  object-position: center;
  opacity: 0;
  transition: opacity 400ms ease;
}
.art-plate-img.active { opacity: 1; }
@media (prefers-reduced-motion: reduce) {
  .art-plate-img { transition: none; }
}

.page {
  position: relative;
  border-radius: 8px;
  background:
    linear-gradient(90deg, transparent 0 48px, rgba(var(--gold-rgb), .22) 49px, transparent 50px),
    repeating-linear-gradient(0deg, transparent 0 66px, rgba(var(--rule-rgb), .19) 67px, transparent 68px),
    var(--paper);
  border: 1px solid var(--line);
  min-height: 38vh;
  /* No max-height, no inner scroll, at any size. app.js's positionTranslationChip grows
     #page's own min-height to fit the guide + glosses + translation chip (see that
     function's comment) — on a long verse (2:255 is ~3.8x a viewport tall) that just
     makes a tall paper, and the ONE document scrollbar carries the reader through it,
     the art plate included, instead of a scrollbar nested inside the paper card. */
  overflow: visible;
}
@media (min-width: 700px) {
  .page { min-height: 45vh; }
}

.trace-text { position: absolute; inset: 0; pointer-events: none; }
/* The passage translation uses the paper's own available width. It must not be centred with
   left: 50% + a negative horizontal transform on an auto-width box: that can measure a
   half-width box before moving it, which is exactly the defect the layout template names
   (artwork and paper shared a 719 px column, but the box used only the remaining half). The
   verified correction is explicit 16 px insets each side, no fixed cap, auto width and no
   centring transform — the same column as the painting and the paper. JOB-007, Step 2. */
.translation-chip {
  position: absolute;
  left: 16px;
  right: 16px;
  width: auto;
  max-width: none;
  direction: ltr;
  text-align: center;
  color: rgba(var(--ink-rgb), .62);
  font-size: .98rem;
  line-height: 1.5;
  background: rgba(var(--panel-rgb), .8);
  padding: 10px 14px;
  border-radius: 8px;
  border: 1px solid rgba(var(--ink-rgb), .08);
}

/* Finger-mode hint (JOB-007, Step 4): one quiet line under the paper, in the same single content
   column. It is a sibling of .page inside .paper-row, so it can never cover the guide or the ink.
   app.js hides it in stylus-only mode, where a finger never writes. */
.canvas-hint {
  margin: 0;
  padding: 0 4px;
  color: var(--muted);
  font-size: .8rem;
  line-height: 1.45;
  text-align: center;
}
.canvas-hint[hidden] { display: none; }

/* Missing meaning layer (JOB-007, Step 6): when the language the reader picked ships no verified
   word glosses for this passage, the guide draws no meaning row and this one plain line says so.
   It is the sibling of the finger-mode hint in the same single content column, so it can never
   cover the guide or the ink. */
.word-help-notice {
  margin: 0;
  padding: 0 4px;
  color: var(--muted);
  font-size: .8rem;
  line-height: 1.45;
  text-align: center;
}
.word-help-notice[hidden] { display: none; }

#writerCanvas {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  touch-action: none;
  cursor: crosshair;
}

.rise-overlay {
  position: absolute;
  inset: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  text-align: center;
  padding: 20px;
  pointer-events: none;
  font-family: var(--text-font);
  font-size: clamp(1.5rem, 5vw, 2.6rem);
  color: var(--gold);
  text-shadow: 0 0 18px rgba(var(--gold-rgb), .35);
  opacity: 0;
  background: rgba(var(--paper-rgb), .65);
}
.rise-overlay.rising { animation: riseFade 2.2s ease forwards; }
@keyframes riseFade {
  0%   { opacity: 0; transform: translateY(18px) scale(0.97); }
  16%  { opacity: 1; transform: translateY(0) scale(1); }
  72%  { opacity: 0.85; transform: translateY(-22px) scale(1.01); }
  100% { opacity: 0; transform: translateY(-70px) scale(1.02); }
}
@media (prefers-reduced-motion: reduce) {
  .rise-overlay.rising { animation: riseFadeReduced 0.6s ease forwards; }
}
@keyframes riseFadeReduced {
  0% { opacity: 0; }
  40% { opacity: 1; }
  100% { opacity: 0; }
}

/* ── art interlude — a painting between verses, illuminated-manuscript style ── */
.art-interlude {
  position: absolute;
  inset: 0;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 16px;
  padding: 28px 24px;
  background: var(--paper);
  opacity: 0;
  transition: opacity 600ms ease;
  pointer-events: none;
}
.art-interlude.visible { opacity: 1; pointer-events: all; }
/* Paintings in this collection run wide (roughly 1.85:1 landscape) rather than
   portrait plates — app.js sets --art-aspect from the loaded image's own
   naturalWidth/naturalHeight (same pattern as .art-plate-frame's --plate-aspect
   below), so a landscape painting gets a landscape frame instead of a tall portrait
   box with a strip of image and a lot of empty cream underneath it. 1.85 is only the
   fallback before an image has loaded once; max-width widened to match. */
.art-frame {
  display: block;
  width: 100%;
  max-width: 640px;
  aspect-ratio: var(--art-aspect, 1.85);
  border: 1px solid var(--line);
  border-radius: 8px;
  overflow: hidden;
  background: var(--paper-deep);
  padding: 0;
  cursor: pointer;
}
.art-image { width: 100%; height: 100%; object-fit: contain; display: block; }
.art-continue { width: auto; padding: 0 24px; }
@media (prefers-reduced-motion: reduce) {
  .art-interlude { transition: none; }
}

/* ── stats row ── */
.completion-footer { display: flex; justify-content: flex-end; }
.completion-footer .tool-btn { width: auto; min-height: 48px; padding: 0 24px; }
@media (max-width: 699px) { .completion-footer .tool-btn { width: 100%; } }
.bottom-panel {
  border-radius: 8px;
  padding: 14px;
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: 12px;
}
.stat { padding: 12px; border-radius: 8px; background: rgba(var(--panel-rgb), .74); border: 1px solid rgba(var(--accent-rgb), .1); }
.stat strong { display: block; font-size: 1.35rem; margin-bottom: 2px; }
.stat span { color: var(--muted); font-size: .86rem; }

@media (max-width: 920px) {
  .app { grid-template-columns: 1fr; padding: 10px; }
  .sidebar { order: 2; }
  .journey-screen { order: 2; }
  .workspace { min-height: 66vh; }
  .bottom-panel { grid-template-columns: 1fr; }
}

@media (max-width: 560px) {
  .topbar { align-items: stretch; flex-direction: column; }
  .actions { justify-content: stretch; }
  .actions .tool-btn { flex: 1 1 100px; }
  /* JOB-006, item 8: on a phone the plate is capped tighter still (46vh) so the first guide
     line is on screen without a swipe — see the .art-plate-frame comment above. */
  .art-plate-frame { max-height: 46vh; }
}

/* ══════════════════════════════════════════════
   JOURNEY SCREEN
═══════════════════════════════════════════════ */
.journey-screen { min-width: 0; grid-column: 1 / -1; }
.journey-panel {
  border-radius: 8px;
  padding: 18px;
  display: flex;
  flex-direction: column;
  gap: 16px;
}
.journey-header { display: flex; align-items: center; gap: 12px; }
.journey-header .tool-btn { width: auto; padding: 0 14px; }
.journey-title { font-size: 1.2rem; font-weight: 800; }

.journey-stats {
  display: flex;
  flex-wrap: wrap;
  gap: 4px 18px;
  font-size: .88rem;
  color: var(--muted);
}
.journey-day { font-weight: 800; font-size: 1.05rem; color: var(--ink); flex-basis: 100%; }
.journey-streak { color: var(--teal); font-weight: 700; }

.journey-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(96px, 1fr));
  gap: 10px;
}
.journey-tile {
  position: relative;
  aspect-ratio: 1 / 1;
  min-height: 44px;
  border-radius: 10px;
  overflow: hidden;
  border: 1px solid rgba(var(--ink-rgb), .12);
  background-color: var(--paper-deep);
  background-size: cover;
  background-position: center;
  cursor: pointer;
  display: flex;
  flex-direction: column;
  justify-content: flex-end;
  padding: 8px;
  color: var(--ink);
  text-align: left;
  transition: border-color .2s, box-shadow .2s;
}
.journey-tile:hover { border-color: var(--teal); }
.journey-tile::before {
  content: '';
  position: absolute;
  inset: 0;
  background: rgba(var(--paper-rgb), var(--tile-overlay, .88));
  transition: background .3s;
}
.journey-tile.is-complete { border-color: var(--gold); box-shadow: 0 0 0 2px rgba(var(--gold-rgb), .35); }
.journey-tile-num, .journey-tile-name, .journey-tile-pct { position: relative; z-index: 1; }
.journey-tile-num { font-weight: 800; font-size: .72rem; color: var(--teal); }
.journey-tile-name { display: block; font-size: .7rem; font-weight: 700; line-height: 1.2; margin-top: 2px; }
.journey-tile-pct { display: block; font-size: .62rem; color: var(--muted); margin-top: 2px; }

/* ══════════════════════════════════════════════
   SHEETS — chapter picker / about / membership / account
═══════════════════════════════════════════════ */
.sheet-overlay {
  position: fixed;
  inset: 0;
  z-index: 200;
  background: rgba(var(--ink-rgb), .35);
  display: flex;
  align-items: flex-end;
  justify-content: center;
}
.sheet {
  background: var(--panel);
  color: var(--ink);
  width: 100%;
  max-width: 480px;
  max-height: 80vh;
  border-radius: 8px 8px 0 0;
  overflow: hidden;
  display: flex;
  flex-direction: column;
}
@media (min-width: 700px) {
  .sheet-overlay { align-items: center; }
  .sheet { border-radius: 8px; max-height: 70vh; }
}
.sheet-header { display: flex; align-items: center; gap: 10px; padding: 14px 16px; border-bottom: 1px solid rgba(var(--ink-rgb), .08); }
.sheet-header .tool-btn { width: 40px; min-height: 40px; padding: 0; flex-shrink: 0; }
.sheet-title { font-size: 1.1rem; font-weight: 800; flex: 1; }
.sheet-search {
  flex: 1;
  min-height: 44px;
  padding: 0 14px;
  border-radius: 8px;
  border: 1px solid rgba(var(--accent-rgb), .18);
  font-size: .95rem;
  color: var(--ink);
  background: #fff;
}
.sheet-body { padding: 16px 20px 22px; font-size: .92rem; line-height: 1.65; color: var(--ink); overflow-y: auto; }
.sheet-body p { margin: 0 0 14px; }
.sheet-attribution { font-size: .78rem; color: var(--muted); }

.chapter-list { overflow-y: auto; padding: 4px 10px 16px; }
.chapter-row {
  display: flex;
  align-items: center;
  gap: 12px;
  width: 100%;
  min-height: 56px;
  padding: 8px 10px;
  border: none;
  background: transparent;
  border-radius: 8px;
  cursor: pointer;
  text-align: left;
}
.chapter-row:hover { background: var(--teal-soft); }
.chapter-row-num {
  width: 30px; height: 30px;
  border-radius: 50%;
  background: var(--teal-soft);
  color: var(--teal);
  display: flex; align-items: center; justify-content: center;
  font-weight: 800;
  font-size: .8rem;
  flex-shrink: 0;
}
.chapter-row-names { display: flex; flex-direction: column; flex: 1; min-width: 0; }
.chapter-row-en { font-weight: 800; font-size: .95rem; }
.chapter-row-meaning { font-size: .78rem; color: var(--muted); }
.chapter-row-sa { font-family: var(--text-font); font-size: 1.05rem; color: var(--ink); flex-shrink: 0; }

/* journey-west-launch-behavior-v1: chapter titles wrap without enlarging the sheet or focus viewport. */
#chapterSheet { min-width: 0; max-width: min(480px, 100%); }
#chapterSheet .sheet-header { min-width: 0; }
#chapterSearch { width: 0; min-width: 0; font-size: 16px; }
#chapterPickerStatus { margin: 8px 16px; overflow-wrap: anywhere; }
#chapterList { min-width: 0; }
#chapterList .chapter-row {
  display: grid;
  grid-template-columns: 30px minmax(0, 1fr);
  gap: 4px 12px;
  align-items: start;
  color: var(--ink);
}
#chapterList .chapter-row-num { grid-column: 1; grid-row: 1 / span 2; }
#chapterList .chapter-row-names { grid-column: 2; min-width: 0; overflow-wrap: anywhere; }
#chapterList .chapter-row-sa {
  grid-column: 2;
  min-width: 0;
  white-space: normal;
  overflow-wrap: anywhere;
  text-align: start;
}

/* ── sign-in / account forms ── */
.field-label { display: block; font-size: .8rem; color: var(--muted); margin: 14px 0 6px; }
.field-input { display: block; width: 100%; margin-bottom: 0; }
.signin-pane { margin-top: 10px; padding-top: 14px; border-top: 1px solid rgba(var(--ink-rgb), .08); }
.google-btn-container { margin: 10px 0; min-height: 40px; display: flex; justify-content: center; }
.code-entry-row { margin-top: 4px; }
.form-error { color: var(--rose); font-size: .82rem; margin-top: 10px !important; }
.form-status { color: var(--teal); font-size: .82rem; margin-top: 10px !important; }
.account-email { font-weight: 800; font-size: 1.02rem; margin-bottom: 2px !important; }
.account-status { color: var(--muted); font-size: .88rem; }

/* ══════════════════════════════════════════════
   WORD TOOLTIP — hover only (pen/mouse, never a press, never touch — see
   canvas-writer.js's pointermove handler). Two lines: pronunciation, then the
   English meaning. pointer-events: none so it can never itself intercept the
   press that's meant for the paper underneath it.
═══════════════════════════════════════════════ */
.word-tooltip {
  box-sizing: border-box;
  overflow: auto;
  position: fixed;
  z-index: 300;
  background: rgba(var(--paper-rgb), .76);
  border: 1px solid rgba(var(--muted-rgb), .16);
  border-radius: 8px;
  padding: 7px 12px;
  box-shadow: 0 4px 16px rgba(var(--ink-rgb), .08);
  max-width: 220px;
  text-align: center;
  pointer-events: none;
}
/* Dark lettering stays readable over the translucent paper-coloured card. */
.word-tooltip-tl { font-style: italic; font-weight: 700; font-size: .88rem; color: var(--muted); }
.word-tooltip-en { font-size: .8rem; color: var(--ink); margin-top: 2px; }
.word-tooltip-sa { font-family: var(--text-font); font-size: 1.25rem; color: var(--ink); line-height: 1.35; }
.word-tooltip-row { margin-top: 3px; }
.word-tooltip-label { display: block; font-size: .62rem; letter-spacing: .06em; text-transform: uppercase; color: var(--muted); }
.word-tooltip-hi { font-family: var(--gloss-font); font-size: .92rem; color: var(--ink); }
.word-tooltip-hi-tl { font-style: italic; font-size: .8rem; color: var(--muted); }
.word-tooltip-letters { display: flex; flex-wrap: wrap; justify-content: center; gap: 4px 8px; margin-top: 6px; padding-top: 6px; border-top: 1px solid rgba(var(--muted-rgb), .18); }
.word-tooltip-letter { display: inline-flex; flex-direction: column; align-items: center; min-width: 26px; }
.word-tooltip-letter .l-sa { font-family: var(--text-font); font-size: 1.05rem; color: var(--ink); line-height: 1.2; }
.word-tooltip-letter .l-tl { font-size: .68rem; color: var(--muted); font-style: italic; }
.word-tooltip-letter .l-parts { font-size: .6rem; color: var(--muted); }
.word-tooltip.rich { max-width: 320px; text-align: center; }
.word-tooltip * { pointer-events: none; }
.word-tooltip:focus-visible { outline: 2px solid var(--gold); outline-offset: 2px; }
.check-row { display: flex; align-items: center; gap: 8px; font-size: .86rem; color: var(--ink); cursor: pointer; }
.check-row input { accent-color: var(--teal); width: 16px; height: 16px; margin: 0; }
.control-label { font-size: .8rem; font-weight: 600; color: var(--muted); }


/* ══════════════════════════════════════════════
   ART PLATE LIGHTBOX — light background, tap anywhere to close, no caption
═══════════════════════════════════════════════ */
.lightbox-overlay {
  position: fixed;
  inset: 0;
  z-index: 400;
  background: var(--paper);
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 0;
  cursor: zoom-out;
}
.lightbox-image {
  width: 100vw;
  height: 100vh;
  height: 100dvh;
  max-width: none;
  max-height: none;
  object-fit: contain;
  border-radius: 0;
}
.lightbox-close {
  position: absolute;
  top: max(12px, env(safe-area-inset-top));
  right: max(12px, env(safe-area-inset-right));
  min-height: 44px;
  padding: 8px 18px;
  border: 1px solid var(--teal);
  border-radius: 999px;
  background: var(--paper);
  color: var(--teal);
  font: inherit;
  font-weight: 700;
  cursor: pointer;
}
.lightbox-close:focus-visible {
  outline: 3px solid var(--teal);
  outline-offset: 3px;
}

/* ══════════════════════════════════════════════
   WELCOME — the WHOLE painting is shown as the backdrop (object-fit: contain, centred,
   at every size), letterboxed against the paper, never cropped: PLAN.md's rule is that
   painted borders stay on the art, and cover was cutting them off on every viewport
   whose shape differs from the painting's. The title/subtitle/button sit in a compact
   white panel over its lower third, never as a small banner strip above the text.
═══════════════════════════════════════════════ */
.welcome-overlay {
  position: fixed;
  inset: 0;
  z-index: 500;
  overflow: hidden;
  background: var(--paper-deep); /* the letterbox bands, and the instant before decode */
}
.welcome-bg {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: contain;
  object-position: center;
}
.welcome-card {
  position: absolute;
  inset: auto 0 0 0;
  display: flex;
  justify-content: center;
  padding: 24px 20px calc(24px + env(safe-area-inset-bottom, 0px));
}
.welcome-content {
  width: 100%;
  max-width: 440px;
  background: var(--panel);
  border-radius: 14px;
  box-shadow: var(--shadow);
  padding: 26px 28px 30px;
  text-align: center;
}
.welcome-title {
  font-family: var(--text-font);
  font-size: clamp(1.8rem, 8vw, 2.6rem);
  color: var(--teal);
  margin: 0 0 4px;
}
.welcome-title-en { font-size: 1.3rem; font-weight: 800; margin: 0 0 14px; }
.welcome-line { color: var(--muted); font-size: .95rem; line-height: 1.55; margin: 0 0 22px; }
/* The one gesture sentence (JOB-007, Step 4) reads as the instruction it is: its own line, a
   touch tighter to the button, still quiet. */
.welcome-gesture { margin: 0 0 18px; border-left: 3px solid rgba(var(--gold-rgb), .5); padding-left: 10px; }
.welcome-content .tool-btn { min-height: 48px; }

/* Phone: the panel runs edge to edge along the bottom instead of floating as a
   centred card with side margins. */
@media (max-width: 700px) {
  .welcome-card { padding: 0; }
  .welcome-content { max-width: none; border-radius: 14px 14px 0 0; }
}

/* ══════════════════════════════════════════════
   SOUNDS — music playlist + ambience loops, sidebar panel (see js/sounds.js). Built
   entirely by JS into #soundsPanel; empty and invisible until playlist.json has
   content, so there is nothing here to hide/show by hand. Reuses .group /
   .group-title / .tool-btn / the global range-input styling above.
═══════════════════════════════════════════════ */
.sounds-subtitle { margin-top: -4px !important; }
.sound-music-row { display: flex; gap: 8px; }
.sound-music-row .sound-toggle { flex: 1; }
.sound-next-btn { flex: 0 0 auto; width: auto; padding: 0 14px; }
.sound-track-title {
  color: var(--muted);
  font-size: .82rem;
  margin-top: -4px !important;
  min-height: 1.2em; /* stable height whether or not a title is showing yet */
}
.sound-ambience-list { display: grid; gap: 8px; }
.sound-ambience-row { display: flex; align-items: center; gap: 10px; }
.sound-ambience-row .sound-toggle { flex: 0 0 auto; width: auto; padding: 0 12px; }
.sound-ambience-row input[type="range"] { flex: 1; }
.sound-icon { display: inline-flex; color: inherit; }
.tool-btn.sound-toggle[aria-pressed="true"] {
  background: var(--teal-soft);
  border-color: var(--teal);
  color: var(--teal);
}

/* ══════════════════════════════════════════════
   REDUCED MOTION
═══════════════════════════════════════════════ */
@media (prefers-reduced-motion: reduce) {
  .journey-tile, .tool-btn, .meter span { transition: none; }
}

/* Compact, accessible shared painting reactions. */
.art-reactions { display:flex; align-items:center; justify-content:center; gap:8px; flex-wrap:wrap; padding:8px 12px; }
.art-reactions[hidden] { display:none; }
.art-reaction { display:inline-flex; align-items:center; justify-content:center; gap:8px; min-height:44px; min-width:64px; padding:8px 12px; border:1px solid var(--line); border-radius:16px; background:var(--paper); color:var(--ink); font:inherit; cursor:pointer; }
.art-reaction[aria-pressed="true"] { color:var(--teal); border-color:var(--teal); background:var(--paper-deep); }
.art-reaction:focus-visible { outline:2px solid var(--teal); outline-offset:3px; }
.art-reaction:disabled { cursor:wait; opacity:.6; }
.reaction-count { font-variant-numeric:tabular-nums; }
.reaction-status { flex-basis:100%; text-align:center; font-size:12px; color:var(--muted); }

/* ── Finger writing loupe (JOB-20261012397) ──────────────────────────────────────
   The small magnified view of the writing surface raised above the finger while a
   touch stroke is being drawn. js/finger-loupe.js paints the real crop and the exact
   point's crosshair and sets left/top each frame; this is only shape and chrome.
   pointer-events:none makes it a view, never an input: it can never capture, block or
   move a stroke, and it sits above the word-help card so the finger can read the ink
   without covering the help. */
.finger-loupe {
  position: fixed;
  left: 0;
  top: 0;
  z-index: 360;
  pointer-events: none;
  overflow: hidden;
  border-radius: 10px;
  border: 1px solid rgba(0, 0, 0, .32);
  box-shadow: 0 6px 20px rgba(0, 0, 0, .28);
  background: var(--paper, #ffffff);
}
.finger-loupe[hidden] { display: none; }
.finger-loupe-surface { display: block; width: 100%; height: 100%; }
/* ══════════════════════════════════════════════════════════════════════════════════════════════
   THE RESERVED PREVIEW + WORD INFORMATION PANE — JOB-20261015316
   ══════════════════════════════════════════════════════════════════════════════════════════════
   The owner's iPad recording showed the magnifier sitting ON the calligraphy and the word card
   crowding the paper above it. Both are gone: this pane is RESERVED layout, part of the page
   before the first stroke. On a narrow screen it is the compact strip between the painting and
   the paper; from 1100px it is the second column beside them, sticky so it stays in view while
   the passage scrolls. Nothing here is fixed, nothing here is positioned over the paper, and
   nothing here takes a pointer event away from the canvas. Both blocks keep a FIXED height, so
   raising the preview or opening word help can never resize the pane and shift the paper out
   from under the writer's hand mid-stroke. Overflow scrolls inside the pane instead. */
.writing-side {
  display: grid;
  grid-template-columns: 104px minmax(0, 1fr);
  gap: 12px;
  align-items: start;
  min-width: 0;
  padding: 8px 10px;
  border: 1px solid var(--line);
  border-radius: 10px;
  background: var(--panel);
}
.writing-preview {
  display: grid;
  gap: 5px;
  min-width: 0;
}
.writing-preview-head {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 6px;
}
.writing-preview-label {
  font-size: .68rem;
  font-weight: 800;
  letter-spacing: .07em;
  text-transform: uppercase;
  color: var(--ink);
}
.writing-preview-state { text-align: right; font-size: .68rem; font-weight: 700; color: var(--teal); }
.writing-preview[data-state="final"] .writing-preview-state { color: var(--gold); }
/* The frame is what makes the crop unmistakable: a plain, distinct border around a real crop of
   the paper. Nothing decorative, nothing that could be read as the paper itself. */
.writing-preview-frame {
  position: relative;
  width: 100%;
  aspect-ratio: 1 / 1;
  border: 2px solid var(--ink);
  border-radius: 8px;
  background: var(--paper);
}
.writing-preview-surface { display: block; width: 100%; height: 100%; pointer-events: none; }
.writing-preview-empty {
  margin: 0;
  font-size: .66rem;
  line-height: 1.25;
  color: var(--muted);
}
/* The empty-state line never leaves the layout (it only stops being visible). The pane is real
   layout, so an element that disappeared would resize the strip above the paper. */
.writing-preview-empty[hidden] { display: block !important; visibility: hidden; }
.writing-preview-note { display: none; margin: 0; font-size: .66rem; line-height: 1.3; color: var(--muted); }
.writing-help {
  display: grid;
  grid-template-rows: auto minmax(0, 1fr);
  gap: 5px;
  min-width: 0;
}
.writing-help-title {
  margin: 0;
  font-size: .68rem;
  font-weight: 800;
  letter-spacing: .07em;
  text-transform: uppercase;
  color: var(--ink);
}
/* Fixed height + inner scroll: the reserved strip can never grow when a long meaning arrives. */
.writing-help-body {
  display: grid;
  grid-template-columns: minmax(0, 1fr);
  align-content: start;
  min-width: 0;
  height: 96px;
  overflow-x: hidden;
  overflow-y: auto;
}
.writing-help-hint, .word-tooltip { grid-area: 1 / 1; }
.writing-help-hint { margin: 0; font-size: .72rem; line-height: 1.3; color: var(--muted); }
.writing-help-hint[hidden] { display: none; }
/* The word card is DOCKED here now: it used to be position:fixed and placed above the pointer,
   which is what put it over the guide. Every placement property is reset, so no rule left over
   from the floating version can move it or clip it. */
.word-tooltip {
  position: static;
  inset: auto;
  z-index: auto;
  width: 100%;
  max-width: none;
  max-height: none;
  overflow: visible;
  padding: 0;
  border: 0;
  border-radius: 0;
  background: none;
  box-shadow: none;
}
/* The "rich" card used to cap itself at 320px because it had to fit beside a pointer; docked in
   the pane it simply fills the reserved help area, which is already bounded. */
.word-tooltip.rich {
  max-width: none;
}
@media (min-width: 1100px) {
  .paper-row {
    display: grid;
    grid-template-columns: minmax(0, 1fr) 300px;
    gap: 16px;
    align-items: start;
  }
  /* The writing viewport is transparent on a wide screen: its children are this grid's own items
     again (display: contents), so the approved wide layout — the painting and the paper in the left
     column, the reserved preview/word strip sticky in the 300px right column — is exactly what it
     was before the wrapper existed. JOB-20261015336. */
  .paper-row > .writing-viewport { display: contents; }
  .paper-row > .art-plate-frame,
  .paper-row > #artPlateReactions,
  .paper-row > .writing-viewport > .paper-clip { grid-column: 1; }
  .paper-row > .writing-viewport > .writing-side {
    grid-column: 2;
    grid-row: 1 / span 2;
    position: sticky;
    top: 16px;
    grid-template-columns: minmax(0, 1fr);
    padding: 10px;
  }
  .writing-preview-frame { max-width: 168px; margin: 0 auto; }
  .writing-preview-note { display: block; }
  .writing-help-body { height: 190px; }
}

/* ══════════════════════════════════════════════════════════════════════════════════════════════
   THE WRITING VIEWPORT — the reserved preview strip stays, the paper is clipped, the document
   still scrolls. JOB-20261015336 (shape read READ ONLY from the accepted Gita333 source)
   ══════════════════════════════════════════════════════════════════════════════════════════════
   Root rejected the previous repair (JOB-20261015325): it made the geometry pass by taking this
   app's own tail (#sidebar, .bottom-panel) out of the narrow flow, which hid Account, Journey,
   About, the ink / writing-mode / progress controls and the session stats on a phone. That rule is
   gone: every one of those controls is back in the narrow document with its own real handler, and it
   is the reservation below — not a hidden panel — that keeps the reserved preview visible whenever
   the paper can still take a stroke.

   On a narrow screen:
     * .paper-row keeps its ordinary document flow (the painting, then the writing viewport);
     * .writing-viewport is a NORMAL-FLOW wrapper around the real strip, the clipped paper view and
       the reserved space, so nothing of this app's flow leaves the document;
     * .writing-side (the real reserved preview + word-information strip) is the ONE sticky item,
       pinned below the system safe top inset;
     * .paper-clip is sticky directly below it and clips the paper to the window js/app.js publishes
       (--preview-paper-window), so a writing row can never be painted over by the strip and no paper
       is writable outside the visible window;
     * .paper-scroll is the view js/app.js offsets from the document scroll, so the document stays the
       ONLY scroll authority: the wheel, the trackpad, the scrollbar, a touch outside the paper and
       this app's own two-finger pan all move that one scrollbar and the clip follows it;
     * .writing-scroll-space gives the pinned strip's footprint back to the document (js/app.js sizes
       it in step with the clip), so the document's own length — and with it every row's reachability
       — never changes while the clip closes.
   On a wide screen nothing here applies: .writing-viewport is display: contents and the paper is an
   ordinary block again.
   ══════════════════════════════════════════════════════════════════════════════════════════════ */
:root {
  /* THE SYSTEM SAFE AREA the pinned strip and the paper window must clear. A pinned
     ('position: sticky; top: ...') element is positioned against the scrollport, so it does not
     inherit the native shell's own body padding: --native-safe-* is that shell's channel
     (native.css publishes it for 'html.is-native-app' from the SystemBars plugin's
     --safe-area-inset-*, falling back to env(), 0px when neither exists) and the plain env() below
     it is the browser's own channel, which a hosted page with viewport-fit=cover also reports.
     max() of the two is the union of both honest measurements of ONE physical inset and errs larger
     — the only safe direction for a reservation whose failure mode is being painted under the system
     bars. Both are 0px on a desktop browser and in the headless suites, and js/app.js reads the chain
     back through a probe element, so the CSS 'top' values and the scroll arithmetic cannot drift. */
  --preview-safe-top: max(env(safe-area-inset-top, 0px), var(--native-safe-top, 0px));
  --preview-safe-right: max(env(safe-area-inset-right, 0px), var(--native-safe-right, 0px));
  --preview-safe-bottom: max(env(safe-area-inset-bottom, 0px), var(--native-safe-bottom, 0px));
  --preview-safe-left: max(env(safe-area-inset-left, 0px), var(--native-safe-left, 0px));
}

.writing-viewport { display: block; min-width: 0; }
.paper-clip { display: block; min-width: 0; }
.writing-scroll-space { display: none; }

@media (max-width: 1099.98px) {
  /* A normal-flow wrapper: the document keeps its own length and stays the only scroll authority;
     the two pinned items inside are what stay with the paper. */
  .writing-viewport {
    display: flex;
    flex-direction: column;
    gap: 16px;
    min-width: 0;
  }
  /* The real reserved preview/word-information strip is the ONE thing pinned, below the system safe
     top inset. It is sticky, not fixed, so it stays a reserved item of the passage's own column:
     while any paper below it is still visible and input-receiving it is on screen, and it leaves only
     once the last slice of paper has closed. Its own height is fixed by its blocks (the preview frame
     and the help body), so raising a crop or a meaning can never resize it and shift the paper. */
  .writing-viewport > .writing-side {
    position: sticky;
    top: var(--preview-safe-top, 0px);
    z-index: 2;
    flex: 0 0 auto;
  }
  /* The clipped view the paper is read in, pinned directly below the strip: its top is the safe inset
     the strip itself starts at PLUS the strip's own measured footprint (--preview-strip-offset,
     published by js/app.js), so the strip can never paint over a writing row and the paper can never
     start under the status bar. Its height is the part of the VISIBLE viewport left below the strip
     and above the bottom safe inset, and js/app.js closes that height with the document scroll as the
     reader leaves the passage. */
  .writing-viewport > .paper-clip {
    position: sticky;
    top: calc(var(--preview-safe-top, 0px) + var(--preview-strip-offset, 118px));
    z-index: 0;
    flex: 0 0 auto;
    min-height: 0;
    height: var(--preview-paper-window, 100dvh);
    overflow: hidden;
    overscroll-behavior: contain;
  }
  .paper-clip > .paper-scroll {
    height: var(--preview-paper-window, 100dvh);
    overflow: hidden;
    overscroll-behavior: contain;
    /* The document scroll moves this view; it must move the moment it is asked to. */
    scroll-behavior: auto;
  }
  .writing-scroll-space { display: block; min-width: 0; }
  /* The strip's own height must not depend on the preview's state line. The panel is RESERVED layout:
     a state change that resized the strip would move the pinned clip (and the paper under the
     writer's hand) at exactly the moment the pen touches down. In the narrow strip the head therefore
     stacks — the label's own line, then the state's — exactly as the accepted Gita333 strip does, so
     every state ("No point yet", "Pen hover", "Pen contact", "Last point (pen)") is the same two lines
     tall and the strip's measured footprint is a constant. Measured on the current bytes before this
     rule: the strip was 216.61px idle and 201.61px on a contact, i.e. a real 15px on-contact jump. */
  .writing-viewport > .writing-side .writing-preview-head {
    flex-direction: column;
    align-items: flex-start;
    gap: 2px;
  }
  .writing-viewport > .writing-side .writing-preview-state { text-align: left; }
  /* The app's own controls always paint over the passive preview. */
  .topbar { position: relative; z-index: 360; }
}

/* ── JOB-20261016409 — the completion painting fits the real visible viewport ────────────────────
   Root's decision: the painting and its Continue must fit the actual visible viewport at every
   tested width and orientation, with the whole painting visible and undistorted. Two tiers of the
   SAME block, so nothing about the painting, the Continue or the advance changes between them:
     * in-flow (default) — the interlude is bounded by the paper window this engine really has right
       now (--paper-window-now, published by js/app.js) instead of by the tall #page, and the frame is
       a shrinkable flex item (min-height:0) whose painting is object-fit:contain;
     * modal (.is-viewport-modal, set by js/app.js when that window is too short for a readable
       painting) — the same bounded column becomes a fixed, viewport-bounded presentation that clears
       the system safe areas, with the real Continue visible and focusable.
   Nothing here is cropped, stretched, hidden or moved off-screen: contain letterboxes instead, and
   the frame never grows past the space it was given. */
.art-interlude {
  /* JOB-20261016409: the box IS the slice of the tall page the reader can really see — the app
     publishes the visible window's own height and its offset inside #page, so the painting is centred
     in what is on screen instead of in a taller box that happens to start at #page's top. */
  top: var(--paper-window-top, 0px);
  left: 0;
  right: 0;
  bottom: auto;
  height: var(--paper-window-now, var(--preview-paper-window, 100%));
  min-height: 0;
  box-sizing: border-box;
  overflow: hidden;
}
.art-frame {
  flex: 0 1 auto;
  min-height: 0;
  width: auto;
  max-width: min(100%, 640px);
  max-height: 100%;
  aspect-ratio: var(--art-aspect, 1.85);
}
.art-image {
  width: 100%;
  height: 100%;
  max-width: 100%;
  max-height: 100%;
  object-fit: contain;
}
.art-interlude.is-viewport-modal {
  position: fixed;
  top: calc(var(--paper-modal-top, 0px) + var(--paper-modal-shift, 0px));
  left: var(--paper-modal-left, 0px);
  right: var(--paper-modal-right, 0px);
  bottom: auto;
  height: var(--paper-modal-height, 100vh);
  max-height: var(--paper-modal-height, 100vh);
  box-sizing: border-box;
  z-index: 400;
  padding: 16px;
}
.art-interlude.is-viewport-modal .art-frame { max-width: min(100%, 960px); }
.art-interlude.is-viewport-modal .art-continue { min-height: 48px; }
