@font-face {
  font-family: 'HadassahFriedlaender';
  font-weight: 400;
  src: url('fonts/HadassahFriedlaender-Regular-TRIAL.otf') format('opentype');
}
@font-face {
  font-family: 'HadassahFriedlaender';
  font-weight: 100;
  src: url('fonts/HadassahFriedlaender-Thin-TRIAL.otf') format('opentype');
}
/* Discordia (Naipe Foundry, licensed via Hafontia) — the DESKTOP title-block and
   hero-title face since 2026-09-27 (typeface harness pick). Regular only. */
@font-face {
  font-family: 'Discordia';
  font-weight: 400;
  src: url('fonts/Discordia-Regular.otf') format('opentype');
}

* { margin: 0; padding: 0; box-sizing: border-box; }

/* Present to screen readers, absent from the layout. Clipped to a 1px box
   rather than display:none/visibility:hidden — those two remove the element
   from the accessibility tree as well, which defeats the point. */
.a11y-only {
  position: absolute;
  width: 1px; height: 1px;
  margin: -1px; padding: 0; border: 0;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
}

/* Shared design tokens — the values repeated across the file: page background,
   title-card geometry, ink/grey colours, type stacks and the CSS-transition
   tempos. Group colours and one-off values stay literal. --bg is also read
   once by JS at load (P7_PAPER in page7.js, FOLD12_GHOST_FILL* in
   js/page8-9-scroll.js) so the canvas paper matches it. */
:root {
  --bg: #FDFCFF;        /* flat page background, per Figma's intro cover */
  /* .text-card width — 480px on desktop, but never wider than the viewport
     minus the .text-section gutters, so it fits a phone without overflowing.
     Still a DEFINITE width (min() resolves to one), which .text-card relies on. */
  --card-w: min(456px, 100vw - 48px); /* 456 = title-width harness pick 2026-09-27 (was 480, then 448); phones still get 100vw - 48px */
  /* vh, NOT dvh: on mobile vh is pinned to the LARGE viewport and never
     re-resolves, while dvh tracks the URL/bottom bar and moves everything it
     sizes each time the bar collapses — the exact instability it was once
     (wrongly) used to prevent. */
  --card-top: 4.4vh;   /* pinned title-card / page9-header top offset */
  /* Ink, greys and lines — the UI's recurring non-group colours. */
  --ink: #111;                      /* .section-title's ink; dark buttons, share/gate fills */
  --ink-soft: rgba(0, 0, 0, 0.81);  /* group labels, the scope button and its filled state */
  --ink-note: rgba(0, 0, 0, 0.85);  /* the legend note / מקרא body text */
  --muted-ui: #767676;              /* secondary grey: note titles, מקרא buttons, language switch */
  --line-light: #E1E1E1;            /* mobile: extreme pill fill/edge, מקרא row fill */
  /* Type stacks. */
  --font-body: 'Assistant', sans-serif;                        /* every body/UI line */
  --font-title: 'Discordia', serif;                            /* title blocks and the hero title */
  --font-ar: 'IBM Plex Sans Arabic', 'Assistant', sans-serif;  /* Arabic body/UI text */
  /* Motion — CSS-transition state flips only (JS-driven beats keep their own constants). */
  --ui-fade-ms: 180ms;                       /* small UI flips: hover colours, menu open/close */
  --tray-ms: .85s;                           /* @fold12 pill tray slide (and its visibility delay) */
  --tray-ease: cubic-bezier(0.22, 1, 0.36, 1); /* the tray slide's ease-out curve */
  --fade-ms: .35s;                           /* @fold12 title card's stick fade (frame, fill, subtitle) */
}

body {
  background: var(--bg);
}
/* NO text selection anywhere on the story pages (explicit instruction
   2026-10-05) — every element, with !important so no later rule switches it
   back on, plus no iOS long-press callout. The methodology pages
   (methodology.html and its en/ ar/ twins) never load this file, so they stay
   selectable. */
*, *::before, *::after {
  -webkit-user-select: none !important;
  user-select: none !important;
  -webkit-touch-callout: none;
}

/* The fold badge — the current @foldN as a bare number, small, top-left.
   Nothing but the digits: no prefix, no label, no control. `display: none` by
   default so the markup is inert until js/nav.js adds .is-visible or .is-dot
   (it only ever does so on a local host — see js/nav.js). */
.fold-number-badge {
  display: none;
  position: fixed;
  top: 12px;
  left: 12px;
  z-index: 1000;
  font: 600 13px var(--font-body);
  color: #fff;
  background: rgba(0, 0, 0, 0.6);
  padding: 2px 7px;
  border-radius: 4px;
  /* CLICKABLE ON BOTH BREAKPOINTS: the badge is the fold picker's handle
     everywhere, so it can no longer be `pointer-events: none`. It is a ~30×20
     chip pinned in the top-left corner — no canvas interaction lives there
     (the drag zone, the timeline and the legend are all further in) — and when
     dismissed it shrinks to the dot below, which is smaller still. */
  pointer-events: auto;
  cursor: pointer;
  -webkit-tap-highlight-color: transparent;
}

.fold-number-badge.is-visible {
  display: block;
}

/* DISMISSED — GONE, no dot. It used to leave a 7px chip to tap back, because a
   phone has no Ctrl+Shift+F; the way back now is the harness panel tab's own
   fold-badge switch (answered by _debug-bus.js, so it works with no harness
   loaded). The `is-dot` class is still set by js/nav.js and still carries the
   element's state — it just paints nothing.
   Removed — don't reintroduce: the white 7px dot (it was invisible on the
   near-white page anyway) and its padded tap target. */
.fold-number-badge.is-dot { display: none; }

/* ── The fold picker ────────────────────────────────────────────────────────
   Clicking/tapping the badge on EITHER breakpoint opens a list of all 16 folds
   and jumps to the one you pick (foldPickerInit, js/nav.js), plus a last row
   that dismisses the badge to its dot. It used to be mobile-only, on the
   grounds that Ctrl+Shift+F covered desktop — but the shortcut only toggles,
   it never navigates, so desktop had no way to jump to a fold at all. */
.fold-picker {
  display: none;
  position: fixed;
  top: 40px;
  left: 12px;
  z-index: 1001;          /* over the badge, which is 1000 */
  max-height: 70vh;
  overflow-y: auto;
  overscroll-behavior: contain;
  -webkit-overflow-scrolling: touch;
  /* Nearly opaque: at 0.88 the hero art read straight through the list and
     the Hebrew titles were unscannable over it. */
  background: rgba(0, 0, 0, 0.95);
  border-radius: 6px;
  padding: 4px 0;
  /* The list is a dev aid over live artwork, so it stays narrow and dark
     rather than trying to look like part of the page. A fixed box with no width
     shrink-to-fits, so without this cap the long Hebrew titles stretched it
     most of the way across a desktop viewport. */
  min-width: 200px;
  max-width: 340px;
}
@media (max-width: 600px) {
  /* Mobile keeps the width it always had — the viewport is the only cap that
     matters there. */
  .fold-picker { max-width: calc(100vw - 24px); }
}
.fold-picker.is-open {
  display: block;
}
.fold-picker-row {
  all: unset;
  display: flex;
  align-items: baseline;
  gap: 8px;
  width: 100%;
  box-sizing: border-box;
  padding: 7px 12px;
  font: 400 12px var(--font-body);
  color: rgba(255, 255, 255, 0.75);
  cursor: pointer;
  -webkit-tap-highlight-color: transparent;
}
.fold-picker-row.is-current {
  color: #fff;
  background: rgba(255, 255, 255, 0.14);
}
.fold-picker-n {
  flex: 0 0 auto;
  min-width: 16px;
  text-align: end;
  font-weight: 600;
  font-variant-numeric: tabular-nums;
}
.fold-picker-t {
  /* One line per fold — the list is for finding a fold, not for reading its
     copy, and a wrapping title would make 16 rows unscannable. The names are
     the latin ones we use for the folds in conversation (FOLD_NAMES in
     js/nav.js), so this cell reads LTR while the panel around it stays neutral. */
  flex: 1 1 auto;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  direction: ltr;
  text-align: start;
}
/* The dismiss row, below a hairline so it never reads as a 17th fold. */
.fold-picker-hide {
  margin-top: 4px;
  border-top: 1px solid rgba(255, 255, 255, 0.16);
  padding-top: 8px;
  color: rgba(255, 255, 255, 0.5);
  justify-content: flex-start;
}
@media (hover: hover) {
  .fold-picker-row:hover { background: rgba(255, 255, 255, 0.09); color: #fff; }
}

/* .graphic-col is taken out of flow entirely (position: fixed), so it doesn't
   share width with .text-col — it's always the full viewport, genuinely
   centered. .text-col stays in normal flow (it alone drives the page's scroll
   height) and now spans the full viewport too — each page's .text-card centers
   itself within that full width (see .text-card), rather than .text-col itself
   being a narrow right-pinned sidebar (the old layout this project moved away
   from when text became a centered dashed card instead of a sidebar). */
.layout {
  position: relative;
}

.graphic-col {
  position: fixed;
  inset: 0;
  z-index: 0;
}

#canvas {
  display: block;
  width: 100%;
  height: 100%;
}

/* No z-index on purpose. As a positioned element it already paints above
   .graphic-col (z-index 0) on tree order, and leaving it z-index:auto means it
   does NOT open a stacking context — which is what lets .text-card's own lift
   to 4 count against the page's other overlays at all (the מקרא legend layer
   deliberately sits above it; the @fold4 flight's under-layer sits below).
   Giving this a z-index would trap every descendant's z-index inside it. */
.text-col {
  position: relative;
  width: 100%;
}

.text-section {
  position: relative;
  /* vh, not dvh — dvh made every section (and so every later fold's offsetTop)
     grow ~100px the moment a phone's URL/bottom bar collapsed, teleporting the
     reader backwards through folds 8–10 while scrollY stayed put. vh = lvh on
     mobile: fixed for the whole session. */
  min-height: 100vh;
  display: flex;
  align-items: center;
  padding: 0 48px;
  direction: rtl;
  text-align: right;
}
/* A fold parked with the `hidden` attribute (@hidden-acled, @hidden-hover for now) — the
   display:flex above would otherwise beat the UA's [hidden] rule. */
.text-section[hidden] { display: none; }

.text-section > .section-text {
  /* Without min-width:0, any unshrinkable (nowrap) descendant would push its
     min-content size up into this flex item, growing it past .text-card's own
     explicit width (set on the .text-card class itself) instead of letting that
     fixed width win. */
  min-width: 0;
}

.text-section.page7-scrub {
  /* The scrub range is this height (see page7UpdateFromScroll in
     js/page7-scrub.js) — t=1 lands when this section's BOTTOM leaves the top of
     the viewport, i.e. when @fold9's section takes the screen. Scrub range
     controls how much scrolling it takes to cross the whole dataset — bigger
     means slower movement through the months.
     780vh -> 730vh (2026-09-09): t used to hit 1 when the bottom reached the
     viewport BOTTOM, leaving a full viewport of scroll with the timeline
     already complete and nothing happening before @fold9's card. The endpoint
     moved half a viewport later (P7_SCRUB_END_LEAD_VH 0.5 — enough scroll left
     for the year axis's lagged fill to settle before the size grid takes over)
     and the section shortened by that same 50vh, so the scrub's pace is
     unchanged.
     730vh -> 560vh (manual/-baked 2026-09-19, DESKTOP — mobile has its own rule):
     a quicker run down the axis. Height must stay 510vh + P7_SCRUB_END_LEAD_VH * 100vh. */
  min-height: 560vh;
  display: block;
  padding: 0;
}


/* Desktop dead-scroll trims (teacher review 2026-09-03, G1/G2). Mobile keeps
   the house geometry — its own pass (G3) is separate.
   G1: @fold2's card is pulled up from its centered resting spot so it enters
   the viewport while the hero title is still leaving, instead of ~40vh of
   empty text column after the hero — AND #page-1 is shortened by the same
   amount so nothing after @fold2 shifts (fold2Trigger keeps the house 0.5
   crossing — the card's position alone closes the gap).

   Tried and REJECTED — don't reintroduce either: (a) pulling MORE cards up off
   centre (#page-2/#page-3) — a card's alignment inside its section doesn't
   change `section height - card height`, so it only relocates the void to the
   next centered fold; (b) shrinking the plain folds' min-height below 100vh —
   at 55vh two title cards sit on screen together, which reads far worse than
   the gap. One card alone on screen needs a gap of a full viewport, i.e.
   min-height >= 100vh + card height, so 100vh is already at the tight end.
   G2 (reverted): the bridge section (#page-12) was trimmed to 60vh so @fold12's
   card rose in while the bridge card left; that put both title blocks on
   screen together, so it is back at 100vh — see the #page-12 rule below. */
@media (min-width: 601px) {
  #page-1 {
    align-items: flex-start;
    padding-top: 10vh;
    /* The card sits ~33vh higher than centered would put it. Without this,
       that 33vh lands BELOW the card and @fold3's card arrives 33vh late.
       Shortening the section by the same amount keeps the @fold2→@fold3
       card-to-card distance identical to the house 100vh rhythm, so only
       @fold2's arrival changes. Derivation: card ≈ 13vh tall, centered top =
       (100 - 13) / 2 ≈ 43vh; 43 - 10 = 33; 100 - 33 = 67. */
    min-height: 67vh;
  }
  #page-10 {
    /* @fold9's card sits 16vh BELOW centre (centre 66vh, not 50vh) — a nudge
       down so the size grid starts a little later, after the year axis has
       finished filling. Same device as G1 in reverse: the section grows by the
       same 16vh so `section height - card offset` is unchanged and @fold11's
       card still arrives on the house 100vh rhythm. Keep the pair in step:
       padding-top p with min-height (100 + d)vh centres the card at
       (p + 100 + d) / 2, so d = 16 needs p = 16. */
    align-items: center;
    padding-top: 16vh;
    min-height: 116vh;
  }
  #page-12 {
    /* @fold11's card keeps the house rhythm on BOTH sides: 100vh from @fold9's
       centre to its own, and 100vh from its own to @fold12's. It can't get both
       from a plain 100vh section, because @fold12's card is not centred in its
       section — it centres 21vh into the desktop 50vh `.page9-title-row`, 29vh
       higher than the house 50vh. So this section is 25vh taller and the extra
       height is parked BELOW the card: `padding-bottom: 25vh` shrinks the
       centring box back to the first 100vh, so the card still centres at 50vh
       (100vh from @fold9) while #page-13 starts 125vh in (100vh to @fold12).
       Don't move the card off centre with `align-items`/`padding-top` — it used
       to be top-aligned at 48px, from when the 780vh scrub section was directly
       above it and there was no @fold9 card to fill that first half-viewport;
       with @fold9 in place that put the two title blocks 0.65vh apart. And
       don't trim the height (60vh was tried): both cards end up on screen. */
    min-height: 125vh;
    padding-bottom: 25vh;
  }
}



.text-section.page9-panel {
  /* 100vh title-row + 100vh sticky = 200vh content. min-height must be ≥ 200vh
     so that when .page9-sticky.frozen switches sticky→fixed (removing it from
     flow), min-height still dominates and the section height stays at 200vh.
     If min-height < 200vh, the section shrinks when frozen fires, shifting
     #page-14 up into the viewport and bypassing the gate. */
  min-height: 200vh;
  display: block;
  padding: 0;
}

/* Desktop dead-scroll trim inside @fold12. The panel (tray / zones / header,
   all position:fixed on desktop) engages the moment the TITLE CARD sticks —
   naturalTop <= --card-top, i.e. ≈ (100vh − cardH)/2 − 4.4vh ≈ 41–46vh into the
   section (page9UpdateFromScroll). The @fold13 gate is one viewport before
   #page-14, so with the house 200vh that left ~55vh of scroll with the panel
   pinned, engaged and motionless before the closing card could even start
   rising. The trim comes off the TITLE ROW, not the sticky: halving the row
   moves #page-14 (and with it the gate + the .frozen threshold, both measured
   off its offsetTop) up to 50vh in, and — because the sticky keeps its full
   100vh — the sticky still reaches top:0 at exactly the scroll position .frozen
   swaps it to fixed, so nothing positioned inside it moves at the swap. (A
   50vh sticky was tried first: it was still mid-viewport when .frozen fired,
   and everything laid out inside it jumped to the top.) The card now centres
   21vh into its 50vh row and sticks ~17vh in, well before the 50vh gate — a
   gate ahead of the stick would hold scroll where the card never pins, so the
   panel never engages and the fold deadlocks. Still = the 50vh + 100vh content
   so the section can't shrink when .frozen pulls the sticky out of flow.
   Mobile keeps the house geometry. */
@media (min-width: 601px) {
  .text-section.page9-panel { min-height: 150vh; }
  /* The matching `.page9-title-row { min-height: 50vh }` lives right AFTER the
     base .page9-title-row rule below — same specificity, so it has to come
     later in the file or the base 100vh wins and only the section shrinks,
     which makes #page-14 jump up 50vh the moment .frozen pulls the sticky
     out of flow. */
}

/* The title's own normal-flow "page" — scrolls up and away like every other
   fold's title block, no JS positioning of its own (see js/page8-9-scroll.js's
   page9UpdateFromScroll, which only watches .page9-sticky now, not this).
   Centered via inset:0 + margin:auto — deliberately NOT percentage-left +
   transform (tried that first; measured centered in every automated test but
   was reported off-center in a real browser) and NOT flex/auto-margins
   (tried that even earlier, same story) — inset+margin:auto is the oldest,
   most universally-reliable centering technique and doesn't involve
   `transform` or percentage math at all, so it can't be tripped up by
   whatever direction/transform-related quirk those other two hit. */
.page9-title-row {
  position: relative;
  min-height: 100vh;
  /* Flex so the card starts centered while it's still scrolling in, then
     position:sticky on the card takes over once it reaches the top. */
  display: flex;
  align-items: center;
  justify-content: center;
}

/* Desktop half of the @fold12 dead-scroll trim — see .text-section.page9-panel
   above. Must stay below the base rule: same specificity, later wins. */
@media (min-width: 601px) {
  .page9-title-row { min-height: 50vh; }
}

.page9-title-row .text-card {
  position: sticky;
  top: var(--card-top);
  height: max-content;
}

/* Once the card has reached its sticky position, switch to fixed so it stays
   pinned for the entire @fold12 scroll range — not just while .page9-title-row
   is in view. Width/centering must be re-stated since fixed is viewport-relative. */
.page9-title-row .text-card.is-stuck {
  position: fixed;
  top: var(--card-top);
  left: 50%;
  transform: translateX(-50%);
  width: var(--card-w);
}

/* @fold12's headline is the interactive pivot of the whole piece and has to
   read as ONE line, but at 480px (--card-w) it wraps to two. Rather than pick
   a magic wider px value, the card and its frame self-size to the text:
   width:max-content on the card gives the frame room to grow, white-space:
   nowrap on the frame guarantees the single line, and the frame's own
   fit-content + margin:0 auto (see .text-card-frame) keeps it centered.

   Gated at 640px, not the house 601px breakpoint: nowrap cannot shrink, so
   below roughly this width the one-line box would overflow the viewport
   instead of fitting. Under 640px the headline wraps exactly as before.

   .is-stuck has to be re-stated because it swaps to position:fixed and
   re-declares width: var(--card-w) for viewport-relative centering. */
@media (min-width: 640px) {
  .page9-title-row .text-card,
  .page9-title-row .text-card.is-stuck {
    width: max-content;
    max-width: calc(100vw - 48px);
  }

  .page9-title-row .section-title.text-card-frame {
    white-space: nowrap;
  }
}

/* Subtitle sits below the title heading (normal flow), hidden while the card
   is still scrolling, revealed once it sticks at the top. */
.page9-title-row .section-title {
  margin-bottom: 0;
}

.page9-title-row .page9-header-subtitle {
  opacity: 0;
  pointer-events: none;
  text-align: center;
  transition: opacity var(--fade-ms) ease-out;
  margin-top: -21px;
}

.page9-title-row .text-card.is-stuck .page9-header-subtitle {
  opacity: 1;
  pointer-events: auto;
}

/* Frame fade-out: dashed SVG border + white fill dissolve once the card
   sticks at the top, leaving only the bare title text.
   Transitions are on the base selectors so they apply in both directions
   (stuck → unstuck reverts smoothly, not a snap). */
.page9-title-row .text-card .text-card-frame-dash {
  transition: opacity var(--fade-ms) ease-out;
}

.page9-title-row .text-card .text-card-frame {
  transition: background var(--fade-ms) ease-out;
}

.page9-title-row .text-card.is-stuck .text-card-frame-dash {
  opacity: 0;
}

.page9-title-row .text-card.is-stuck .text-card-frame {
  background: transparent;
}

.page9-sticky {
  /* The zones lock in place (extreme near the top, legitimate pinned near the
     bottom) the moment this wrapper reaches the top — no title inside it
     anymore, so nothing here needs its own travel/glide. */
  position: sticky;
  top: 0;
  height: 100vh;
  /* Nothing in the tray is reading material — zone labels, the pill rail, the
     pills — and every one of them is a press-and-hold target. Suppressing
     selection on the whole wrapper means a slow drag anywhere in it can't
     raise handles + the Copy bar over the gesture. The title card is in
     .text-col, outside this, so it stays selectable. */
  -webkit-touch-callout: none;
}

/* Switched to fixed once the user scrolls into @fold13 (#page-14), keeping
   the tray/zones visually frozen in place even though #page-13 has ended.
   Width must be explicit — fixed elements size relative to the viewport, not
   their DOM parent, so the implicit 100% no longer covers the full width. */
.page9-sticky.frozen {
  position: fixed;
  top: 0;
  width: 100%;
}

/* Nothing in the panel — the dragcards tray, the dropped-pill stack — is
   visible/interactive until the title card sticks at the top (see
   page9UpdateFromScroll in js/page8-9-scroll.js's "engaged" toggle). The tray slides in
   first (CSS transition on .page9-tray); the zone-wrap and header fade in
   0.4 s later so the sequence is tray → zone. The (at-rest empty)
   dropped-pill stack just fades; the tray gets its own rule below since it
   also slides up, not just fades. */
/* position:fixed — never scrolls, always at the same viewport position.
   (The .page9-header / .page9-zone-wrap-extreme engage-fade props live in
   each rule's single declaration block below.) */
/* `:not(.training)` on both: on mobile @fold12 plays in TWO beats, per explicit
   instruction — the pill convoy first (p9TrainToggle, page9.js, which holds
   `.training` for its duration), then everything else. The panel arriving
   underneath the train competed with it for attention and made the fold read as
   one busy moment instead of two clear ones. Inert on desktop and under reduced
   motion: neither ever gets `.training` (p9TrainToggle bails before adding it),
   so `:not(.training)` is simply always true there and this reads exactly as
   `.engaged` did. */
.page9-sticky.engaged:not(.training) .page9-zone-wrap-extreme {
  opacity: 1;
  pointer-events: auto;
}

.page9-sticky.engaged:not(.training) .page9-header {
  opacity: 1;
  pointer-events: auto;
}

/* New pinned header (Figma node 201:49243) added alongside, not instead of,
   .page9-title-row's own full-screen scrolling card above — that one keeps
   scrolling away as always; this is a second, separate title+subtitle that
   stays on screen for as long as the panel itself is engaged. Horizontally
   centered on the viewport (per explicit request) — Figma's own frame
   measured this off-center, but that read wrong rendered live, so centered
   wins over the literal Figma offset here. */
.page9-header {
  position: absolute;
  left: 50%;
  transform: translateX(-50%);
  top: var(--card-top);
  text-align: center;
  direction: rtl;
  opacity: 0;
  pointer-events: none;
  /* Slowed from 0.5s per explicit feedback — the engage fade read a touch
     quick right when @fold12's title hits the top and the interaction state
     kicks in. Kept matched with .page9-zone-wrap-extreme below. */
  transition: opacity 0.7s ease-out;
}

.page9-header-subtitle {
  /* Mobile values (harness pick, 2026-09-22): 500 / 0.25. Desktop overrides
     BOTH properties in the min-width: 601px block below, so this base rule is
     effectively the mobile one. */
  font: 500 16px var(--font-body);
  /* Alpha, not the `opacity` property — that one is the stick reveal
     (0 → 1 on .is-stuck, ~line 274) and overwriting it would kill the fade. */
  color: rgba(0, 0, 0, 0.25);
  margin: 4px 0 0;
  white-space: nowrap;
}

/* Stack of dropped-into-"extreme" pills, centered directly on the canvas's
   horizontal middle (Figma's floating label chips all share one x-center,
   756 of a 1512-wide frame) rather than anchored to the page margin. */
.page9-zone-wrap-extreme {
  position: fixed;
  left: 50%;
  transform: translateX(-50%);
  opacity: 0;
  pointer-events: none;
  /* See .page9-header above — same engage-fade slowdown, kept matched. */
  transition: opacity 0.7s ease-out;
  top: 14vh;
  /* LEGACY DESKTOP LAYOUT ONLY — overridden to `auto` by the
     .page9-layout-v2 block below, which derives its own top from measured CSS
     vars instead of hand-syncing a second copy of a JS constant. */
  bottom: 28.78vh; /* ends ~2vh above the divider — P9_MID (page9.js) is now 719/982 ≈ 73.22vh, unlike the canvas grids this box's own vh value isn't derived from that constant at render time, so it has to be hand-kept in sync whenever P9_MID moves */
  display: flex;
  flex-direction: column;
  align-items: center;
  /* flex-end, not center: anchors the zone to the bottom of this area (just
     above the "פעולות קיצוניות" axis label) so a tall dragging-state box
     (see --page9-zone-stack-height) grows upward, away from the label,
     instead of growing symmetrically and overlapping it from below. */
  justify-content: flex-end;
  gap: 8px;
}

.page9-zone {
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
  min-height: 44px;
  padding: 6px 0;
  border-radius: 8px;
  transition: background 0.15s;
}

/* A fixed stack of 2 row wrappers (.page9-tray-row, created in p9BuildPanel),
   not flex-wrap directly: each row is its own independent grid so a long
   pill in one row never inflates another row's column width (see
   P9_TRAY_GRID/page9.js) — sharing one grid across all 2 rows would force
   every column wide enough for whichever row's occupant in that column is
   widest, leaving shorter pills centered in leftover space with an uneven
   gap to their neighbor instead of the row's own, consistent `gap`. */
#page9ZoneBelow {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 18px;
}

/* Track sizes are assigned once in p9BuildPanel from each row's own real
   measured pill widths/heights, not from which categories happen to
   currently be in it — dragging a pill out to #page9ZoneAbove just empties
   its cell; the others never reflow to fill the gap, and the row's own
   height never changes. Both grid-template-columns and the per-pill
   grid-column are set inline by JS, not here, so this rule only needs the
   parts that don't vary. */
.page9-tray-row {
  display: grid;
  gap: 0 10px;
  justify-items: center;
  align-items: center;
}

/* The pill tray — every category's drag origin, bottom-centered and flush
   with the viewport's bottom edge, like Figma's frame (node 201:49246 — y=761
   to 982 of a 982-tall frame, i.e. its bottom edge IS the frame's bottom edge,
   no gap). That's also why only the top two corners are rounded — a 0-radius
   bottom flush against the edge reads as part of the screen's own boundary,
   not a floating card. A plain filled card, not a dashed box (Figma dropped
   that treatment for this component specifically) — solid fill + soft
   ambient shadow. Sized from its content (the fixed-track grid above, plus
   the hint text) rather than a hardcoded width, so it always exactly fits the
   grid p9BuildPanel computed. padding-top is wider than the other 3 sides
   (50px vs the others' 15/29px) to leave room for the floating "פעולות"
   label (.page9-tray-title, absolutely positioned, see below) and its own
   divider (.page9-tray-divider) above the pill rows. The three gaps this
   creates — above the title, title-to-divider, divider-to-first-row — are
   all the same 10px (G in the comments below), assuming the title's own
   rendered line-box is ~19px tall (16px/700 Assistant); previous px picks
   here left those three gaps visibly uneven (12/7/5). */
.page9-tray {
  /* Fixed to the viewport bottom so its CSS slide-in is visible the instant
     .engaged fires — position:absolute would put it off-screen below the
     sticky panel until the panel actually pins, so the animation would be
     invisible. When the sticky panel IS pinned (top:0, height:100vh) this
     is the same visual position as absolute;bottom:0 would give.
     fold13's slide-out overrides transform via an inline style (see
     updateFold13 in js/fold11.js) and sets transition:none so it tracks scroll
     frame-by-frame without lag from the CSS transition. */
  position: fixed;
  left: 50%;
  bottom: 0;
  transform: translate(-50%, 100%); /* off-screen below until .engaged */
  /* The hidden transform above is a percentage of the tray's OWN height, and
     that height isn't final on a refresh: p9MeasureTrayLayout (page9.js)
     writes each row's pixel height at DOMContentLoaded and AGAIN at
     document.fonts.ready, since 'Assistant' loads with display:swap. Until
     then the box is short, 100% of it doesn't clear the viewport bottom, and
     the pill rows peek above the bottom edge — then the height correction
     re-resolves the translate and the transition below animates that
     difference, so the tray visibly slides away during @fold1. visibility
     doesn't depend on any measurement, so it hides the tray from the first
     paint. The 0.85s delay makes it outlast the slide-out (it has to stay
     visible for the whole animation, including fold13's scroll-driven one,
     which never removes .engaged); .engaged clears the delay so the slide-IN
     is visible from its first frame. Mirrored by the ≤600px block, which
     re-declares both halves for the band. */
  /* Slowed from 0.6s per explicit feedback, same reasoning as .page9-header's
     own slowdown above — the engage slide-in read a touch quick. */
  visibility: hidden;
  transition: transform var(--tray-ms) var(--tray-ease),
              visibility 0s linear var(--tray-ms);
  /* Belt to the visibility brace above, because that brace was observed losing:
     on a phone refresh the tray, its zone and its pills all painted over @fold1
     and dropped out one at a time (12ms / 75ms / 542ms) exactly as each
     measurement landed. Opacity is the right second gate because it does NOT
     affect layout — offsetWidth/offsetHeight inside p9MeasureTrayLayout still
     read true, which `display: none` would break. Lifted by .is-measured, added
     from the document.fonts.ready measure pass (page9.js) — the pass that makes
     the -100% transform above finally resolve to a real height. Nothing else
     writes an inline opacity on the tray (js/fold11.js fades the header, title
     card, zone wrap and legend, never this), so nothing can fight it. */
  opacity: 0;
  width: max-content;
  max-width: calc(100vw - 96px);
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 18px;
  padding: 50px 29px 35px; /* bottom bumped from 15px — taller frame, more breathing room below the lower pill row */
  border-radius: 20px 20px 0 0;
  background: var(--bg);
  border: 1px solid rgba(90, 90, 90, 0.45);
  border-bottom: none; /* flush with the viewport's bottom edge — no visible edge to stroke there */
  pointer-events: none;
  z-index: 10;
}

/* Boot gate released — see the opacity comment in .page9-tray above. Separate
   from .engaged on purpose: this only says "the tray's height is now real, so
   its hidden transform actually hides it", which is true for the whole rest of
   the page. Whether the tray is on SCREEN stays .engaged's job alone. */
.page9-tray.is-measured {
  opacity: 1;
}

.page9-sticky.engaged .page9-tray {
  transform: translate(-50%, 0); /* slides in */
  pointer-events: auto;
  visibility: visible;
  transition: transform var(--tray-ms) var(--tray-ease),
              visibility 0s linear 0s;
}

/* A pill dragged back down gets a dragover class on #page9ZoneBelow itself
   (see dropTargets in p9BuildPanel, page9.js) — but that element has no fill
   of its own, the visible box is its parent .page9-tray, so :has() is needed
   to actually darken something on screen. Figma's mockup doesn't show a
   drag-over state for this component, so this just darkens the existing fill
   rather than inventing border chrome that has no ground truth. */
.page9-tray:has(#page9ZoneBelow.dragover) {
  background: rgb(247, 246, 251);
}

/* New tray header (Figma node 201:49246) — a floating overlay, not a flex
   child: Figma positions it absolute (top:16px, right edge at the card's own
   horizontal center + 23px) so it never participates in the flex column's
   gap/layout and doesn't push the pill rows down. The card's own padding-top
   above is widened specifically to leave room for it. */
.page9-tray-title {
  position: absolute;
  top: 10px; /* G */
  right: calc(50% - 23px);
  font: 600 16px var(--font-body);
  color: rgba(0, 0, 0, 0.37);
  white-space: nowrap;
  margin: 0;
}

/* Used to separate the "פעולות" title from the dragcards below it — removed
   per explicit request (the tray's own new border, above, was enough
   definition without this too). Element (index.html) is kept in the DOM
   rather than removed from markup, just made invisible here. */
.page9-tray-divider {
  display: none;
}

/* ── @fold12 desktop layout V2 ────────────────────────────────────────────────
   Scoped entirely under .page9-sticky.page9-layout-v2, a class JS puts on the
   panel when P9_LAYOUT_V2 is on AND we're above the 600px breakpoint (see
   p9SyncLayoutV2Class, page9.js) — so nothing here can reach the mobile layout,
   and flipping the flag off restores the rules above untouched.

   The layout: the pill row moves from a bottom sheet to a bare band under the
   titles — no card, just a divider rule beneath it, the same treatment the
   ≤600px block gives the mobile band — the drop zone sits directly below it
   and runs horizontally, and the canvas legit grid keeps its ordinary
   free-dot shuffle in a shorter strip (P9_LEGIT_H_V2, page9.js). It is NOT
   mobile's packed `mode: "bar"`.

   --p9-v2-tray-top / --page9-tray-height are written onto .page9-sticky by
   p9MeasureTrayLayout, from P9_TRAY_TOP_V2 and the tray's real measured
   height — so the drop zone's `top` below is derived, never a hand-synced
   twin of a JS constant (the trap the legacy `bottom: 28.78vh` above is). */
.page9-sticky.page9-layout-v2 .page9-tray {
  top: var(--p9-v2-tray-top, 120px);
  bottom: auto;
  /* Not slid off-screen any more: the band waits in place, invisible
     (visibility/opacity gates from the base rule), and its pills pop in on
     .engaged. updateFold13's inline slide-out (js/fold11.js) still lifts it
     off the top by P9_TRAY_TOP_V2 at @fold13. */
  transform: translate(-50%, 0); /* in place — see the H2 pop-in block below */
  /* No card chrome at all — the pills sit straight on the page and a single
     full-bleed rule under them separates the band from the canvas below,
     exactly as on mobile. Full-bleed needs the width overrides too, since the
     base rule is `width: max-content` sized to the pills. Background stays
     transparent: there is no card to fill, and the canvas behind the band is
     empty at this height. */
  width: 100vw;
  max-width: 100vw;
  background: transparent;
  border-radius: 0;
  border-left: none;
  border-right: none;
  border-top: none;
  border-bottom: none; /* the rule is the ::after below, so it can draw in */
  /* The title overlay is hidden below, so the padding no longer has to leave
     room for it — even top/bottom instead of the sheet's 50/35, and no side
     padding now that the band is full-bleed and its pills are centered. */
  padding: 14px 0 14px;
}

/* ── V2 engage: pills pop in right → left, rule draws under them in step
   (teacher review 2026-09-03, H2). The band no longer slides down from the
   top: it sits in place (the transform below is the at-rest value in BOTH
   states) and is simply invisible until .engaged. Each pill starts at
   scale(0) and pops to 1 in P9 "dot pop" time (280ms, the hero dots' own
   PAGE0_POP_MS), delayed by its --p9-pop-i (page9.js: 0 for the rightmost
   pill, the first in RTL reading order, up to 9) × 60ms — so the pop crest
   travels leftward across the band. Un-engaging (scrolling back up) mirrors
   it: the leftmost pill shrinks first. The full-bleed rule is a ::after
   scaled from its RIGHT edge over the same 9×60+280 = 820ms, linear so its
   leading edge keeps pace with the constant stagger. Only pills IN the band
   are scoped — a pill docked in #page9ZoneAbove is untouched. ── */
.page9-sticky.page9-layout-v2 .page9-tray {
  --p9-pop-ms: 280ms;
  --p9-pop-stagger: 60ms;
  --p9-pop-total: 820ms;
}
.page9-sticky.page9-layout-v2 .page9-tray::after {
  content: "";
  position: absolute;
  left: 0;
  right: 0;
  bottom: 0;
  height: 1px;
  background: rgba(90, 90, 90, 0.18);
  transform: scaleX(0);
  transform-origin: right center;
  transition: transform var(--p9-pop-total) linear;
}
.page9-sticky.page9-layout-v2.engaged .page9-tray::after {
  transform: scaleX(1);
}
.page9-sticky.page9-layout-v2 .page9-tray .page9-pill {
  transform: scale(0);
  transition: transform var(--p9-pop-ms) cubic-bezier(0.37, 0, 0.63, 1)
              calc((9 - var(--p9-pop-i, 0)) * var(--p9-pop-stagger)),
              background-color var(--p9-pop-ms) ease, box-shadow var(--p9-pop-ms) ease,
              border-color var(--p9-pop-ms) ease;
}
/* DESKTOP, @fold11: the early pills are outlines only — no fill, no drop
   shadow, and a fainter outline (0.2, manual/-baked 2026-09-28; 0.3 once
   engaged). All three arrive with the rest of the panel on @fold12's stick (.engaged),
   when the pills become draggable. Mobile keeps its own pill look. */
@media (min-width: 601px) {
  .page9-sticky.page9-layout-v2:not(.engaged) .page9-tray .page9-pill {
    background-color: transparent;
    box-shadow: none;
    border-color: rgba(130, 130, 130, 0.2);
  }
}
/* The pop is scoped to .pills-in, NOT .engaged: the pills come in one fold
   early, on @fold11's own crossing (page8CheckScroll, js/page8-9-scroll.js),
   while the rule, the drop zone and the pinned header all still wait for
   @fold12's stick. Same animation, same stagger — only the class that fires
   it moved. The band itself carries no chrome in V2, so making it visible
   here shows nothing but the pills. */
.page9-sticky.page9-layout-v2.pills-in .page9-tray {
  visibility: visible;
  transition: transform var(--tray-ms) var(--tray-ease),
              visibility 0s linear 0s;
}
/* The vacancy pops on the pill's clock (same --p9-pop-i, copied over in
   p9SyncVacancy) but is scoped to .engaged, NOT .pills-in: it belongs to
   @fold12's drop zone, so it leaves when the title block unsticks on the way
   back up — otherwise the recesses read as a row of ghost pills over @fold11. */
.page9-sticky.page9-layout-v2 .page9-tray .page9-pill-vacancy {
  transform: scale(0); /* no transition: leaving is a snap, only the way in pops */
}
.page9-sticky.page9-layout-v2.engaged .page9-tray .page9-pill-vacancy {
  transition: transform var(--p9-pop-ms) cubic-bezier(0.37, 0, 0.63, 1);
  transform: scale(1);
  transition-delay: calc(var(--p9-pop-i, 0) * var(--p9-pop-stagger));
}
.page9-sticky.page9-layout-v2.pills-in .page9-tray .page9-pill {
  transform: scale(1);
  transition-delay: calc(var(--p9-pop-i, 0) * var(--p9-pop-stagger));
}
/* The 6-dot grip handle is @fold12's beat, not @fold11's. @fold11 pops the
   pills in a fold early (.pills-in above) while the tray is still
   pointer-events:none — nothing is draggable yet, so a grip affordance there
   promises something the fold can't deliver. So in the tray the handle starts
   collapsed and the pill is narrower by exactly its footprint; @fold12's stick
   (.engaged) pops the dots in and the pill widens around them.

   Collapsing it takes THREE properties, not just the transform: `scale(0)` is a
   paint-time transform and leaves the 8px grid box in the flex line, so the pill
   would keep the space and only the dots would vanish. `width: 0` retracts the
   box, and the negative inline-end margin swallows the pill's own 6px flex gap,
   which is otherwise still drawn between a zero-width handle and the label. All
   three animate together on the pill's own pop clock, so the widening reads as
   one move. Scoped to `.page9-tray` so a dropped pill in #page9ZoneAbove (which
   hides its dots on its own terms, further down) is untouched, and to
   `.page9-layout-v2` so mobile — where the handle is display:none anyway — never
   sees it. */
.page9-sticky.page9-layout-v2 .page9-tray .page9-pill .page9-handle {
  width: 0;
  margin-inline-end: -6px;
  overflow: hidden;
  transform: scale(0);
  transition: width var(--p9-pop-ms) cubic-bezier(0.37, 0, 0.63, 1),
              margin-inline-end var(--p9-pop-ms) cubic-bezier(0.37, 0, 0.63, 1),
              transform var(--p9-pop-ms) cubic-bezier(0.37, 0, 0.63, 1);
}
.page9-sticky.page9-layout-v2.engaged .page9-tray .page9-pill .page9-handle {
  width: 7px;   /* 2 columns of 2.5px + the 2px column gap */
  margin-inline-end: 0;
  transform: scale(1);
  transition-delay: calc(var(--p9-pop-i, 0) * var(--p9-pop-stagger));
}
/* Held for the length of p9MeasureTrayLayout's reads only (page9.js): the fixed
   column tracks it bakes have to reserve the handle's width even though the
   handle is collapsed at measure time, or the pills overflow their tracks the
   moment @fold12 pops the dots in. No transition — this state is never seen. */
.page9-sticky.page9-layout-v2.page9-measuring-handles .page9-tray .page9-pill .page9-handle {
  width: 7px;
  margin-inline-end: 0;
  transform: none;
  transition: none;
}

/* Reduced motion, CSS half. The JS half is prefersReducedMotion() in
   js/core.js, read by makeTrigger (js/groups.js) — together they cover every
   fold beat and every state flip.

   Near-zero durations rather than `none` so transitionend/animationend still
   fire, and so an animation's end state is still what lands — the same
   principle makeTrigger uses. Iteration count 1 stops anything that loops.
   This is safe to apply this broadly because of the project convention that
   elements JS repaints every frame carry no CSS transition at all
   (Animation-System.md), so there is nothing here for it to fight with.

   NOT covered, deliberately: motion that IS the scroll position — @fold8's
   scrubbed timeline and @fold11's glide are the content, and freezing them
   would leave nothing to read. Nor page9.js's finalized drop animation. */
@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after {
    transition-duration: 0.01ms !important;
    transition-delay: 0s !important;
    animation-duration: 0.01ms !important;
    animation-delay: 0s !important;
    animation-iteration-count: 1 !important;
  }
  html { scroll-behavior: auto !important; }
}

/* The dragover tint is a fill on the tray card — and in V2 there is no card,
   so there's nothing to tint. The drop target itself still works; its
   feedback is the pill returning to its slot. */
.page9-sticky.page9-layout-v2 .page9-tray:has(#page9ZoneBelow.dragover) {
  background: transparent;
}

/* Dragging a pill OUT of the extreme zone: the legit band is the destination,
   so it gets the same fill ladder the extreme zone shows on a tray→extreme
   drag (dragging rgb(239,238,248) → dragover rgb(230,229,244)); the extreme
   zone's own drag fills are suppressed for this direction (see its
   :not(.dragging-from-above) rules). The extra class also out-specifies the
   transparent :has() rule above. */
.page9-sticky.page9-layout-v2.dragging-from-above .page9-tray {
  background: rgb(239, 238, 248);
}
.page9-sticky.page9-layout-v2.dragging-from-above .page9-tray:has(#page9ZoneBelow.dragover) {
  background: rgb(230, 229, 244);
}

.page9-sticky.page9-layout-v2.engaged .page9-tray {
  transform: translate(-50%, 0);
}

/* "סוגי פעולות" — dropped in V2 per explicit request: the band is just the
   pills. The element stays in the DOM (index.html), same as the divider. */
.page9-sticky.page9-layout-v2 .page9-tray-title {
  display: none;
}

/* Both row wrappers are always built (p9BuildPanel), but V2 puts all 10 pills
   in row 1 — the empty second row would otherwise still contribute the
   column's 18px gap. */
.page9-sticky.page9-layout-v2 .page9-tray-row:empty {
  display: none;
}

/* ── Regular-desktop tier (≤1600px) ──
   The V2 band was tuned on a big screen; at regular desktop widths all 10
   pills can't hold one 20px row, so p9TrayGrid() (page9.js — same 1600 cutoff,
   P9_DESKTOP_REGULAR_MAX, keep them in sync) falls back to the legacy 5/5
   two-row arrangement. Chosen over shrinking the pills in a live compare
   ("two rows at 20px", picked at a 1492px viewport). The 10px row gap matches
   the spacing judged in that compare (the base #page9ZoneBelow gap is 18px). */
@media (max-width: 1600px) {
  .page9-sticky.page9-layout-v2 #page9ZoneBelow {
    gap: 10px;
  }
  /* TRIAL under judgment: single row kept at this width (see p9TrayGrid), so
     the pills shrink 20px→18px to fit ten across. Applies to dropped pills in
     the zone too — the zone's reserved width is measured off tray pills, so a
     zone pill at a bigger font would overflow the reserve. The ghost is listed
     separately because it lives on <body> (see .page9-pill-ghost), OUTSIDE
     .page9-layout-v2's scope — without it a drag would pop the pill back to
     20px under the cursor. The ghost needs the double-class specificity bump:
     bare .page9-pill-ghost ties with the base `.page9-pill { font: 600 20px }`
     rule, which sits LATER in the file and won on source order — the ghost
     popped back to 20px mid-drag. (0,2,0) beats it regardless of order. Kept
     out of the ≤600px range so mobile's own 16px .page9-pill rule, which this
     would also outweigh, stays in charge there. */
  .page9-sticky.page9-layout-v2 .page9-pill {
    font-size: 18px;
  }
}
@media (max-width: 1600px) and (min-width: 601px) {
  .page9-pill.page9-pill-ghost {
    font-size: 18px;
  }
}
/* Tight tier: when even the 18px single row is wider than the viewport, the
   centered row runs off both screen edges — drop to 16px. No media query on
   purpose: the overflow point depends on ten Hebrew labels' rendered widths,
   so p9MeasureTrayLayout MEASURES the row and toggles .page9-pills-tight on
   the panel (and on <body> for the ghost, which lives there, outside the
   panel's scope — same story as the 18px ghost rule above). Specificity
   (0,3,0) beats both the 18px tier and the base 20px rule regardless of
   source order. Desktop-only by construction: the class is only ever set
   when p9IsV2() is true, so mobile's own 16px rule stays in charge. */
.page9-sticky.page9-layout-v2.page9-pills-tight .page9-pill {
  font-size: 16px;
}
body.page9-pills-tight .page9-pill.page9-pill-ghost {
  font-size: 16px;
}

/* ── Desktop pills, tuned on a manual/ harness ──
   16px / weight 400 at every desktop width (outranks the 20/18px tiers above),
   a 4-dot (2×2) grip of 2.5px dots, and no rule under the band. The ghost
   (lives on <body>, outside .page9-layout-v2) gets the same, so a dragged
   pill looks exactly like the one it was lifted from. Mobile keeps its own. */
@media (min-width: 601px) {
  .page9-sticky.page9-layout-v2.page9-layout-v2 .page9-pill,
  body .page9-pill.page9-pill-ghost.page9-pill-ghost {
    font-size: 16px;
    font-weight: 400;
    white-space: nowrap;   /* a label never wraps, whatever its track width */
    flex-shrink: 0;
  }
  .page9-sticky.page9-layout-v2 .page9-pill .page9-handle,
  body .page9-pill.page9-pill-ghost .page9-handle {
    grid-template-columns: repeat(2, 2.5px);
    grid-template-rows: repeat(2, 2.5px);
  }
  .page9-sticky.page9-layout-v2 .page9-pill .page9-handle span,
  body .page9-pill.page9-pill-ghost .page9-handle span {
    width: 2.5px;
    height: 2.5px;
  }
  .page9-sticky.page9-layout-v2 .page9-pill .page9-handle span:nth-child(n+5),
  body .page9-pill.page9-pill-ghost .page9-handle span:nth-child(n+5) {
    display: none;
  }
  .page9-sticky.page9-layout-v2 .page9-tray::after {
    display: none;
  }
  .page9-sticky.page9-layout-v2 .page9-tray-row {
    column-gap: 8px;
  }
  .page9-sticky.page9-layout-v2 .page9-tray .page9-pill,
  body .page9-pill.page9-pill-ghost.page9-pill-ghost {
    /* --p9-pill-pad is set by p9MeasureTrayLayout (page9.js) only when the
       row would come closer than P9_TRAY_EDGE_MARGIN to the screen edge. */
    padding-left: var(--p9-pill-pad, 16px);
    padding-right: var(--p9-pill-pad, 16px);
  }
}

/* The drop zone stays BETWEEN the two canvas column-blocks, as in the legacy
   layout — V2 only moves the band it lives in: it spans from under the pill
   row down to the top of the legit strip (--p9-v2-legit-h, published by
   p9MeasureTrayLayout from P9_LEGIT_H_V2 rather than hand-synced here — the
   28.78vh trap the base rule below documents), and centers in it so the box
   sits level with the columns it divides. */
.page9-sticky.page9-layout-v2 .page9-zone-wrap-extreme {
  top: calc(var(--p9-v2-tray-top, 120px) + var(--page9-tray-height, 0px) + 18px);
  /* +24px clearance so the dashed box never touches the legit strip below it
     (it did on regular-height viewports, where the band spans less room). */
  bottom: calc(var(--p9-v2-legit-h, 150px) + 24px);
  justify-content: center;
}

/* The zone is a VERTICAL stack in V2, same as the legacy layout — it inherits
   the base rule's column direction, flush-bottom anchoring and min-width; only
   the things V2 genuinely changes are listed here. Real 8px gaps between
   dropped pills instead of the base layout's flush 0, because the seam-line
   trick those relied on is switched off below (dropped pills keep their tray
   chrome). padding is a touch taller than the base 6px so the stack isn't
   against the dashed frame; p9MeasureTrayLayout reads both the gap and this
   padding back off the computed style when it sizes the reserved box, so
   there's no second copy of either number in JS. The base 20px bottom margin
   is clearance for the "פעולות קיצוניות" axis label under the legacy
   bottom-anchored zone; V2's zone floats in the columns' gap with nothing
   below it. */
.page9-sticky.page9-layout-v2 #page9ZoneAbove {
  gap: 8px;
  /* Side/vertical padding tuned by eye (harness). The
     reserved box width is measured off this (p9MeasureTrayLayout reads the
     computed padding), so the dashed box narrows with it — and the canvas
     center gap, derived from that width, closes in to match. */
  padding: 18px 16px;
  /* The base rule's 260px min-width exists so an EMPTY legacy column zone
     stays a hittable target; V2 always has an explicit measured width, and
     the floor would otherwise cancel the narrower padding above. */
  min-width: 0;
  margin-bottom: 0;
  /* The stroke is UNCONDITIONAL in V2. The base layout only colors the border
     under :empty / .tray-pill-hover / .dragging, so a zone that already holds
     pills and isn't being interacted with loses its outline entirely — the
     drop target stops reading as a target. Here it is always visible; the
     rules below only *deepen* it per state, never turn it off. The 2px dashed
     width/style still comes from the base rule, so nothing about the box model
     changes between states. */
  border-color: rgba(0, 0, 0, 0.08);
  /* The BOX is unconditional too, for the same reason: the base layout only
     applies these vars under :empty / .tray-pill-hover / .dragging, so the
     moment a pill lands the zone shrink-wraps to its contents and the outline
     collapses onto the pills. Pinning the reserved size at all times keeps one
     stable frame that pills fill up inside. Safe because the reserve is
     already computed for the full ten (p9MeasureTrayLayout's V2 arm) and
     p9ExtremeTopY budgets for it whether or not it's occupied. */
  position: relative;
  border-radius: 8px;
  height: var(--page9-zone-stack-height);
  /* The reserve above is sized for all ten pills; on regular-height viewports
     that can exceed the band the wrap spans (pill row → legit strip), and the
     centered box would poke past both ends — including into the strip the
     wrap's 24px bottom clearance exists to protect. Never grow past the band. */
  max-height: 100%;
  /* The WIDTH is the full reserve at all times — the box never narrows onto
     its dropped pills. The reserve is sized for the single longest category
     (p9MeasureTrayLayout's V2 arm) and the canvas center gap is derived from
     it (p9ZoneStackWV2), so a hugging width would have moved the extreme
     columns every time a pill landed. */
  width: var(--page9-zone-stack-width);
  /* On a short viewport the max-height clamp above can leave the box with
     less room than the full ten-pill reserve — then, and only then, the
     stack scrolls. `auto` is the whole "only in this circumstance" gate:
     while the content fits it renders nothing and changes nothing. The
     bottom anchoring can NOT stay on the base rule's justify-content:
     flex-end for this — a flex container scrolls only toward its end edge,
     so with flex-end the overflowing pills poke past the top and no
     scrollbar can ever reach them. flex-start + margin-top: auto on the
     first pill (rule below) is the standard swap: identical bottom-anchored
     layout while there's spare room (the auto margin soaks it up), and the
     margin resolves to 0 the moment content overflows, leaving a normally
     scrollable stack. */
  overflow-y: auto;
  justify-content: flex-start;
  /* Every state change here — fill, stroke, hint-text colour — snaps. Kills
     the base .page9-zone's `transition: background 0.15s`; the border and the
     ::after colour were never transitioned to begin with, and this keeps all
     three switching on the same frame instead of the fill lagging behind. */
  transition: none;
}

/* The other half of the scroll swap above: soaks up the spare height so the
   stack stays glued to the box's bottom edge exactly as flex-end kept it
   (the pills are the zone's only children, so :first-child is always the
   newest — topmost — pill). The `.page9-pill` in the selector keeps this off
   the :empty state, whose only "child" is the absolutely-centered ::after
   hint, which auto margins must not touch. */
.page9-sticky.page9-layout-v2 #page9ZoneAbove .page9-pill:first-child {
  margin-top: auto;
}

/* The V2 fill ladder, all three steps tuned by eye against each other
   (harness): empty rgb(245,245,251) → dragging rgb(235,234,246) → dragover
   rgb(225,224,242), an even 10-per-step walk from white toward the panel's
   lilac. Opaque rather than the base layout's alpha'd whites, so the step size
   doesn't drift with whatever is behind the zone. The fourth state — pills in
   it, nothing hovered — is deliberately NOT filled at all (no rule; see the
   base V2 rule above): its dashed stroke is enough on its own, and a resting
   fill competed with the dropped pills sitting inside it. */
.page9-sticky.page9-layout-v2 #page9ZoneAbove:empty,
.page9-sticky.page9-layout-v2 #page9ZoneAbove.tray-pill-hover {
  background: rgb(245, 245, 251);
  border-color: rgba(0, 0, 0, 0.1);
}
/* Only the very first state — empty, with the "גררו..." hint — insets its sides
   further, so the hint line isn't near the dashed edge. Box-sizing is
   border-box and the width is the explicit reserve var, so this changes where
   the text sits, never how wide the box is; .tray-pill-hover is deliberately
   NOT included, or the inset would jump the moment a tray pill is hovered. */
.page9-sticky.page9-layout-v2 #page9ZoneAbove:empty {
  padding-left: 26px;
  padding-right: 26px;
  /* The untouched zone also breathes bigger than the working reserve: 16px of
     extra air on every side, snapping back to the exact reserve the moment the
     first pill lands (the base V2 rule's height/width take over when :empty
     drops). Safe against the canvas: the center gap is derived from the
     un-padded reserve var, but extreme columns only exist once something has
     been dropped — at which point the box is no longer :empty and is back at
     reserve size. max-height: 100% in the base rule still clamps the taller
     empty box inside the band on short viewports. */
  height: calc(var(--page9-zone-stack-height) + 32px);
  width: calc(var(--page9-zone-stack-width) + 32px);
}
/* Dragging: one step darker than empty above, and deliberately kept LIGHTER
   than the .dragover fill below so actually holding a pill over the zone still
   reads as the darkest state.
   The border-color is here for a second reason: without it the base dragging
   rule's own 0.11 would win on source order and the stroke would go *lighter*
   the moment a drag starts.
   Both drag fills are gated on :not(.dragging-from-above): a pill dragged OUT
   of the extreme zone is heading for the legit band below, so the destination
   highlight belongs to the tray (see the .dragging-from-above rules in the V2
   tray block), not to the zone the pill just left. */
.page9-sticky.page9-layout-v2.dragging:not(.dragging-from-above) #page9ZoneAbove {
  background: rgb(239, 238, 248);
  border-color: rgba(0, 0, 0, 0.22);
}
/* Top of the three-step ladder: a pill actually held over the zone. One more
   step past .dragging above, and darker than the base layout's own dragover
   (rgba(239,238,244,0.78)). The extra .page9-layout-v2 class is what carries
   it past that base rule, which is later in the file and otherwise ties. */
.page9-sticky.page9-layout-v2.dragging:not(.dragging-from-above) #page9ZoneAbove.dragover {
  background: rgb(230, 229, 244);
  border-color: rgba(0, 0, 0, 0.26);
}

/* Dragging OUT of the extreme zone: the V2 drag fills above stand down (their
   :not(.dragging-from-above) gate), but the legacy base layout's own
   `.page9-sticky.dragging #page9ZoneAbove` fill/border (and its .dragover
   twin, both later in the file) would then show through — this explicitly
   zeroes the fill for that direction. The doubled .dragging.dragging-from-above
   is a deliberate specificity bump past the legacy dragover rule's
   (1,3,0); this lands at (1,4,0) despite sitting earlier in the file. */
.page9-sticky.page9-layout-v2.dragging.dragging-from-above #page9ZoneAbove {
  background: transparent;
  border-color: rgba(0, 0, 0, 0.08);
}

/* A dropped pill keeps the exact appearance it had in the band — per explicit
   request, dropping changes only WHERE a pill is, never how it looks. This
   undoes the whole legacy dropped-chip treatment (stroke-only chrome, forced
   shared width, transparent fill, brightened handle) so every property falls
   back to the base .page9-pill rule. The ::before/::after seam lines go with
   it: they exist only to join a flush vertical stack, which V2 isn't. */
.page9-sticky.page9-layout-v2 #page9ZoneAbove .page9-pill {
  border: 1px solid rgba(130, 130, 130, 0.3);
  border-radius: 4px;
  padding: 12px;
  background: #fff;
  box-shadow: 0 1px 1.1px rgba(0, 0, 0, 0.07);
  width: auto;
  justify-content: flex-start;
}
.page9-sticky.page9-layout-v2 #page9ZoneAbove .page9-pill::before,
.page9-sticky.page9-layout-v2 #page9ZoneAbove .page9-pill::after {
  display: none;
}
.page9-sticky.page9-layout-v2 #page9ZoneAbove .page9-pill .page9-handle span {
  background: rgba(0, 0, 0, 0.4);
}
/* Hovering a dropped pill lifts it with the same shadow the tray's pills get
   (.page9-tray .page9-pill:hover) — same affordance, it's still grabbable. */
.page9-sticky.page9-layout-v2 #page9ZoneAbove .page9-pill:hover {
  box-shadow: 0 2px 8px rgba(0, 0, 0, 0.15);
}

/* Once dropped into the extreme zone, pills stack centered on the divider's
   midpoint (see .page9-zone-wrap-extreme) rather than wherever they happened
   to land — and drop their drag handle since they're no longer being browsed
   from a shared pool, just placed markers. Figma's floating chips (node
   136:433215) use different chrome from the tray's pill (136:410819), not the
   same chrome minus the grip dots — see the override below. */
#page9ZoneAbove {
  flex-direction: column;
  flex-wrap: nowrap;
  align-items: center;
  /* Bottom-anchored, not the default flex-start: at rest this box is sized to
     just its own dropped pills, so it doesn't matter — but once dragging
     starts and the box's height jumps to fit all 10 (see
     --page9-zone-stack-height below), flex-start would shove already-dropped
     pills up to the box's new top edge. flex-end instead adds the extra
     space above them, so a pill dropped earlier stays exactly where it was. */
  justify-content: flex-end;
  /* Without an explicit width, an empty column flex item collapses to 0 width
     (no content to size against) — leaving nothing for a drop to actually land
     on. A generous min-width keeps it a real, hittable drop target at rest. */
  min-width: 260px;
  /* Overrides .page9-zone's `padding: 6px 0` to add horizontal breathing room
     too — needed so the longest pill (the dragging-state width is sized to
     it exactly, see --page9-zone-stack-width below) isn't flush against the
     dashed border. */
  padding: 6px 12px;
  /* Overrides .page9-zone's `gap: 8px` — dropped pills stack flush against
     each other (see #page9ZoneAbove .page9-pill::after below, which draws
     the only line at each seam) rather than leaving a visible gap between
     each pill's own independent top/bottom stroke. */
  gap: 0;
  /* Fixed gap from the "פעולות קיצוניות" axis label sitting just below this
     zone's wrap (see justify-content: flex-end above) — independent of the
     box's own height, so it holds even as the dragging-state box grows. */
  margin-bottom: 20px;
  /* Always the same 2px width, just transparent until :empty/.dragging below
     gives it a real color — a border that only *appears* (0px -> 2px) right
     when a drag starts shrinks the content box by that same amount at that
     exact moment, nudging already-dropped pills up by a couple px. Keeping
     the width constant at all times means toggling the color never touches
     the box model, so picking up a pill can't shift its siblings. */
  border: 2px dashed transparent;
}

/* Dropped pills must stack in DROP order — first drop at the bottom, each new
   one on top (the DOM prepend in placePillInZone). Every pill carries an
   inline `order` (set in page9.js for the MOBILE tray's flex row), and since
   this zone is also a flex column that inline value would re-sort the stack
   by tray position instead; !important is required to beat the inline style. */
#page9ZoneAbove .page9-pill {
  order: 0 !important;
}

/* The extreme zone shows its own boundary up front, before the first drop,
   as an invitation — not just once a pill is actually being dragged
   (:empty alone covers that initial state; .dragging is still listed too so
   the box stays visible as a drop target on every *later* drag as well,
   once the zone already has something in it and :empty no longer matches —
   this part of the box, the outline/size, is meant to keep showing).
   .tray-pill-hover (toggled by p9CategoryTooltipInit, page9.js) previews the
   same box the instant a tray pill is merely hovered, before any drag even
   starts. Sized to --page9-zone-stack-height/-width (set in
   p9MeasureTrayLayout, page9.js) — the height of all 10 categories stacked
   at once and the width of the single longest one, not just whatever's
   actually dropped in it right now, so the box never resizes mid-drag. */
#page9ZoneAbove:empty,
#page9ZoneAbove.tray-pill-hover,
.page9-sticky.dragging #page9ZoneAbove {
  position: relative;
  /* Width/style already set (transparent) on the base rule above — only the
     color actually needs to change here, see that rule's own comment. */
  border-color: rgba(0, 0, 0, 0.07);
  border-radius: 8px;
  height: var(--page9-zone-stack-height);
  width: var(--page9-zone-stack-width);
}

/* Idle (:empty, not dragging) — a light tint (same hue as the dragging fill
   below, lower opacity) instead of matching the page background exactly, so
   the zone reads as a distinct drop target even before a drag starts.
   .tray-pill-hover shares this fill too — see the box rule above for why —
   but never the "גררו..." hint text, which stays tied to :empty::after
   alone (below) so it can't show once the zone actually has pills in it. */
#page9ZoneAbove:empty,
#page9ZoneAbove.tray-pill-hover {
  background: rgba(247, 246, 250, 0.6);
}

/* Tinted fill once actively dragging — restored for the non-empty case too
   (higher specificity than the :empty rule above, so it correctly wins over
   the page-bg fill on the empty+dragging overlap as well). A bit darker
   (fill and border-color both) than the idle state above, so the zone reads
   as more emphatically "live" once a drag is actually in progress. */
.page9-sticky.dragging #page9ZoneAbove {
  background: rgba(243, 242, 248, 0.7);
  border-color: rgba(0, 0, 0, 0.11);
}

/* :empty alone (not gated on .dragging) — shows from the very start, before
   any drop, and disappears the moment the first pill actually lands here
   (since it'd otherwise sit underneath/behind the dropped pills). */
#page9ZoneAbove:empty::after {
  content: "גררו סוגי פעולות קיצוניות לכאן";
  position: absolute;
  top: 50%;
  left: 50%;
  transform: translate(-50%, -50%);
  font: 500 15px var(--font-body);
  color: rgba(0, 0, 0, 0.5);
  white-space: nowrap;
  pointer-events: none;
}

/* Desktop only: one step up from the shared 15px. Scoped above the 600px
   breakpoint so the mobile band — where the zone is narrower and the hint has
   far less room — keeps the smaller size. */
@media (min-width: 601px) {
  #page9ZoneAbove:empty::after { font-size: 16px; color: rgba(0, 0, 0, 0.25); }
  /* Desktop-tuned subtitle (harness); mobile keeps the base 500 / 0.25. */
  .page9-header-subtitle { font-weight: 400; color: rgba(0, 0, 0, 0.35); }
}

/* Dropped-chip chrome, per a series of explicit requests that moved it away
   from Figma's own dropped-chip node (136:433215) and toward matching the
   tray pill (187:15505/201:49246) instead:
   - Text (font-size, color) is no longer overridden — inherits the base
     .page9-pill rule, same 20px/rgba(0,0,0,0.5) as the tray.
   - The top/bottom stroke's darkness now matches the tray's own border
     exactly (rgba(130,130,130,0.3) — see ::before/::after below), not
     Figma's fainter 0.14.
   - white-space:nowrap is still needed: --page9-zone-stack-width (set in
     p9MeasureTrayLayout, page9.js) is measured against the tray pill's own
     width, and this rule's padding (10px) still differs from the tray's
     (12px), so a dropped label can still be wider than that budget.
   What's still genuinely different from the tray pill: top/bottom-only
   stroke (no left/right, no corner radius — the tray's 4px rounding is a
   base-class inherit that doesn't apply once there's no boxed border to
   round), no drop-shadow, and a transparent fill instead of the tray's
   white (a fixed opaque fill would visibly clash once the zone's own
   background tints during a drag — see .page9-sticky.dragging
   #page9ZoneAbove above).
   The stroke itself is drawn via ::before/::after (a 1px solid bar), not
   `border`/`border-image` — tried border-image first, but border-image
   rendering with asymmetric per-side widths (nonzero top/bottom, 0
   left/right) is a known cross-browser weak spot (notably Safari, the only
   browser available in this dev environment) and rendered wrong. A plain
   background-color bar has no such ambiguity.
   Untapered (flat rgba, no gradient) per explicit request. Only the very
   first pill draws its own ::before (top line) — every other pill only
   draws ::after (bottom line) — since #page9ZoneAbove now stacks pills with
   no gap (see its own rule above): each pill's bottom line already sits
   exactly where the next pill's top line would, so drawing both there would
   double-composite two translucent lines into a visibly darker seam instead
   of reading as the one line it should be. */
#page9ZoneAbove .page9-pill {
  /* Sizes the PARKED ✕ affordance further down (.page9-x-affordance): the glyph
     box, and what the grip it would replace costs (8px grid + the row's 6px
     gap). Unused while the option is off — the width below is then just the
     plain shared width. */
  --p9-x-size: 24px;
  --p9-x-slot: calc(var(--p9-x-size) + 6px);
  --p9-handle-slot: 14px;
  position: relative;
  border: none;
  border-radius: 0;
  white-space: nowrap;
  padding: 10px;
  background: transparent;
  box-shadow: none;
  /* Every dropped pill shares one fixed width (the tray's own longest label,
     הפגנה לא אלימה — --page9-max-pill-width, set once in
     p9MeasureTrayLayout, page9.js) instead of each sizing to its own
     shorter text, so the top/bottom lines below (spanning the pill's full
     width) all line up at one standard width rather than each pill having
     its own shorter line. */
  width: var(--page9-max-pill-width);
  /* Centers the label+handle group within that shared width instead of
     packing to the base .page9-pill flex-start default, which would leave
     the extra space (for any pill shorter than הפגנה לא אלימה) trailing to
     one side only. */
  justify-content: center;
}


/* ===== OPTIONAL: the ✕ affordance on a dropped (extreme) pill =============
   PARKED, not live. Everything in this block is gated on `.page9-x-affordance`,
   a class nothing currently adds — so a dropped pill ships exactly as it did
   before the ✕ existed: 6-dot grip on the right, no ✕. Kept whole because the
   decision is deferred, not made.

   TO TURN IT ON: add the class to .page9-sticky (in index.html's markup, or a
   one-liner in page9.js). That is the only change needed — the
   <span class="page9-pill-x"> is already built into every pill (p9BuildPanel,
   page9.js) and is display:none until these rules match.

   What it does when on: the ✕ REPLACES the 6-dot grip at the pill's right-hand
   end (order:-1 — lower order = further right in this RTL row), chosen by eye
   with a `compare/` harness on 2026-09-04 over two left-hand placements. The
   reasoning: a dropped pill's one gesture is a click that returns it to the
   tray, which is what a ✕ announces and what a "drag me" grip does not.

   It stays DECORATION, per explicit instruction — the whole pill remains the
   single click/drag target. Hence pointer-events:none: the pill's own
   pointerdown (which starts the drag) must keep firing over it. */
.page9-x-affordance #page9ZoneAbove .page9-pill .page9-pill-x {
  display: inline-flex;
  order: -1;
  align-items: center;
  justify-content: center;
  flex-shrink: 0;
  width: var(--p9-x-size);
  /* ZERO height, deliberately. A dropped pill is auto-height, so it takes the
     height of its tallest item — and a 24px ✕ box is taller than the label's
     own line box, which grew the whole pill. At 0 it contributes nothing on the
     cross axis: the pill's `align-items: center` centres this empty box on the
     label's line and the glyph overflows it evenly. Width still counts, which
     is the point — the ✕ takes horizontal room, never vertical. */
  height: 0;
  pointer-events: none;
  font: 400 var(--p9-x-size)/1 var(--font-body);
  color: currentColor;
  opacity: 0.55;
}

/* The grip stands down where the ✕ takes over — two icons at the same end
   would say two different things. Dragging still works either way; it just
   stops advertising itself. */
.page9-x-affordance #page9ZoneAbove .page9-pill .page9-handle {
  display: none;
}

/* Hover takes the ✕ fully black: it is what states what the hover is offering,
   so it steps forward while the rest of the pill holds still. Both properties,
   not just `color` — at rest it is 0.55 opacity over a half-black label, so
   recolouring alone barely registers. No transition, matching the pill's own
   chrome (only its box-shadow moves on hover). */
.page9-x-affordance #page9ZoneAbove .page9-pill:hover .page9-pill-x {
  color: #000;
  opacity: 1;
}
/* ===== end optional ✕ ==================================================== */

/* Brighter (lighter) than the tray's own handle dots (.page9-pill
   .page9-handle span, rgba(0,0,0,0.4)) — lower opacity black reads as
   brighter/lighter against the page's light background. */
#page9ZoneAbove .page9-pill .page9-handle span {
  background: rgba(0, 0, 0, 0.25);
}

#page9ZoneAbove .page9-pill::before,
#page9ZoneAbove .page9-pill::after {
  content: "";
  position: absolute;
  left: 0;
  right: 0;
  height: 1px;
  background: rgba(130, 130, 130, 0.3);
  pointer-events: none;
}

#page9ZoneAbove .page9-pill::before {
  top: 0;
  display: none;
}
#page9ZoneAbove .page9-pill:first-child::before { display: block; }
/* A pill's "top line" is normally the *previous* pill's own ::after (shared
   seam, no gap between them — see #page9ZoneAbove's own comment above) —
   but that previous pill goes invisible (opacity:0) the instant it's picked
   up to drag (.dragging, page9.js), taking its ::after down with it. This
   pill falls back to showing its own ::before for as long as that's
   happening, so the line doesn't just vanish out from under it. */
#page9ZoneAbove .page9-pill.dragging + .page9-pill::before { display: block; }
#page9ZoneAbove .page9-pill::after { bottom: 0; }

/* Toggled by p9HoverInit (page9.js) on whichever dropped pill matches the
   currently-hovered dot's category (also fires when the pill itself is what
   triggered the hover, see zoneAboveEl's own pointerover, page9.js) — text
   goes fully black, calling out which category the hovered dot belongs to.
   Its own handle dots and top/bottom lines darken along with it, same idea. */
#page9ZoneAbove .page9-pill.is-hover-highlighted {
  color: #000;
}

#page9ZoneAbove .page9-pill.is-hover-highlighted .page9-handle span {
  background: rgba(0, 0, 0, 0.55);
}


#page9ZoneAbove .page9-pill.is-hover-highlighted::before,
#page9ZoneAbove .page9-pill.is-hover-highlighted::after {
  background: rgba(0, 0, 0, 0.5);
}

/* The line visually "above" a non-first pill isn't its own ::before (only
   :first-child draws one, see that rule above) — it's the *previous* pill's
   own ::after, shared at the seam where they touch with no gap. CSS has no
   previous-sibling selector, but :has() can look at a *following* sibling —
   "the pill whose immediately-next sibling is hover-highlighted" is exactly
   the pill sitting above the hovered one, so darken its ::after too. */
#page9ZoneAbove .page9-pill:has(+ .page9-pill.is-hover-highlighted)::after {
  background: rgba(0, 0, 0, 0.5);
}

/* .has-hover-highlight (also toggled by p9HoverInit, on the zone itself) dims
   every OTHER dropped pill's text a touch below its normal rgba(0,0,0,0.4)
   while one is singled out above, for a bit more contrast against it. */
#page9ZoneAbove.has-hover-highlight .page9-pill:not(.is-hover-highlighted) {
  color: rgba(0, 0, 0, 0.25);
}

/* Extreme zone only — the legit zone below stays a valid drop target (you can
   still drag a pill back down) but no longer shows this hover highlight while
   dragging over it. Selector needs both .dragging and .dragover (rather than
   just .dragover) to out-specificity the base .dragging fill/border above —
   a plain `#page9ZoneAbove.dragover` rule has lower specificity than
   `.page9-sticky.dragging #page9ZoneAbove` and would silently lose to it. */
.page9-sticky.dragging #page9ZoneAbove.dragover {
  background: rgba(239, 238, 244, 0.78);
  border-color: rgba(0, 0, 0, 0.2);
}

/* Hint text turns fully black for the whole time a pill is being dragged
   (any drag, not just once hovered directly over this zone) — per a later
   explicit request superseding the note this comment used to carry (the
   text staying unchanged on hover). Needs the same .dragging specificity
   bump as the fill/border rules above to beat the base
   #page9ZoneAbove:empty::after color rule. */
.page9-sticky.dragging #page9ZoneAbove:empty::after {
  color: #000;
}

.page9-pill {
  display: flex;
  align-items: center;
  gap: 6px;
  font: 600 20px var(--font-body);
  color: rgba(0, 0, 0, 0.5);
  background: #fff;
  border: 1px solid rgba(130, 130, 130, 0.3);
  border-radius: 4px;
  padding: 12px;
  box-shadow: 0 1px 1.1px rgba(0, 0, 0, 0.07);
  cursor: grab;
  /* All three, not just the unprefixed one: iOS WebKit still reads
     -webkit-user-select, and a press-and-hold on a pill was raising a text
     selection (with handles and the Copy bar) over the drag instead of picking
     the pill up. -webkit-touch-callout kills the callout menu the same gesture
     summons. Same suppression the graphic column already carries for the
     @fold8 loupe. */
  -webkit-touch-callout: none;
  -webkit-user-select: none;
  user-select: none;
}

/* Matches Figma's text-box-trim:trim-both/text-box-edge:cap_alphabetic on this
   label — trims the line box down to the font's cap-height/alphabetic baseline
   instead of the browser's default leading, so the 10px padding above/below
   reads as tight as Figma's own measurement assumes. No fallback needed where
   unsupported (older Safari) — it's just ignored, leaving the old (slightly
   more generous) default leading. */
.page9-pill-label {
  text-box-trim: trim-both;
  text-box-edge: cap alphabetic;
}

.page9-tray .page9-pill:hover {
  box-shadow: 0 2px 8px rgba(0, 0, 0, 0.15);
}

.page9-pill:active {
  cursor: grabbing;
}

/* :focus-visible, not :focus — the pill is keyboard-operable (role="button" +
   tabIndex, see p9BuildPanel), and this is the only thing telling a keyboard
   user where they are. :focus alone would also ring it after a mouse press,
   which is why the browser distinguishes the two. Drawn with outline +
   offset rather than a border/box-shadow so it can't shift the pill's box —
   the tray's grid tracks are baked from measured pill widths
   (p9MeasureTrayLayout), and a focus style that changed the width would
   reflow the row under the reader's cursor. */
.page9-pill:focus-visible {
  outline: 2px solid #1a1a1a;
  outline-offset: 3px;
  border-radius: 4px;
}

.page9-pill.dragging {
  opacity: 0;
  cursor: grabbing;
}

/* THE VACANCY — the tray cell a pill has been moved out of (p9SyncVacancy,
   page9.js). The pill's footprint pressed faintly into the paper, so the gap
   in the row reads as "something used to be here" instead of as a stray space.
   No border, no text: picked in a compare/ pass over a dashed frame, a frame
   keeping the category name, a single dashed underline, and the faded pill.

   0.0275 is one flat alpha, not a 0.05 fill behind a 0.55 opacity as the
   harness drove it — the two composite identically over the page (measured:
   rgb(246,245,248) on the #FDFCFF paper), and a single value can't be read as
   two independent knobs later.

   `justify-self: stretch` is load-bearing: the tray row sets its own
   `justify-items`, so the default `auto` collapses this to its own width
   (32px in a 123px track) and the hole stops matching the pill that left.

   Desktop only, and it can't be otherwise — under 600px pills never leave the
   tray, so no cell is ever vacated (p9SyncExtremeOrder). */
.page9-pill-vacancy {
  grid-row: 1;
  justify-self: stretch;
  width: 100%;
  box-sizing: border-box;
  border-radius: 4px;             /* the pill's own radius */
  background: rgba(0, 0, 0, 0.0275);
  pointer-events: none;
}

/* The actual visual dragged around under the cursor (see the pointerdown handler
   in page9.js) — a free-floating clone, since the real pill stays put in the DOM
   (just hidden) until a drop actually lands. Appended to <body>, which isn't RTL,
   so direction must be set explicitly here or the handle flips to the wrong side. */
.page9-pill-ghost {
  position: fixed;
  z-index: 1000;
  pointer-events: none;
  direction: rtl;
  box-shadow: 0 3px 10px rgba(0, 0, 0, 0.19);
  cursor: grabbing;
}

/* 6-dot grip handle (2 cols x 3 rows) — exact match to Figma's Frame 3186 icon
   (viewBox 8x15: 3px dots, 2px column gap, 3px row gap, #959595 @ 38%). A normal
   flex item (not absolutely positioned) so it shares the same vertical-centering
   line as the label — no font-metric guesswork needed. */
.page9-pill .page9-handle {
  flex-shrink: 0;
  display: grid;
  grid-template-columns: repeat(2, 3px);
  grid-template-rows: repeat(3, 3px);
  gap: 3px 2px;
}

/* The ⓘ affordance is built into every pill but is mobile-only — desktop
   already exposes the description on hover, so here it is purely hidden.
   Two-class selector so the ≤600px rule that reveals it can match this
   specificity (a media query adds none of its own). */
.page9-pill .page9-pill-info {
  display: none;
}

/* Same deal for the selection circle (page9.js): built into every pill, but
   desktop shows the classification by MOVING the pill into #page9ZoneAbove, so
   a checkbox beside the label would be a second, redundant readout. Mobile,
   where the pill never leaves the tray, is the only place it says anything.
   Two-class selector for the same specificity reason. */
.page9-pill .page9-pill-check {
  display: none;
}

/* The pill's place in the flex row. Published per pill as --p9-order-row by
   page9.js (1 = rightmost in the RTL band) rather than set inline, because
   mobile needs a SECOND sequence for @fold11's column and `order` can only hold
   one at a time — see --p9-order-col in the 600px block. Inert on desktop,
   whose two tray grids place every pill explicitly and ignore `order`. */
.page9-pill {
  order: var(--p9-order-row, 0);
}

/* Decorative ✕, built into every pill (page9.js) but PARKED — the only rule
   that would reveal it is gated on `.page9-x-affordance`, which nothing adds
   (see the "OPTIONAL" block above). So today this is simply always hidden.
   Two-class selector to match that reveal rule's specificity, same reason as
   the ⓘ above. */
.page9-pill .page9-pill-x {
  display: none;
}

.page9-pill .page9-handle span {
  width: 3px;
  height: 3px;
  border-radius: 50%;
  /* Close to the pill's own label text color (.page9-pill, rgba(0,0,0,0.5))
     but a touch lighter, per explicit request — still darker than the
     original rgba(149,149,149,0.38) it replaced. */
  background: rgba(0, 0, 0, 0.4);
}

/* Hover tooltip for a single event dot in page-13's extreme/legit grid (Figma
   node 158:41181) — date + short Hebrew description. Stroke color is set
   per-hover in JS (p9HoverInit, page9.js) to match the hovered dot's own
   group color, so the `color` value here is just a fallback/default. The
   bottom-left corner is deliberately square (not rounded like the other
   three) — that's the design's anchor point, positioned right at the hovered
   dot.

   The dash matches the title cards' (.text-card-frame) exactly — 2px stroke,
   2px-dash/2px-gap — and is drawn the same way, and for the same reason: a
   native `border: 2px dashed` renders the browser's own much looser dash
   spacing. So the border here is transparent (it only holds the box-model
   spacing open) and the visible stroke is an absolutely positioned inline
   <svg> child (.page9-tooltip-dash, built/sized by updateTooltipDash() in
   js/core.js on every hover) whose viewBox is set 1:1 to the box's own pixel
   size, so the dash can't be scaled or squashed by the box's aspect ratio.
   Unlike the title cards' plain rounded rect, this one is a <path> — it has
   to carry the one square anchor corner (and swap which corner that is for
   .is-mirrored below). It strokes `currentColor`, which is why JS sets
   `color` rather than `border-color`. */
.page9-tooltip {
  --tip-border-w: 2px;
  position: fixed;
  z-index: 1000;
  pointer-events: none;
  display: none;
  direction: rtl;
  background: #fff;
  color: #f16f16;
  border: var(--tip-border-w) solid transparent;
  border-radius: 8px 8px 8px 0;
  padding: 7px 10px;
  /* Widened from 222px at the same time the text went to 14px — a bigger face
     in the old box wrapped the descriptions onto more lines. 275px (≈251px of
     text after the 10px side padding + 2px borders) is sized so @hidden-hover's demo
     description — row-34, 109 characters, FOLD6_SQUARE_ROW_IDS[0] in js/groups.js —
     lands on exactly 3 lines. Mobile keeps 222 (it clamps against the viewport
     anyway, see the ≤600px block). */
  width: 275px;
  flex-direction: column;
  align-items: flex-end;
  gap: 4px;
}

.page9-tooltip.is-visible {
  display: flex;
}

/* Mirrored variant for events on the left-side block of the extreme/legit
   grid (event.side === "left") — the tooltip should open toward the left of
   the dot, not the right, so it doesn't reach across the canvas's center gap
   toward the opposite side's column. Swaps the anchor ("pointer") corner from
   bottom-left to bottom-right; positioning (which edge of the box sits at the
   dot) is computed in JS, see p9HoverInit in page9.js. */
.page9-tooltip.is-mirrored {
  border-radius: 8px 8px 0 8px;
}

/* Flipped variant (desktop @fold12 only, p9HoverInit): when opening upward
   would poke above the column area's ceiling, the box hangs BELOW the dot, so
   the square anchor corner moves to the top edge — top-left normally,
   top-right when also mirrored. updateTooltipDash squares the same corner in
   the SVG stroke. */
.page9-tooltip.is-flipped {
  border-radius: 0 8px 8px 8px;
}

.page9-tooltip.is-mirrored.is-flipped {
  border-radius: 8px 0 8px 8px;
}

/* Same correction as .text-card-frame-dash: an abs-pos element's containing
   block is its nearest positioned ancestor's PADDING box, not its border box,
   so plain `inset: 0` would size this --tip-border-w smaller than the
   tooltip's real border-box on every side and leave a ring of bare white fill
   outside the dash. */
.page9-tooltip-dash {
  position: absolute;
  top: calc(-1 * var(--tip-border-w));
  left: calc(-1 * var(--tip-border-w));
  width: calc(100% + 2 * var(--tip-border-w));
  height: calc(100% + 2 * var(--tip-border-w));
  pointer-events: none;
}

/* 14px on desktop — up from the original 12px, chosen by eye against 12/13/15.
   Mobile's ≤600px block still overrides both rules to 15px (the pill tooltip,
   .page9-cat-tooltip-desc, is 15px everywhere). The box was widened to 250px at
   the same time so the bigger face doesn't wrap descriptions onto more lines. */
.page9-tooltip-date {
  font: 700 14px var(--font-body);
  color: #000;
  text-align: right;
  width: 100%;
}

.page9-tooltip-desc {
  /* 550, not 400 — a real weight off the `Assistant` VARIABLE font (loaded at
     wght@300..800, index.html:10), picked by eye against the group-colour
     fill below, where plain 400 in white went thin and hazy. Applies on mobile
     too, where the same text sits dark on white and simply reads a touch
     sturdier. Any value outside 300-800 would be browser-synthesised — keep it
     in range. */
  font: 550 14px var(--font-body);
  color: #000;
  text-align: right;
  width: 100%;
}

/* Event-tooltip look: the box is FILLED with the event's own group colour,
   carries no stroke at all, and its text is white.

   `currentColor`, not a palette lookup — JS already writes the group colour to
   this element's `color` per event (fold8TooltipEl.style.color in
   js/update-groups.js for @hidden-hover's scripted demo, p9HoverInit in page9.js for
   @fold12's hover), so the fill picks it up for free and keeps lerping in
   exactly the way the stroke used to, rather than snapping.

   The 2px border stays but stays transparent: it holds the box-model spacing
   open, so fold8PositionTooltip's geometry is unchanged.

   Every width, desktop and mobile alike. The two fixtures are still different
   SHAPES — desktop is a free-floating box anchored to the dot it describes,
   mobile is the docked frame (.is-docked, with its own picker and expanded
   states) — but they now share one look. Mobile's own bit of type,
   .p7-tip-more, is re-toned to white alphas in the ≤600px block to sit on the
   fill.

   The dash <svg> is still BUILT by updateTooltipDash() on every hover — it's
   only hidden here, and `color` is still the TRUE (undarkened) group colour on
   the element, so restoring a stroked variant needs nothing but deleting the
   `display: none`. */
.page9-tooltip {
  /* --tip-fill, not currentColor: JS writes the event's group colour to both,
     but --tip-fill has passed through tooltipFill() (js/core.js), which
     darkens the two group colours too light to carry white text (#F9B624 and
     #31CE1C, ~1.7:1 and ~1.9:1 against white) down to the ~3.2:1 the other
     four already have. currentColor stays the TRUE group colour. The fallback
     keeps this correct if a code path ever sets `color` without the
     property. */
  background: var(--tip-fill, currentColor);
}

.page9-tooltip-date,
.page9-tooltip-desc {
  color: #fff;
}

.page9-tooltip-dash {
  display: none;
}

/* The mobile event picker's two elements (built unconditionally by
   p7InspectInit, page7.js) are hidden outright above the 600px breakpoint —
   desktop has hover and must be pixel-identical. Everything that gives them a
   look lives in the media query at the bottom of this file. */
.p7-inspect-hint,
.p7-tip-more,
.p7-loupe,
/* The picker's instruction band is mobile-only — desktop has hover and needs no
   gesture label. Everything that gives it a look is in the ≤600px block. */
.p7-hint-band { display: none; }

/* Hover tooltip for a category pill in page-13's tray (#page9ZoneBelow only —
   dropped pills in the extreme zone get a different hover treatment, see
   .page9-pill.is-hover-highlighted above) — a short description of what the
   category covers. The arrow (.page9-cat-tooltip-arrow) is a separate element
   from the box, but both sit under this one positioned wrapper so a single
   `filter: drop-shadow` renders as one shadow around their combined
   silhouette — two independent box-shadows would leave a visible seam/double
   shadow right where the triangle meets the box. */
.page9-cat-tooltip {
  position: fixed;
  /* Above EVERY title block, per explicit instruction. The mobile ladder it has
     to clear: the docked event tooltip is 1000, the cards that share the screen
     with it are bumped to 1001, and the open מקרא panel to 1002 — so this sits
     at 1003. It's a direct .layout child (index.html), which is what makes
     the number bite at all: nested in .graphic-col it would be trapped in that
     stacking context. It's `pointer-events: none`, so painting on top of a card
     costs the card nothing. Raised past that whole ladder — and past the drag
     ghost's mobile 1005 — because in desktop V2 the tooltip hangs BELOW its
     pill (p9CategoryTooltipInit), straight over the drop zone and whatever is
     docked in it, and must stay legible on top of all of it. */
  z-index: 1006;
  pointer-events: none;
  display: none;
  width: 200px;
  background: #fff;
  border-radius: 8px;
  padding: 10px 14px;
  filter: drop-shadow(0 2px 6px rgba(0, 0, 0, 0.1));
}

.page9-cat-tooltip.is-visible {
  display: block;
}

.page9-cat-tooltip-desc {
  font: 400 15px var(--font-body);
  color: #000;
  text-align: center;
  direction: rtl;
}

/* A square rotated 45deg, half-tucked behind the box's bottom edge (same
   fill, so the tucked half is invisible) — only the exposed bottom half pokes
   out as the pointer triangle. .page9-cat-tooltip's own position:fixed makes
   it the containing block for this absolutely-positioned child, no extra
   position:relative needed. */
.page9-cat-tooltip-arrow {
  position: absolute;
  left: 50%;
  bottom: -6px;
  width: 12px;
  height: 12px;
  background: #fff;
  transform: translateX(-50%) rotate(45deg);
}

/* Flipped: on mobile the tray is a band near the TOP of the screen, so there's
   no room above a pill and the box drops below it instead (see the `is-below`
   branch in p9CategoryTooltipInit). The arrow has to move to the box's top
   edge with it, or it would point away from the pill it belongs to. */
.page9-cat-tooltip.is-below .page9-cat-tooltip-arrow {
  bottom: auto;
  top: -6px;
}

/* Title cards, DESKTOP: Discordia Regular (400), 18.5px — the _debug-typeface.js
   harness pick of 2026-09-27 (over Hadassah, Days, Fedra Serif Pro, Lava Pro).
   The ≤600px block keeps the PHONE on HadassahFriedlaender Thin 100 / 16px
   (never tuned on a phone — a desktop pick must not touch mobile). Both local
   families are declared at the top of this file. line-height 1.5 opens up
   the multi-line Hebrew cards. This is the shared
   base for ALL title cards; no page should override font-size/weight here
   individually.

   TO GO BACK TO HADASSAH (the previous face, still in fonts/ with its
   @font-face rules at the top of this file):
     1. this rule:  font: 100 20px/1.5 'HadassahFriedlaender', serif;
        (Thin 100 — the real Thin OTF; 16px mobile override is unchanged)
     2. js/bootstrap.js: swap the Plex preload back to
        document.fonts.load("100 16px 'HadassahFriedlaender'"),
     3. index.html: drop the candidate family from the Google Fonts link
        (optional — harmless if left). */
.section-title {
  font: 400 18.5px/1.5 var(--font-title); /* 18.5: title-size compare pick 2026-09-27 (was 18) */
  color: var(--ink);
  margin: 0 0 8px;
}

/* Left at the base weight deliberately. This rule used to force 400 to dodge
   the synthetic-bold artifact (a capital "A"'s apex filling into a solid
   wedge) back when .section-title asked for a faked 600. At the light weight
   the title renders the real Thin face with no synthesis at all, so there is
   no artifact to dodge — and a 400 acronym would now sit HEAVIER than the
   Hebrew around it. Only ever applied to the ACLED acronym on the @hidden-acled
   card. */
.latin-acronym {
  font-weight: inherit;
}

/* Device-dependent card copy. Only @hidden-hover needs it today: desktop hovers a
   square, a phone presses and holds, so the instruction is a different
   sentence rather than a reworded one. display:none (not visibility) so the
   hidden variant contributes nothing to .text-card-frame's fit-content width. */
.copy-mobile { display: none; }

/* The ACLED acronym on the @hidden-acled card is a real external link (teacher review
   2026-09-03, E2): visibly underlined so it signals "go check the source". */
.card-link {
  color: inherit;
  text-decoration: underline;
  text-underline-offset: 0.12em;
  text-decoration-thickness: 1px;
}
.card-link:hover,
.card-link:focus-visible {
  text-decoration-thickness: 2px;
}

/* Normal-flow, horizontally-centered text block for every page's scrolled
   text, replacing both the old right-aligned sidebar AND the later
   position:fixed/pinned-center treatment — text now scrolls continuously
   with the page instead of snapping visible/invisible at a fixed screen
   position. Centering comes from the parent (.text-section/the per-page
   sticky wrappers are flex containers with align-items:center) plus margin:auto here so it
   stays centered without taking over layout. No fade — the card is always
   visible and simply scrolls with the page. The dashed-box visual treatment
   lives on .text-card-frame below, since per Figma (node 119:997 vs 119:969)
   the box wraps only the title, not sibling content below it. */
.text-card {
  /* A definite width (not just max-width) matters here: as a flex item this
     box would otherwise shrink-to-fit its widest child, so any full-width
     descendant would resolve against an ambiguous auto width. A fixed width
     gives every descendant the same definite box to measure against. */
  width: var(--card-w);
  margin: 0 auto;
  direction: rtl;
  text-align: right;
}

/* The actual dashed-border white card chrome (Figma node 119:997: 4px dashed
   black border, white fill, 8px radius, 21px/29px padding — stroke thinned to
   1.5px per explicit instruction (harness pick 2026-09-22; was 2px), overriding the Figma-spec 4px;
   2px at both breakpoints — the ≤600px block sets no override) — applied only
   to the title, not to sibling content below it.

   Native `border: 4px dashed` renders the browser's own loose dash spacing
   (dash/gap noticeably longer than 4px), not Figma's exact 2px-dash/2px-gap
   repeat (measured by pixel-sampling the Figma export). A `border-image`
   9-slice was tried first but proved unreliable across `repeat`/`round`
   combinations — these cards are much wider than they are tall, and the
   short top/bottom-vs-left/right edges scaled (and in some engines visibly
   squashed) very differently from each other regardless of which repeat
   mode was used on the short axis. The dash is drawn instead via a plain
   transparent border (preserves the box-model spacing) plus an absolutely
   positioned inline `<svg>` child (`.text-card-frame-dash`, inserted/sized by
   updateTextCardFrameDashes() in js/core.js) whose viewBox is set to the frame's
   own actual pixel size every time it's (re)measured — a true 1:1
   viewBox-to-CSS-px mapping on both axes, so the dash/radius can't be
   scaled or distorted no matter the box's aspect ratio. */
.text-card-frame {
  --frame-border-w: 1.25px;   /* both breakpoints; must match FRAME_STROKE_W_* (js/core.js) */
  position: relative;
  /* display:block + width:fit-content + margin:0 auto centers the box itself
     within .text-card regardless of content width — display:inline-block
     instead let the parent's text-align:right (set for the RTL text inside)
     also flush the box itself to the right edge, which is invisible for
     titles long enough to wrap near the card's full 480px width but visibly
     off-center for short, single-line titles. Inner text stays right-aligned since text-align is
     inherited independent of the box's own display/positioning. */
  display: block;
  width: fit-content;
  margin: 0 auto;
  background: #fff;
  border: var(--frame-border-w) solid transparent;
  border-radius: 8px;
  padding: 21px 29px;
}

/* An abs-pos element's containing block is its nearest positioned ancestor's
   PADDING box, not its border box — plain `inset: 0` would size this 4px
   (the frame's own border-width) smaller than the frame's actual visible
   border-box on every side, leaving a ring of bare white fill between the
   dash and the frame's true edge. Expanding by --frame-border-w on all
   sides corrects for that, so this overlay's rendered CSS size (which
   updateTextCardFrameDashes() in js/core.js reads via offsetWidth/offsetHeight
   to set its viewBox) actually matches the frame's real border-box. */

/* @fold12's ghost frame (both breakpoints; js/page8-9-scroll.js): a clone of the title
   block's own <h2>, parked at the top of the screen. No transition — JS writes
   its opacity every frame of the fade. */
.text-card-frame.fold12-ghost {
  position: fixed;
  left: 50%;
  transform: translateX(-50%);
  box-sizing: border-box;
  margin: 0;
  z-index: 4;
  pointer-events: none;
}

.text-card-frame-dash {
  position: absolute;
  top: calc(-1 * var(--frame-border-w));
  left: calc(-1 * var(--frame-border-w));
  width: calc(100% + 2 * var(--frame-border-w));
  height: calc(100% + 2 * var(--frame-border-w));
  pointer-events: none;
}

/* The single overlay holding all 9 groups across the legend (fold 2) through
   fold 5 — one persistent set of .group-item nodes, continuously
   repositioned/restyled rather than separate per-fold overlays crossfading
   into each other (see GROUPS in js/groups.js / updateGroups in
   js/update-groups.js). Visibility is
   t-gated by that same code, not page-gated. */
/* Invisible hit boxes over the two mini-legend columns — hovering either one
   types every group label back in (js/groups.js fold6LabelHoverTrigger).
   Geometry is written per frame by updateGroups because the rows themselves
   are laid out in JS against the live viewport. Desktop only; hidden until
   @fold4's glide has begun. */
.fold6-legend-hover {
  position: fixed;
  z-index: 3;
  display: none;
}

/* A direct .layout child (index.html), NOT nested in .graphic-col: that column
   is its own stacking context, so a group row inside it could never climb above
   the מקרא bar's layer — and on mobile the groups have to paint OVER the legend
   (see the mobile stack comment in the 600px block). `position: fixed; inset: 0`,
   so where it lives in the tree changes nothing about where its rows land.
   z-index 0 here is the DESKTOP behaviour, unchanged: it sits after .graphic-col
   and before .text-col in source order, so it still paints over the canvas and
   under the title cards exactly as it did from inside the column. */
.groups-overlay {
  position: fixed;
  inset: 0;
  z-index: 0;
  pointer-events: none;
  opacity: 0;
  transition: opacity 200ms ease-out;
}

/* Holds only the fold6 source-credit note + its divider (js/groups.js reparents
   fold6NoteEl/fold6NoteDividerEl here instead of .groups-overlay) — a direct
   .layout child, not nested in .graphic-col, so its z-index actually stacks
   above .text-col (see #page9CatTooltip for the same
   pattern). Same fixed/inset/pointer-events convention as .groups-overlay;
   only .fold6-note-link inside re-enables pointer-events so it's clickable
   while every other pixel still passes clicks through to .text-col. */
.fold6-note-layer {
  position: fixed;
  inset: 0;
  z-index: 2;
  pointer-events: none;
}

.groups-overlay.is-active {
  opacity: 1;
}

/* 10 static squares (Figma node 120:1279) that fade in at the center as the
   cluster moves into fold6's left mini-legend — opacity is set directly
   inline (e6, the same progress driving the legend's own move), no CSS
   transition needed since the JS value is already continuous. Each square
   gains an action-type label once fold 8's timeline-intro is reached (node
   120:1299), then loses it again — and 5 of them gain a group color — in
   fold 9 (node 162:63876) — .fold6-square-wrap is the zero-size anchor
   (same convention as .group-item: its left/top IS the square's own
   position), so the label can be positioned relative to it without
   re-measuring anything. Both label opacity and square color are driven
   continuously from JS (updateGroups in js/update-groups.js), so neither gets a CSS
   transition here — same reasoning as the overlay's own opacity above. */
.fold6-squares-overlay {
  position: fixed;
  inset: 0;
  z-index: 0;
  pointer-events: none;
  /* The 8 sample squares are DOM, not canvas, so the timeline's own clip cannot
     reach them — they printed over the picker's hint band while the camera
     carried them up off the top of the chart. `clip-path`, not `top`: an inset
     clip hides without moving, where shifting the overlay's edge would move
     every square's coordinate origin with it. 0 everywhere but the folds that
     raise the band (p7HintBandApply, page7.js). */
  clip-path: inset(var(--sq-clip-top, 0px) 0 0 0);
}

.fold6-square-wrap {
  position: absolute;
}

.fold6-square {
  position: absolute;
  left: 0;
  top: 0;
  width: 8px;
  height: 8px;
  background: var(--muted-ui);
  transform-origin: center;
  /* Resting opacity 0 for the same reason .groups-overlay stays off its
     `is-active` until bootstrap.js's first layoutGroups(): .fold6-square-wrap
     carries no left/top until that layout, so every square resolves to the
     overlay's origin and all 8 paint stacked against the viewport's left edge
     for the ~70ms bootstrap.js spends awaiting document.fonts.load(). Safe to
     default off because updateGroups writes an inline opacity on every square
     on every frame (js/update-groups.js), so nothing has to turn this back on. */
  opacity: 0;
}

/* @hidden-hover fake cursor (desktop). A drawn pointer, not the real one: it glides
   from the 8 squares' centre to square 0 on fold7CursorTrigger (js/groups.js),
   positioned + faded by JS every frame (js/update-groups.js) — so no
   transition on transform/opacity. Its hot spot is FOLD7_CURSOR_TIP.
   A direct .layout child so it paints above the canvas. `.is-dismissed` (a
   real hover took over) is a pure state flip, so it may transition. */
.fold7-cursor {
  position: fixed;
  left: 0;
  top: 0;
  /* The arrow's own aspect (viewBox 14.7 × 20.7), so the box hugs the glyph —
     no transparent margin around it. --cursor-size is its HEIGHT. */
  height: var(--cursor-size, 24px);
  width: calc(var(--cursor-size, 24px) * 0.71);
  pointer-events: none;
  z-index: 1006;
  background: no-repeat center / contain;
  opacity: 0;
}
.fold7-cursor { background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='3.9 1.9 14.7 20.7'%3E%3Cpath d='M4.5 2.5 L4.5 19.5 L9 15.3 L12.2 22 L15 20.7 L11.9 14.2 L18 14.2 Z' fill='%23111' stroke='%23fff' stroke-width='0.9' stroke-linejoin='round'/%3E%3C/svg%3E"); }
.fold7-cursor.is-dismissed {
  visibility: hidden;
}

/* right: 8px — the anchor IS the square's left edge, so this offset is the
   real gap directly (same anchor-is-left-edge convention as .group-item). */
.fold6-square-label {
  position: absolute;
  top: 2.5px;
  right: 8px;
  transform: translateY(-50%);
  white-space: nowrap;
  font: 16px var(--font-body);
  color: rgba(0, 0, 0, 0.9);
  opacity: 0;
}

/* Figma's intro screen (revised, node 217:9176 — was 167:90164, 146:105164) is a centered
   full-width cover, not the right-aligned 480px sidebar that pages 1-7 use — so
   #page-0's content scrolls in normal flow like every other fold's title
   block, rather than a fixed full-viewport overlay faded by currentPage —
   .page0-overlay is just #page-0's own content box now, sized to exactly one
   viewport height so the center-anchored math below (left/top or
   bottom: calc(50% ± Npx), matching how Figma's own frame positions
   everything relative to its vertical center) still lands the same way it
   did when that box was the live viewport. */
.page0-overlay {
  position: relative;
  width: 100%;
  height: 100vh;
  pointer-events: none;
}

/* The dot columns' fixed-to-viewport layer (see the comment above
   buildPage0AllDots in page1.js for why this isn't normal-flow content) —
   same position:fixed/inset:0/z-index:0/pointer-events:none convention as
   .groups-overlay. Opacity is set every frame from updateGroups
   (js/update-groups.js)
   (1 - fold2Trigger's progress), not a CSS transition, same reasoning as
   every other continuously-JS-driven opacity in this file. */
.page0-dots-overlay {
  position: fixed;
  inset: 0;
  z-index: 0;
  pointer-events: none;
}

/* Each dot itself — see buildPage0AllDots in page1.js, which sets every
   one's top/left/background inline. position:absolute resolves against
   .page0-dots-overlay (its nearest positioned — fixed counts — ancestor). */
.page0-dot {
  position: absolute;
  width: 7px;
  height: 7px;
}

/* Overrides .text-section's default 0 48px padding — the center-anchored
   math the page0-* rules below use needs .page0-overlay's box to be the
   *true* full viewport width, same as when it was position: fixed; inset: 0. */
#page-0 {
  padding: 0;
}

/* @fold13 (the closing statement, #page-14) and @fold16 (the outro/credits
   card, #page-17). Both share the .page12-sticky-center wrapper below, but
   only @fold16 actually pins with it — see the override under #page-14. */
#page-14 {
  /* Was 125vh: 100vh to bring the card up to rest flush at the top, then a
     quarter viewport of empty run before the next card starts rising. @fold16 (the outro) is a near-full-height
     outro card, not a one-line title, so at the house 100vh its top edge was
     already climbing into view while this card still sat mid-screen — "too
     close behind".
     The extra height only works as spacing because the wrapper below is made
     static for this fold. Left sticky, the same 150vh would pin the card dead
     centre for that quarter viewport instead of scrolling it away — which is
     exactly how a 200vh attempt read as "stuck". Height here is spacing only
     while the wrapper is static; sticky turns it into hold time.
     The spacing is SOLVED by p12SpacingFit() (page12.js) on the wrapper as
     50vh + half card 1, followed by one 100vh .page13-follow block — see
     there. Don't replace it with a fixed vh: any fixed value is off by the
     half-card it ignores (the fade/gate/freeze all measure offsetTop, not this
     height, so they don't care). */
  display: block;
  padding: 0;
}

/* The closing statement's card 2 (index.html). An ordinary house title
   block — centred in a 100vh run — placed after card 1's wrapper, whose
   height is solved to 50vh + half card 1 (p12SpacingFit). So card centres sit
   halfCard and 100vh + halfCard below the section top, and @fold14's partner
   card 200vh + halfCard: 100vh apart, house rhythm. The gate,
   fold13ScrollT and .frozen all measure #page-14's offsetTop / the wrapper's
   top, so the extra height moves none of them. Carries the gutter itself
   because #page-14 zeroes the .text-section padding. */
.page13-follow {
  min-height: 100vh;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 0 48px;
  box-sizing: border-box;
  direction: rtl;
  text-align: right;
}
@media (max-width: 600px) {
  .page13-follow {
    padding: 0 24px;
  }
}

/* @fold15 (#page-16, the share block) is an ordinary house fold — 100vh, card
   centred — see the #page-16 rule further down. Nothing here overrides it. */

/* Dead-scroll trim between @fold12 and @fold13: @fold13 waits directly under
   @fold12, with no empty run between them (explicit instruction) — at every
   width. The gate holds scroll at `#page-14.offsetTop - 100vh` — one viewport
   before the section — and that line can't move (any later and the section is
   already on screen while still locked). So the ONLY way to have nothing dead
   after the gate is for the card to be at the very top of its section: card
   top = section top means the card's first pixel is at the viewport's bottom
   edge at the exact scroll position the gate unlocks. It then rises a full
   viewport to rest flush at the top, and fold13ScrollT's fade spans exactly
   that rise. Any padding-top here re-opens the gap by its own height — that's
   what the old 25vh did, and centring the card in its 100vh wrapper (the old
   mobile geometry) opened half a viewport the same way.
   The section's height is solved as 50vh + half this card (p12SpacingFit,
   page12.js), so the share card's centre is exactly 100vh under this card's
   centre — house rhythm. On a phone the fixed מקרא bar and the docked
   event frame sit at the top edge where the card lands, but both are on
   fold13ScrollT's fade (updateFold13, js/fold11.js) and are gone by arrival. */
#page-14 .page12-sticky-center {
  align-items: flex-start;
  padding-top: 0;
  box-sizing: border-box;
  /* The shared wrapper is 100vh tall for the outro's sticky centring; here it
     is static and would hold #page-14 open to a full viewport regardless of
     the section's 45vh, keeping @fold15's share card a screen away. Let it
     hold card 1 plus the solved spacing: p12SpacingFit (page12.js) sets its
     height to 50vh + half card 1, which puts card 2's centre (in the first
     .page13-follow) exactly 100vh below card 1's. 50vh is the no-JS fallback.
     checkFold13 / p13SyncGateVisibility only read its top / opacity. */
  height: 50vh;
}

/* @fold13's card scrolls up and away like every other title fold's, rather
   than pinning: the sticky behaviour was only ever inherited from sharing
   .page12-sticky-center with the outro card, and nothing needs it here.
   checkFold13 watches @fold13's own wrapper now, and p13SyncGateVisibility's
   gate-hidden toggle is a plain opacity flip that works on any element.
   Kept 100vh + flex (top-aligned, see the trim above) so the card rests
   flush at the top on arrival, one viewport in — where fold13ScrollT()
   finishes. */
#page-14 .page12-sticky-center {
  position: static;
}

/* The outro (@fold16) is an ordinary title block, but it is the one card that
   is near-viewport-tall (100vh − 96px), so centring it in a 100vh section would
   leave only ~50vh of air under the share card — visibly short of the house
   gap. It is placed by its TOP edge instead: p12SpacingFit() (page12.js) sets
   this section's padding-top to 50vh − half the share card, which is where an
   ordinary centred card's top would land, and its min-height to just clear the
   card, so the document ends on it. That needs the card IN FLOW, hence the
   static wrapper below — a sticky wrapper ignores the padding and re-centres.
   No fixed drawer or map here — see the "Removed — don't reintroduce" note
   in CLAUDE.md / wiki/Folds.md. */
#page-17 {
  min-height: 100vh;
  display: block;
  padding: 0;
}
@media (min-width: 601px) {
  #page-17 .page12-sticky-center {
    position: static;
    height: auto;
  }
}

/* Sticky full-viewport centering wrapper — pins at top:0 so the card inside
   stays visually centered (via flex) for the entire scroll through the section. */
.page12-sticky-center {
  position: sticky;
  top: 0;
  height: 100vh;
  display: flex;
  align-items: center;
  justify-content: center;
  direction: rtl;
}

/* Forced invisible while @fold13's scroll gate (js/fold11.js, p13GateLocked) is
   locked — momentum-phase wheel events are non-cancelable in some browsers,
   so the page can genuinely scroll past the gate for a paint or two before
   the safety net corrects scrollY back. Since this wrapper is position:sticky,
   a real (if brief) overscroll is enough to pin it into view on its own —
   toggled the instant p9.sides changes (page9.js), not on a scroll/rAF tick,
   so there's no lag window for it to peek through regardless of scroll timing. */
.page12-sticky-center.gate-hidden {
  opacity: 0;
  pointer-events: none;
}

/* The card's size is set from the VIEWPORT EDGES, not from its copy: a 22px
   gap above and below (height = 100vh − 2×22). The WIDTH is solved at load and
   on resize by p12CardWidthFit() (page12.js) — the narrowest width in
   320–900px at which the copy still clears the bottom padding, which is also
   the width that fills the card, since a narrower column is a taller one. The
   520px here is that answer for a 982px-tall viewport and is what stands if
   the script never runs. Whatever height is left over is split evenly above
   and below the copy (flex column, centred), so the top and bottom gaps read
   equal. The .text-card wrapper is fit-content so the frame stays centred
   past the 480px --card-w column. */
#page-17 .text-card {
  width: fit-content;
}
#page-17 .text-card-frame {
  width: 520px;
  height: calc(100vh - 44px);   /* 22px off the screen's top and bottom (picked by eye 2026-09-22, @fold16 harness) */
  padding: 42px 40px;   /* 42px top and bottom, 40px sides */
  box-sizing: border-box;
  display: flex;
  flex-direction: column;
  justify-content: center;
  direction: rtl;
}

/* On shorter laptop viewports the card's height (title + body copy) leaves
   almost no gap from the screen's top/bottom edges — the sticky wrapper is a
   fixed 100vh with no scroll room of its own to compensate (see #page-17's
   min-height comment above), so trim the card's own padding here instead. */
@media (max-height: 920px) {
  #page-17 .text-card-frame {
    padding: 24px 28px;
  }
  /* id-scoped: the base .page12-credits rule sits later in the file */
  #page-17 .page12-credits {
    margin-top: 9px;
    padding-top: 9px;
  }
}
@media (max-height: 820px) {
  #page-17 .text-card-frame {
    padding: 18px 22px;
  }
}

#page-17 .section-title {
  font-size: 36px;   /* desktop; picked by eye 2026-09-22 (was 40) */
  text-align: center;
  line-height: 1.2;
  margin-bottom: 42px;
}

.page12-body {
  font-family: var(--font-body);
  font-size: 16px;
  line-height: 1.6;
  color: var(--ink);
  text-align: right;
}

.page12-body-heading {
  /* 600 / 8px picked by eye on desktop 2026-09-22 (@fold16 harness) and
     carried to mobile as-is (was 700 / 12px). */
  font-weight: 600;
  margin-bottom: 8px;
}

.page12-body p {
  margin-bottom: 12px;
  /* Rebalances line breaks across the whole paragraph instead of greedy-
     filling each line — fixes single-word orphan last lines (e.g. "ביותר"
     stranded alone) by reflowing earlier lines rather than forcing a
     no-break on just the last two words (which would just push the whole
     pair down instead of pulling the orphan up). */
  text-wrap: pretty;
}

.page12-body p:last-child {
  margin-bottom: 0;
}

/* Credits under the methodology (teacher review 2026-09-03, K3), kept tight
   on purpose: the card already fills a 900px-tall viewport before this row
   (see the max-height rules above). Same Assistant face as .page12-body. */
/* @fold15 (#page-16): the share row is its own title block now — a plain
   .text-card-frame card with the "שתפו" .section-title over the button row,
   centred like every other title fold. */
.page12-share-card {
  direction: rtl;
  text-align: center;
}
.page12-share {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: center;
  gap: 12px;
  margin-top: 16px;
  font-family: var(--font-body);
}

/* Icon buttons (WhatsApp / X / Facebook / LinkedIn / copy link): inline SVG
   glyphs in currentColor, a round button sized by --share-icon, the glyph by
   --share-glyph. Three looks, one class on #page12Share: .is-outline (a ring,
   inverts on hover), .is-filled (black disc), .is-brand (each platform's own
   colour). Hover is instant — no transition (explicit instruction). */
.page12-share {
  --share-icon: 36px;
  --share-glyph: 50%;
}
.page12-share-btn {
  position: relative;
  box-sizing: border-box;
  width: var(--share-icon);
  height: var(--share-icon);
  display: inline-grid;
  place-items: center;
  padding: 0;
  border-radius: 50%;
  border: 1.5px solid var(--ink);
  background: none;
  color: var(--ink);
  cursor: pointer;
  text-decoration: none;
  font: inherit;
}
.page12-share-btn svg {
  width: var(--share-glyph);
  height: var(--share-glyph);
  display: block;
}
.page12-share-btn .page12-share-ico-done { display: none; }
.page12-share-btn.is-copied .page12-share-ico-link { display: none; }
.page12-share-btn.is-copied .page12-share-ico-done { display: block; }

.page12-share.is-outline .page12-share-btn:hover,
.page12-share.is-outline .page12-share-btn:focus-visible,
.page12-share.is-outline .page12-share-btn.is-copied {
  background: var(--ink);
  color: #fff;
}
.page12-share.is-filled .page12-share-btn {
  background: var(--ink);
  border-color: var(--ink);
  color: #fff;
}
.page12-share.is-filled .page12-share-btn:hover,
.page12-share.is-filled .page12-share-btn:focus-visible {
  background: #444;
  border-color: #444;
}
.page12-share-btn[data-share="whatsapp"] { --brand: #25D366; }
.page12-share-btn[data-share="x"]        { --brand: #000; }
.page12-share-btn[data-share="facebook"] { --brand: #1877F2; }
.page12-share-btn[data-share="linkedin"] { --brand: #0A66C2; }
.page12-share-btn[data-share="copy"]     { --brand: #555; }
.page12-share.is-brand .page12-share-btn {
  background: var(--brand);
  border-color: var(--brand);
  color: #fff;
}
.page12-share.is-brand .page12-share-btn:hover,
.page12-share.is-brand .page12-share-btn:focus-visible {
  box-shadow: inset 0 0 0 100px rgba(0, 0, 0, 0.12);
}

/* The platform name on hover — a small dark label above the button. A pure
   state flip, so it may fade. */
.page12-share-btn::after {
  content: attr(data-tip);
  position: absolute;
  bottom: calc(100% + 6px);
  left: 50%;
  transform: translateX(-50%);
  padding: 2px 8px;
  border-radius: 4px;
  background: var(--ink);
  color: #fff;
  font: 400 12px/1.4 var(--font-body);
  white-space: nowrap;
  pointer-events: none;
  opacity: 0;
  transition: opacity 0.15s;
}
.page12-share-btn:hover::after,
.page12-share-btn:focus-visible::after,
.page12-share-btn.is-copied::after {
  opacity: 1;
}

/* @fold15: an ordinary house fold — one viewport, card centred. With #page-14's two cards spaced 100vh apart and
   #page-17 centring the outro in its first viewport, the three card centres
   sit exactly 100vh apart, like every other consecutive pair. */
#page-16 {
  min-height: 100vh;
  align-items: center;
}

.page12-credits {
  /* Same hairline as the share row's top border, so the share block is closed
     on both sides and the credits read as their own footer band. */
  margin: 19px 0 0;   /* 38px gap, hairline centred in it — same as .page12-share */
  padding-top: 19px;
  border-top: 1px solid rgba(0, 0, 0, 0.15);
  font: 400 14px/1.5 var(--font-body);
  letter-spacing: 0.04em;
  color: rgba(0, 0, 0, 0.7);
  text-align: center;
}

.page12-credits a {
  color: inherit;
  text-decoration: underline;
}

/* Title/subtitle no longer share a bottom edge (node 217:9176 drops the old
   node's flush-bottom pairing) — each is independently top-anchored,
   matching Figma's own absolute top values converted to the project's
   H/2-relative convention (982px reference frame, center at 491px):
   title top 233px → 258px above center; subtitle top 312px → 179px above
   center. Horizontal: title's left edge sits 8px right of center; subtitle's
   right edge sits 10px left of center (an 18px gap between them, not the old
   11px) — subtitle uses the same left + translateX(-100%) right-edge-anchor
   trick Figma's own node uses, rather than a literal `right` value, to stay
   center-relative. */
.page0-title {
  /* position: fixed (not absolute, despite the general note above about
     .page0-title/.page0-subtitle staying normal-flow) — same reasoning and
     .page0-overlay's box already
     matches the viewport exactly at scroll 0, so this calc(50% - Npx) value
     renders identically either way, and fixed just keeps it glued to the
     viewport instead of scrolling away by document flow. Switched from
     absolute per explicit instruction: the scroll-driven lag effect in
     js/fold1-intro.js (page0ApplyTitleScrollLag) needs to be the *only* thing moving this
     element — layering a JS transform on top of native scroll-driven flow
     movement (the old absolute behavior) meant two separate browser
     pipelines (layout/paint for the flow position, compositing for the
     transform) were fighting over the same element's position every frame,
     which read as jitter no amount of lerp/clamp tuning fixed. */
  position: fixed;
  left: calc(50% + 8.5px); /* hero harness 2026-09-27: 26px column gap, box 1.5px inside its column's inner edge (was +8 at the 20px gap) */
  /* Nudged up an extra 18px (276 vs the Figma-literal 258) so the gap below
     the title to its dot column relates to the subtitle's own gap — title and
     subtitle render at different heights and different fonts (3 lines @42px
     HadassahFriedlaender vs 4 @18px Assistant), whose line-height metrics
     don't scale the same way, so neither a Figma-literal top value nor a
     naive assumed-equal offset (the previous 286px, tuned by eye rather
     than measurement, overshot by ~10px) actually relates the two gaps —
     only a pixel-measured comparison does. The measurement that matters is
     TRIMMED: last baseline → top edge of the dot column, not box bottom →
     dot, since the line box carries the descender plus half-leading and both
     grow with line-height. Trimmed, this sits at 23.73px against the
     subtitle's 20px — picked by eye on 2026-09-07 with the hero-title
     `manual/` harness (wiki/Dev-Workflow.md). */
  top: calc(50% - 270.8px); /* hero harness 2026-09-27: last baseline 22px above its dots (was -276 / 23.7px) */
  width: 185px;
  /* 400, not 600: fonts/ holds exactly two HadassahFriedlaender faces (Thin
     100, Regular 400 — see the @font-face rules at the top of this file), so
     a 600 request had no file behind it and the browser synthesised a fake
     bold by smearing Regular at draw time. Synthetic bold is applied by the
     rasteriser, not baked into the glyph outlines, so how much ink it lays
     down can shift between rendering paths — which is what read as the hero
     title changing weight during the reveal. 400 is a real drawn face with
     fixed outlines and no such freedom. If retail weights above 400 are ever
     licensed, add the @font-face rule first, then this value may go up. */
  /* Desktop: Discordia, like the title blocks (typeface harness, 2026-09-27);
     the ≤600px block keeps Hadassah. */
  font: 400 40px var(--font-title); /* 40px, hero harness 2026-09-27 (was 42) */
  /* 1.31 is what the browser's own `normal` already resolved to for this face
     (55px / 42px) — stated explicitly so the `top` above stays valid if the
     font file is ever swapped for one with different metrics. */
  line-height: 1.35; /* hero harness 2026-09-27 (was 1.31) */
  color: #000;
  text-align: left;
  margin: 0;
}

/* @fold1's page-load entrance (playPage0Entrance, js/fold1-intro.js) sets this
   element's own transform directly every animation frame (translateY from
   100vh down to 0, p9Ease'd) — deliberately no CSS transition here, since
   one would just re-ease motion that's already eased in JS, the same
   "double-easing" this file avoids everywhere else continuously-JS-driven
   values are set (see .page0-dot, .group-swatch, etc.). */
.page0-subtitle {
  /* position: fixed — same reasoning as .page0-title above. */
  position: fixed;
  left: calc(50% - 11.5px); /* hero harness 2026-09-27: 26px column gap (was -10 at the 20px gap); the ≤600px block keeps -10 */
  transform: translateX(-100%);
  /* 189.1, not the old 179: the line-height below opened the 4 lines up, and
     `top` is re-solved so the LAST BASELINE lands 20px above its dot column's
     top edge (the trimmed gap — see .page0-title). Both numbers were picked by
     eye on 2026-09-07 with the hero-title `manual/` harness. Exact px as
     tuned, deliberately not converted to a relative unit. */
  top: calc(50% - 186.7px); /* hero harness 2026-09-27: last baseline 20px above its dots (was -189.1) */
  width: 139px; /* hero harness 2026-09-27 (was 125); the ≤600px block keeps 125 */
  font: 300 18px var(--font-body);
  /* Opened up from the browser's `normal` (which resolved to 1.2778 here) —
     4 lines of 18px Assistant read tight against the title's much larger
     leading. Changing this moves the last baseline, so `top` above must be
     re-solved with it; the two are one setting. */
  line-height: 1.48; /* hero harness 2026-09-27 (was 1.52) */
  color: #000;
  text-align: right;
  margin: 0;
}


#page-0 .section-text {
  visibility: hidden;
}

/* One persistent .group-item per group, used across every stage (legend
   through fold 5) — see GROUPS (js/groups.js) / updateGroups
   (js/update-groups.js). left/top (set every
   scroll tick) is the SWATCH's own anchor point, not the item box, since the
   item itself has no size: .group-swatch sits at that exact point and
   .group-label's own left (also set every tick, continuously lerped rather
   than flipped between two fixed values) is offset from it by however far
   the swatch/label side-swap has progressed. */
.group-item {
  position: absolute;
}

.group-swatch {
  position: absolute;
  left: 0;
  top: 0;
  width: 13px;
  height: 13px;
}

.group-label {
  position: absolute;
  transform: translateY(-50%);
  white-space: nowrap;
  font: 18px var(--font-body);
  color: var(--ink-soft);
}

/* Once a row has settled into the mini-legend, its label gets an OPAQUE plate
   in the page's own background colour (.is-plated, toggled in updateGroups).
   It must not change the look at all — --bg is exactly what the page paints
   behind it, so the plate is invisible — it exists only so the label, which
   expands back out of the bare swatch on hover, occludes whatever canvas
   content it lands on instead of tangling with it. The box-shadow is the plate
   too, not a shadow: a 4px spread of the same colour bleeds it just past the
   text's own box so glyphs don't sit flush against foreign ink. Painted below
   the label's own text automatically (background/box-shadow of the element
   itself), and only from @hidden-acled on, so the @fold2/@fold3 columns over the dot
   grids are untouched. */
.group-label.is-plated {
  background: var(--bg);
  box-shadow: 0 0 0 4px var(--bg);
}

/* .is-emphasized (toggled in updateGroups from @fold2 until the mini-legend
   settles) lifts a group label to full black — the default resting state is
   81%-opacity black. */
.group-item.is-emphasized .group-label {
  color: #000;
}

/* The two camp headers ("קואליציית הימין" / "גוש השינוי") — no Figma node, new
   content per explicit written spec. ONE pair of elements spanning three
   folds, not a fold-4 thing: they type in at @fold2, stay through @fold3 and
   un-type at @fold4 — not fold-4-only, despite living near @fold4's code.
   Same type
   ramp as .group-label's is-emphasized state (18px/#000) but bold, so it
   reads as a heading over the rows beneath it. Right-aligned via a static
   translate(-100%, -50%) rather than JS-measured left math — left/top set in
   JS is always the header's own untransformed anchor point (colX +
   swatchSize), and the transform keeps the header's right edge pinned there
   regardless of the two headers' differing text lengths. */
.camp-header {
  position: absolute;
  transform: translate(-100%, -50%);
  white-space: nowrap;
  /* The header types in as two sibling spans (revealed prefix + transparent
     remainder, js/fold8-tooltip.js: fold8SetupTypewriter). The document is dir=ltr, so
     without this the two Hebrew runs would lay out left-to-right relative to
     each other and the prefix would render on the wrong side. */
  direction: rtl;
  font: 18px var(--font-body);
  /* Tuned by eye (manual/ harness). 'Assistant' is loaded as a VARIABLE font
     (wght@300..800, index.html), so 660 is a real interpolated face, not a
     rounded-up 700 — an off-hundred value here is deliberate, don't "tidy" it
     to 700. It keeps the heading clearly above the 400 group rows beneath
     while sitting a shade lighter than full bold. */
  font-weight: 660;
  color: #000;
  opacity: 0;
}

/* Source-credit line under fold6's mini-legend (js/groups.js: fold6NoteEl) — no
   Figma node, new content. Fixed px width so it wraps consistently
   regardless of viewport (same convention as .text-card's fixed 480px);
   left/top are the note's own position (not an anchor offset from a
   sibling, unlike .group-swatch/.group-label), so no position:relative
   parent is needed. Opacity-only fade, set inline in JS (e6) — no CSS
   transition, same reasoning as .fold6-square-label above. */
.fold6-note {
  position: absolute;
  direction: rtl;
  text-align: right; /* right-aligned: the note now sits under the RIGHT legend column and hugs its right edge (see fold6X / noteRightEdge in js/update-groups.js) */
  font: 14px var(--font-body);
  line-height: 1.4;
  color: var(--ink-note);
  white-space: pre-line; /* FOLD6_NOTE_TEXT's \n splits it into two paragraphs */
  opacity: 0;
}

/* Heading over the note ("איסוף הנתונים"). Deliberately the same 14px/1.4 box
   as .fold6-note — the vertical rule's ink trim (fold6NoteLead in
   js/update-groups.js) assumes that line-height — so only the weight and the
   colour separate them; both greys were picked in a manual/ pass. It types in as the first segment of the
   note's own typewriter stream, so it has no separate timing of its own.
   Weight tuned by eye against a manual/ harness. */
.fold6-note-title {
  position: absolute;
  direction: rtl;
  text-align: right;
  font: 14px var(--font-body);
  font-weight: 600;
  line-height: 1.4;
  /* Read by js/update-groups.js, not by any declaration here: the gap the
     chevron keeps from the title's text, and the room it keeps from the card's
     left edge. Both decide how narrow the collapsed card may be, so the JS that
     sizes the card is the only thing that can act on them. */
  --note-chevron-gap: 16px;
  --note-chevron-inset: 6px;
  /* 4.54:1 on white — #949494 was 3.03:1, under AA for body text. */
  color: var(--muted-ui);
  opacity: 0;
}

/* The note and its title are their own hover target (explicit instruction) —
   hovering either types the body back in, exactly as hovering the legend rows
   does. #fold6NoteLayer is pointer-events: none, so the two opt back in. The
   typewriter keeps the block at full size from the first frame, so the hit
   area doesn't shrink along with the visible text. */
.fold6-note, .fold6-note-title { pointer-events: auto; }

/* Accordion chevron beside the note's title. A pseudo-element, so the title's
   typewriter (which rewrites the element's own spans every frame) can't touch
   it. It sits to the PHYSICAL LEFT of the text for free: ::after is last in
   inline order and the block is `direction: rtl`, so "after" renders leftward.
   The classic two-border square: an "L" made of border-left + border-bottom,
   rotated -45deg, reads as a chevron pointing DOWN (closed); +180deg from
   there points it UP (open).
   Both the rotation and the fade-in are interpolated from custom properties
   written per frame by updateGroups — --note-open (body typed 0..1) and
   --note-title-p (title typed 0..1) — so the chevron turns WITH the un-type
   and hover-retype instead of snapping at a threshold, and doesn't appear
   before the title it belongs to has finished typing.
   It replaced the vertical rule as the note's affordance (the rule is off
   below): a chevron says "this opens", a hairline only says "this is a block".
   `--note-chevron-gap` is not read here — it is the space the chevron keeps
   from the title's text, and only js/update-groups.js needs it, to work out how
   narrow the collapsed card may be. */
.fold6-note-title::after {
  content: "";
  display: block;
  /* Pinned to the CARD's left edge rather than flowing after the text, so it
     stays on the frame as the card collapses and reopens. The title box is
     absolutely positioned, so it is its own containing block; `--note-chevron-x`
     is that edge expressed in the title box's own coordinates, written per
     frame by updateGroups (the card's width is the thing that moves, not the
     title). `top: 50%` + the translate centres it on the title's single line —
     the translate has to live in the same `transform` as the rotation, since
     the later declaration would otherwise win outright. */
  position: absolute;
  left: var(--note-chevron-x, 0px);
  top: 50%;
  /* --note-chevron-inset (the breathing room between the chevron and the card's
     left edge) is not read here — updateGroups folds it into --note-chevron-x,
     because the collapsed card has to be that much wider to hold it. */
  width: var(--note-chevron-size, 6px);
  height: var(--note-chevron-size, 6px);
  border-left: var(--note-chevron-weight, 1.5px) solid currentColor;
  border-bottom: var(--note-chevron-weight, 1.5px) solid currentColor;
  transform: translateY(-50%) rotate(calc(-45deg + var(--note-open, 0) * 180deg));
  /* Its own grey, a shade off the title's — NOT currentColor: the chevron is
     the affordance, the title is the label, and they were tuned separately.
     Darkened with the title to keep that one-shade gap; 4.20:1, past the 3:1
     a non-text affordance needs. */
  color: #7a7a7a;
  opacity: var(--note-title-p, 0);
}

.fold6-note-title.is-in-panel {
  position: static;
  width: auto;
  margin-top: 10px;
}

/* No chevron inside the מקרא panel (explicit instruction). On desktop the
   chevron is the note CARD's affordance — it turns as the card opens, and
   --note-chevron-x is that card's left edge. In the panel the card is hidden
   (.fold6-note-card.is-in-panel) and the note simply flows into a legend that
   is already open, so there is nothing for a chevron to promise and no edge for
   it to ride. The divider below does the one job left: separating the note from
   the rows. `content: none` removes the pseudo-element outright, so none of the
   custom properties updateGroups keeps writing can bring it back. */
.fold6-note-title.is-in-panel::after { content: none; }

/* The hairline between the group rows and the ACLED note inside the panel
   (built in js/groups.js, shown/hidden with the note by updateGroups). Same
   1px / 12%-black as the note's vertical rule on desktop, turned to lie across
   the block it separates. The margins are the note title's own 10px doubled on
   the rows' side, so the note reads as attached to the rule below it rather
   than floating between two equal gaps. */
.fold6-mlegend-divider {
  height: var(--mlg-rule-w, 1px);
  margin: 8px 0 0;
  /* The same hairline as the one under the title (the card's ::before) — both
     lightened together, and both on the group cards' own box. */
  background: var(--mlg-rule, rgba(0, 0, 0, 0.12));
}


.fold6-note-link {
  color: inherit;
  text-decoration: underline;
  pointer-events: auto;
}

/* ── @fold10's «היקף האירועים» toggle ──────────────────────────────────
   The one control the reader can press in the mini-legend: it puts the
   crowd-size grid back on after @fold10 has flattened every square to one
   size. It is a direct .layout child (see js/groups.js) rather than a
   .groups-overlay one, because that overlay's z-index: 0 stacking context
   would bury it under .text-col and eat every click.
   Position (left/top) and opacity are written every frame by updateGroups in
   js/update-groups.js, so there is deliberately no transition on those; the
   pressed-state colours DO transition, because they're a pure state flip. */
.p7-scope-btn {
  position: fixed;
  z-index: 3;
  box-sizing: border-box;
  margin: 0;
  padding: 4px 0;
  border: 0;
  background: transparent;
  color: var(--ink-soft);
  /* Same face, size and colour as a settled mini-legend label (.group-label,
     written to 14px by updateGroups) — and, like it, NO letter-spacing: the
     0.04em this carried as a pill made it read as a different typeface sitting
     right next to the rows it belongs to. */
  font: 14px var(--font-body);
  white-space: nowrap;
  cursor: pointer;
  pointer-events: auto;
  /* No fade-in: @fold10 reveals it by POPPING the ring and TYPING the label
     (js/update-groups.js). Opacity is still written per frame, but only for
     @fold13's shared fade-OUT — so no transition on it either. */
  opacity: 0;
  /* RTL: the ring belongs to the RIGHT of the Hebrew label, and ::before is
     laid out first, so the row is reversed to put it there. */
  display: flex;
  align-items: center;
  flex-direction: row-reverse;
  gap: 8px;                /* ring → text */
  /* The label TYPES IN, so the box starts with no text at all. Left to the
     font, an empty button is 18px tall and a one-character button is 23px —
     and since updateGroups places it by `rowY − gap − btnH`, that one-frame
     growth jerked the whole control 5px upward just as the ring popped.
     Pinning the line box keeps the height constant from the very first frame. */
  line-height: 17px;
  min-height: 25px;        /* the 17px line box + 4px padding, top and bottom */
  transition: color var(--ui-fade-ms) ease;
}
/* The ring: an empty 10px circle that simply FILLS dark when the crowd sizes
   are showing — no tick inside it. At 10px a checkmark is a smudge, so the
   fill alone carries the on/off state. */
.p7-scope-btn::before {
  content: "";
  flex: 0 0 auto;
  /* The `*` reset above does NOT match pseudo-elements, so say it here: the
     10px is the OUTER diameter, border included. js/update-groups.js places the
     button off that number (P7_SCOPE_RING_PX), so the two must agree. */
  box-sizing: border-box;
  width: 10px;
  height: 10px;
  border-radius: 50%;
  border: 1px solid rgba(0, 0, 0, 0.45);
  background: transparent;
  /* The pop-in. --p7-scope-ring-t is written per frame by updateGroups off
     @fold10's reveal trigger, so there is deliberately no transition on the
     transform; the box keeps its 10px either way, which is what the button's
     right-edge placement measures against. */
  /* …and the ink correction: align-items centers the ring on the LINE BOX,
     whose unused Hebrew descent sits it visibly low against the letters.
     --p7-scope-ring-ink is groupLabelInkShift(12) — the same measurement the
     legend uses to center its swatches on their labels — so the ring reads
     centered on the ink instead. */
  /* …times the hover/pressed grow (--p7-scope-ring-hover, also per frame from
     updateGroups off p7ScopeHoverTrigger / p7ScopeOnTrigger): the ring swells a
     little under the pointer and STAYS swollen while pressed. A multiplier on
     the same transform, not a width change — the button is placed off the
     ring's 10px box, so growing the box would walk the whole control. */
  transform: translateY(calc(-1 * var(--p7-scope-ring-ink, 0px)))
             scale(calc(var(--p7-scope-ring-t, 1) * var(--p7-scope-ring-hover, 1)));
  transition: background-color var(--ui-fade-ms) ease, border-color var(--ui-fade-ms) ease;
}
.p7-scope-btn:hover::before { border-color: rgba(0, 0, 0, 0.75); }
/* Pressed = the size grid is ON. Kept in step with p7Grid.on (and aria-pressed)
   by updateGroups, so a scroll crossing that flips the grid also flips this. */
.p7-scope-btn.is-on::before {
  background-color: var(--ink-soft);
  border-color: var(--ink-soft);
}
/* The size grid is desktop-only, so its switch is too. */
@media (max-width: 600px) {
  .p7-scope-btn { display: none; }

  /* @fold9's card sits BELOW centre on mobile too — the same device as the
     desktop rule at the top of this file, and for a longer reason. Desktop's
     16vh only has to let the year axis finish filling; mobile also has to let
     the end-of-fill ZOOM-OUT play out (P7_ZOOMOUT_MS, page7.js) before the
     title block arrives, so the reader sees the whole timeline at rest first.
     Without this the card crossed mid-screen while the fill was still short of
     the end and the zoom-out never got to run.
     The zoom-out is a fixed-duration trigger (P7_ZOOMOUT_MS), so this vh is
     what buys the ORDERING — it is the runway the clock has to elapse inside.
     The two are a PAIR: shorten this too far and the title block lands mid-zoom.
     Keep the pair in step, exactly as desktop does: padding-top p with
     min-height (100 + d)vh centres the card at (p + 100 + d) / 2, so d = p.
     `.text-section` already centres, so only the offset pair is set here; the
     ID out-specifies `.text-section { padding: 0 24px }` further down, so the
     side gutters are untouched and the top offset stands regardless of order. */
  #page-10 {
    /* 55, not 75. Measured at 393x852: the zoom-out arms ~400px before #page-9
       ends and lands ~40px into this section at a normal reading scroll, so the
       last 20vh of the old runway was dead scroll — the whole timeline sitting
       at rest with nothing happening and no card yet on screen. 55vh keeps the
       ordering the pair exists for (the beat still finishes before the card is
       on screen, let alone before its crossing) and drops ~170px of that wait.
       Do not cut it to the beat's own length: the runway has to hold at a
       reading scroll, not just a slow one. */
    padding-top: 55vh;
    min-height: 155vh;
  }
}

/* ── The note's CARD ────────────────────────────────────────────────────
   The frame the title + body sit inside. A SIBLING painted behind them, not a
   wrapper — see the comment by fold6NoteCardEl in js/groups.js for why. Its
   rect (and so its collapse to the title row and its growth as the body types)
   is written per frame by updateGroups, hence no CSS transition: JS repaints it.
   The three tunables are custom properties so the card's LOOK can be swapped
   without touching the geometry. */
.fold6-note-card {
  position: absolute;
  box-sizing: border-box;
  border-radius: var(--note-card-radius, 16px);
  /* Chosen in a compare/ pass against a dashed frame and two hairline-box
     variants: no border at all, just a tint. The note is an aside — a drawn
     frame competed with the legend rows it sits under. */
  border: var(--note-card-border, none);
  /* OPAQUE: the tint is laid over the page's own paper (--bg), not over whatever
     is behind the card — the note sits in front of the timeline dots when they
     collide (explicit instruction), in the open AND the collapsed (title-row)
     state alike, since updateGroups keeps this card sized to both. A bare tint
     let the canvas dots print through the copy. */
  background: linear-gradient(var(--note-card-bg, rgba(0, 0, 0, 0.035)),
                              var(--note-card-bg, rgba(0, 0, 0, 0.035))), var(--bg);
  pointer-events: auto; /* part of the note's hover target, like the text */
  opacity: 0;
}
/* Inside the mobile מקרא panel the panel IS the frame — a card in a card reads
   as a mistake — so it stands down there, exactly as the rule does. */
.fold6-note-card.is-in-panel { display: none; }

/* The note's VERTICAL rule — now OFF on desktop (the card + chevron replaced
   it); kept intact so it can be turned back on by removing the display rule
   below. It replaced the old horizontal hairline under the rows. Same
   hairline colour and 1px weight, turned on its side: it runs down the RIGHT edge of the note block — the reading-start side
   in RTL — and updateGroups writes its `height` every frame, so it grows as
   the note types and shrinks back to the title's height as the body un-types.
   No CSS transition: JS repaints it per frame. */
.fold6-note-rule {
  display: none;
  position: absolute;
  width: 1px;
  background: rgba(0, 0, 0, 0.12);
  opacity: 0;
}

/* Inside the mobile מקרא panel both of these FLOW instead of being positioned
   by updateGroups, so the absolute positioning has to be undone — the JS clears
   their inline left/top/width on the way in, but not the stylesheet's own
   `position: absolute`. */
.fold6-note.is-in-panel {
  position: static;
  width: auto;
}
.fold6-note.is-in-panel { margin-top: 2px; } /* the title above it carries the gap from the rows */
/* The rule is a desktop device: inside the panel the note flows full-width and
   has no column edge to run down, so it simply doesn't appear there. */
.fold6-note-rule.is-in-panel { display: none; }

/* ────────────────────────────────────────────────────────────────────────
   Mobile מקרא legend bar (built in js/groups.js)
   ────────────────────────────────────────────────────────────────────────
   Hidden entirely above the breakpoint — the desktop mini-legend is drawn as
   DOM group rows over the canvas instead. The layer is fixed and full-bleed
   like the other direct-.layout-child overlays, and passes clicks through; only the
   bar itself takes pointer events (and only once updateGroups fades it in).

   A z-index above `auto` keeps the button tappable — the .text-section boxes
   are full-viewport, so anything painting under them can't be reached by a tap.
   It is the BOTTOM of the three-way stack the mobile block sets (bar → groups →
   title blocks, explicit instruction): both the title blocks and the group rows
   paint over it, open or closed. The real number is the mobile override there
   (1002, above the docked tooltip); this base only applies above the
   breakpoint, where the bar doesn't exist.
   The z-index has to live on the layer: it is the stacking context, so nothing
   inside it can ever climb past this number. */
.fold6-mlegend-layer {
  position: fixed;
  inset: 0;
  z-index: 5;
  pointer-events: none;
}

/* Stand-ins for the six group rows and the two camp headers during @fold4's
   mobile FLY hand-off. ONE parking layer, inside .fold6-mlegend-layer and after
   the panel, so a stand-in paints IN FRONT OF THE LEGEND for the whole flight
   (explicit instruction) rather than only once it reaches the sheet.
   *Removed — don't reintroduce:* `.fold6-mfly-layer-under`, a second layer that
   was a direct `.layout` child at z-index 1003 and held each stand-in until it
   overlapped the panel. 1003 was above the legend's old 1002; once the legend
   went to 1006 to clear the title blocks, that layer silently fell behind it and
   the rows flew under the sheet.
   The real elements park here because they are `visibility: hidden` mid-flight;
   nothing here is interactive, and the panel below still takes the taps. */
.fold6-mfly-layer {
  position: absolute;
  inset: 0;
  pointer-events: none;
}
/* The real row/header while its stand-in is flying: still laid out (updateGroups
   measures its label width every frame) but not painted. */
.is-mfly-hidden { visibility: hidden; }

.fold6-mlegend { display: none; }


/* ────────────────────────────────────────────────────────────────────────
   Mobile (≤600px — the same breakpoint as isMobile() in js/core.js; keep the two
   in sync).

   Only values that are hardcoded to a desktop width live here. Everything
   already viewport-relative — .graphic-col/#canvas, the fractional SBB
   boxes, every frame-scaled Y, the live-measured SVG dash frames — needs
   nothing. Folds 8-10 (the pinned timeline, the bridge and the drag
   categorization) are NOT adapted yet; see wiki/Folds.md.
   ──────────────────────────────────────────────────────────────────────── */
@media (max-width: 600px) {
  /* Half the desktop gutter — --card-w's own min() subtracts 48px to match. */
  .text-section {
    padding: 0 24px;
  }

  /* Title block, 20px -> 16px, picked by eye against all three candidates on
     a 393px screen. Still ONE shared rule for every fold's title — this is a
     breakpoint override, not the per-page kind the hard rule forbids, so the
     titles stay uniform with each other at any given width.

     The frame around it is scaled to match rather than left at its desktop
     padding: at 21px/29px around 16px type the box reads as a large empty
     white slab with a small title floating in it. 16px/22px holds the Figma
     frame's own horizontal:vertical ratio (29/21 = 1.38, 22/16 = 1.375).

     Only font-size is touched, deliberately: on the title cards .section-title
     and .text-card-frame are the SAME <h2>, so the base rule's `margin: 0 0
     8px` is already overridden to 0 by .text-card-frame's later `margin: 0
     auto` (same specificity, later wins). Setting a bottom margin here would
     therefore add a gap on mobile that desktop doesn't have — the frame's
     padding is the only vertical spacing in play.

     The dash SVG needs no change — updateTextCardFrameDashes() re-measures
     the frame's real pixel box and rewrites the viewBox 1:1, so the 2px dash
     stays exact at the new size. */
  .section-title {
    /* Phone: Discordia too (phone typeface harness, 2026-09-29), at its own
       16px — Discordia ships Regular only, so 400. */
    font: 400 16px/1.5 var(--font-title);
  }
  .text-card-frame {
    padding: 16px 22px;
  }

  /* The outro card carries its own fixed width instead of --card-w. */
  #page-17 .text-card {
    width: var(--card-w);
  }
  #page-17 .text-card-frame {
    width: min(450px, 100vw - 48px);
    padding: 24px 22px 28px;
    box-sizing: border-box; /* the phone width is viewport-bound, not padding-bound */
    height: auto;           /* the desktop 100vh-gap height would clip the copy here */
  }

  /* 40px over 3-4 Hebrew words overflows a 393px card; this is the one
     documented per-page title size (the shared .section-title 20px base is
     untouched — see the hard rule in CLAUDE.md). */
  #page-17 .section-title {
    font-size: 26px;      /* desktop's 40→36 step carried over (was 28) */
    margin-bottom: 26px;  /* desktop's 32→42 ratio carried over (was 20) */
  }

  /* The outro (#page-17) is the one fold whose height is set by its own copy,
     not by the canvas: ~1400 characters of methodology text at 16px in a
     ~300px-wide card is taller than a phone viewport no matter how the padding
     is trimmed. So mobile drops the desktop "exactly one viewport" rule (see
     the min-height comment above) rather than clipping the text:

       - the wrapper stops being sticky and grows to its content, so the card
         scrolls past normally instead of being pinned inside a 100vh box with
         no scroll room of its own;
       - min-height: 100vh is kept so short-content/large-phone cases still
         center the card.

     Scoped to #page-17 on purpose. @fold13's wrapper (#page-14) shares this
     class but must stay sticky and exactly one viewport tall on mobile too:
     p13GateMax() and fold13ScrollT() both measure off #page-14's offsetTop and
     one innerHeight, so letting it grow past 100vh would move the gate line.
     Its copy is one short title, so it never needs the extra room anyway.

     checkFold13 (watchCardThreshold on #page-14's wrapper at frac 0) is
     unaffected either way — it watches the sticky wrapper, which still pins. */
  #page-17 .page12-sticky-center {
    height: auto;
    min-height: 100vh;
    /* top is overwritten inline by p12SpacingFit(); the 16px bottom is the air
       the document ends with under the card — desktop's 48→22 step, carried
       over (was 32). */
    padding: 32px 0 16px;
    box-sizing: border-box;
  }

  /* 16px/1.6 is desktop's measure at a 370px column; at ~300px it runs to
     ~45 lines. Dropping a step keeps the same rhythm at phone width. */
  .page12-body {
    font-size: 14px;
    line-height: 1.55;
  }

  .page12-body p {
    margin-bottom: 10px;
  }

  /* Hover tooltips: fixed 222px/200px boxes clip against a narrow viewport. */
  .page9-tooltip {
    width: min(222px, 100vw - 32px);
  }

  /* Tooltip text is a step larger on a phone (12px reads too small at arm's
     length; 14px was the first step, bumped to 15px by eye). The docked
     frame's height and line-heights below are solved against this size —
     change them together. */
  .page9-tooltip-date,
  .page9-tooltip-desc {
    font-size: 15px;
  }

  /* The DOCKED tooltip — @hidden-hover, @fold6 and the real timeline (#page-9) on
     mobile only. Instead of a callout hung off the dot it describes (which on
     a 393px screen covers that dot, and half the timeline grid with it), the
     tooltip is one designated frame in a fixed spot: same position, same size,
     for every event — only the text inside changes as the selection moves.
     Applied from JS by tooltipDockMobile (js/fold8-tooltip.js), which also
     clears the inline left/top the floating layout writes.

     `top` is a FALLBACK only — tooltipDockTopPx writes it inline every tick,
     because the frame has two spots: just above the 8 sample squares at
     @hidden-hover, gliding up to this one as the real timeline arrives
     (fold9FlyTrigger). The value below is that final spot, and the two must
     stay in sync with TOOLTIP_DOCK_TOP_PX.

     It sits above the timeline grid: SBB_TIMELINE_MOBILE.top is 0.19H
     (~160px on a 844px phone), so 62px + this height clears it. (The מקרא
     sheet lives at the BOTTOM edge — FOLD6_MLEGEND_EDGE_GAP_PX — and no
     longer competes for the top.) It's a flat px, not a fraction of the viewport, so the frame
     doesn't drift between @hidden-hover (no grid on screen yet) and the timeline.

     The height is FIXED and the box clips: the frame must not resize as a
     longer or shorter description swaps in — that's the whole point of a
     designated spot — so the description gets the room it gets: three clamped
     lines (descHeMedium runs 39–308 characters, median 101, so most events
     print in full at this width). The rest end in an ellipsis and can be
     opened on demand — see .is-expanded below (and the picker's hold-to-expand
     in page7.js), the cases where the frame grows, and only while the reader
     is actually reading. A fourth line was tried: it made the frame too heavy
     a block at the top of a phone screen for what it buys, given the toggle
     already covers the tail. */
  .page9-tooltip.is-docked {
    top: 62px;
    left: 50%;
    right: auto;
    bottom: auto;
    /* 300px keeps ~76% of descriptions inside the 3 clamped lines (measured
       across the full dataset at 14px); wider frames bought a few more points
       (320px → 82%, the 342px title-block width → 87.5%) but read too wide.
       Capped to the viewport for very narrow screens. */
    width: min(300px, 100vw - 48px);
    /* 100 = 2×2px border + 2×7px padding + the 18px date line + the 4px flex
       gap + 3 clamped 20px description lines (see the desc rule below). Solved
       exactly — any slack pools at the bottom (flex-start) and reads as uneven
       padding. */
    height: 100px;
    /* No pointer corner — nothing is being pointed at. The dash <svg> rounds
       all four to match (updateTooltipDash in js/core.js). */
    border-radius: 8px;
    justify-content: flex-start;
    /* JS owns transform here: `translateX(-50%) scale(g)` — the centering
       first, then @hidden-hover's grow-in pop about the centered box (the same
       fold8TooltipGrowEase desktop uses). The scale is the ENTRANCE only; the
       frame's resting size never changes, and unlike desktop it is not
       multiplied by fold9TooltipShrinkTrigger — at @fold6's end the frame
       stays put and empties instead of shrinking away. */
  }

  /* The clipping that keeps a long description from bursting the fixed height
     goes on the DESCRIPTION, never on the frame: .page9-tooltip's dashed
     stroke is an <svg> child inset -2px on every side (it has to straddle the
     border box, not the padding box — see .page9-tooltip-dash), so
     `overflow: hidden` on the frame itself clips precisely the stroke away and
     the tooltip loses its border. `min-height: 0` is what actually lets a flex
     child shrink below its content height; without it `flex: 1` alone won't. */
  .page9-tooltip.is-docked .page9-tooltip-desc {
    flex: 1;
    min-height: 0;
    overflow: hidden;
    /* The clip is made VISIBLE: -webkit-line-clamp cuts at a whole line and
       prints an ellipsis there, so a truncated description reads as "there is
       more" instead of a sentence that stops mid-word. It only works on the
       -webkit-box display, hence the two lines above it; `flex: 1` still
       applies, since that governs how this box behaves as its PARENT's flex
       item, not what it lays out as inside. line-height is pinned rather than
       left at `normal` because the clamp count and the 100px frame height are
       solved against it: 3 × 20 = 60px, plus the 18px date line and the
       padding. */
    display: -webkit-box;
    -webkit-box-orient: vertical;
    -webkit-line-clamp: 3;
    line-height: 20px;
  }
  .page9-tooltip.is-docked .page9-tooltip-date {
    line-height: 18px;
  }

  /* @hidden-hover's frame (up to the handover into the bar — tooltipFitFold7,
     js/fold8-tooltip.js) is content-sized like the desktop tooltip: no fixed
     height, no clamp, no «עוד». The spot above the squares re-solves off the
     frame's live height, so a longer description lifts it, never overlaps. */
  .page9-tooltip.is-docked.is-fit {
    height: auto;
  }
  .page9-tooltip.is-docked.is-fit .page9-tooltip-desc {
    flex: none;
    display: block;
    overflow: visible;
    -webkit-line-clamp: unset;
  }
  .page9-tooltip.is-docked.is-fit .p7-tip-more {
    display: none;
  }

  /* Opened by the reader (the p7-tip-more toggle, p7InspectInit in page7.js) on
     the ~third of events whose description is longer than four lines. This is
     the only state where the frame is allowed to grow, and it grows DOWNWARD
     over the chart rather than pushing it: the dot grid's top clearance is
     derived from the collapsed height (SBB_TIMELINE_MOBILE_TOP_PX), so a frame
     that resized the layout would shift every dot on screen the moment someone
     read a long caption. Covering a few dots for as long as the text is open is
     the cheaper trade. max-height keeps the longest description (308 chars) on
     screen; past that the text itself scrolls. */
  .page9-tooltip.is-docked.is-expanded {
    height: auto;
    max-height: calc(100vh - 62px - 24px);
  }
  .page9-tooltip.is-docked.is-expanded .page9-tooltip-desc {
    display: block;
    overflow-y: auto;
  }

  /* Whole-frame tap. Once there is more text to show, the entire tooltip is the
     control — a card-width 100px target instead of a 30px word, which on a phone is the
     difference between a reliable tap and a fiddly one. It opts back into
     pointer events only in these two states; in every other state the frame is
     inert (pointer-events: none) so it never eats a gesture aimed at the chart.
     `chartTouch` (page7.js) already ignores touches inside the frame, so the
     press-and-hold is unaffected either way. */
  .page9-tooltip.is-docked.is-expandable,
  .page9-tooltip.is-docked.is-expanded {
    pointer-events: auto;
    cursor: pointer;
  }

  /* The toggle — now a LABEL for that whole-frame tap rather than the target
     itself (hence pointer-events: none: a tap on the word must be handled by
     the frame, exactly like a tap anywhere else in it).
     It sits in the date row's empty half — the date is RTL and
     right-aligned in a full-width row, so the left end of that row is free —
     rather than under the text, where a last line running long would collide
     with it. A white ALPHA, not a solid: the frame is filled with the event's
     group colour (see .page9-tooltip's background above), and a partly
     transparent white keeps this reading as interface sitting behind the
     description rather than competing with it for the eye — the same job the
     old near-black alpha did on the white frame. */
  .p7-tip-more {
    position: absolute;
    top: 5px;
    left: 8px;
    display: none;
    pointer-events: none;
    padding: 2px 4px;
    border: 0;
    background: none;
    font: 700 11px var(--font-body);
    color: rgba(255, 255, 255, 0.75);
    cursor: pointer;
  }
  .page9-tooltip.is-expandable .p7-tip-more,
  .page9-tooltip.is-expanded .p7-tip-more { display: block; }
  /* Mid-hold (the picker's hold-to-expand, page7.js) the label is not a
     control — the finger is on the chart, and the frame collapses by itself
     on release — so no עוד/פחות while `is-holding` is on. */
  .page9-tooltip.is-holding .p7-tip-more { display: none; }

  /* ---- The picker's instruction band (p7HintBandInit, page7.js) ----------
     «לחצו והחזיקו על נקודה…», the label for the press-and-hold gesture. Its own
     full-bleed band rather than the docked frame's resting content (which is
     what it used to be): it spans the whole screen width, where the frame is a
     300px box, and it carries a backdrop blur that has to sit behind the text
     and in front of the canvas — the frame can be neither.

     `position: fixed`, so it holds its edge while the timeline scrubs behind it.
     Height is the LINE's own height plus its padding — "its weight" — and the
     blur covers exactly that band, full width. `--hint-blur` is written by
     p7HintBandApply (a manual/ harness drives it live).

     backdrop-filter, not a translucent fill: the dots behind it are 2px marks,
     and a flat wash over them either hides them or does nothing, where a blur
     softens them into a ground the type can sit on and still reads as the
     timeline continuing underneath. -webkit- prefix for iOS Safari, which still
     needs it. The band is pointer-events: none — the gesture is a hold anywhere
     on the chart, including through this. */
  .p7-hint-band {
    display: block;
    position: fixed;
    left: 0;
    right: 0;
    z-index: 999;              /* under the docked frame (1000), over the canvas */
    padding: 9px 24px;
    pointer-events: none;
  }
  /* NO BLUR. It was a masked-gradient backdrop-filter on a ::before; removed on
     explicit instruction — this rule replaced it. **Don't reintroduce** (its
     knobs, P7_HINT_FADE_PX / P7_HINT_SOLID_PCT, went with it).

     THE LINE under the hint is where the timeline clips (p7HintClipTopY,
     page7.js): the axis and its dots are cut off at it, so nothing of the
     timeline ever prints above the hint. Full-bleed, so it reads as the edge of
     the chart's own area rather than as an underline belonging to the text. */
  .p7-hint-band.is-below { /* `bottom` is written by p7HintBandApply */ }
  .p7-hint-band.is-above { top: 0; }
  /* The rule DRAWS IN with the line above it — it is not a border, because a
     border is simply there the instant the band is, and the band exists (empty)
     for the whole of the axis's build-in. A pseudo-element scaled on X instead,
     off --hint-rule (0..1), which p7HintBandApply drives from the SAME clock as
     the typewriter (p7HintTypeT). transform, not width: no layout per frame.
     Origin right — RTL, so it unrolls the way the sentence is read. */
  .p7-hint-band.is-above::after {
    content: "";
    position: absolute;
    left: 0;
    right: 0;
    bottom: 0;
    height: 1px;
    background: var(--rule, rgba(0, 0, 0, 0.14));
    transform: scaleX(var(--hint-rule, 0));
    transform-origin: right center;
  }
  .p7-hint-band .p7-inspect-hint {
    display: block;
    direction: rtl;
    /* Longhands, not the `font` shorthand — same reason as everywhere else in
       this file: a shorthand would reset the size back out from under the
       line-height set with it. */
    font-family: var(--font-body);
    font-weight: 400;
    font-size: 15px;
    line-height: 1.3;
    text-align: center;
    color: #858585;            /* FOLD8_TOOLTIP_REST_COLOR, the resting grey */
  }

  /* ---- Mobile event picker (p7InspectInit, page7.js) ----------------------
     Touch has no hover, so the real timeline's per-dot tooltip needs a
     deliberate gesture instead: a press-and-hold anywhere on the chart.

     The resting state is EMPTY — no fill, no border, no dash — except on
     @fold12, where it carries the «לחצו והחזיקו על נקודה» line (explicit
     instruction, 2026-09-14; static, no typewriter, see `.is-hint` below).
     The old typed version on the timeline folds stays removed. The box comes back
     the instant an event is picked, when is-picker is dropped and the
     group-coloured frame from the base rule takes over. */
  .page9-tooltip.is-docked.is-picker {
    align-items: center;
    justify-content: flex-start;
    padding-top: 0;
    background: none;
    border-color: transparent;
  }
  .page9-tooltip.is-docked.is-picker .page9-tooltip-date,
  .page9-tooltip.is-docked.is-picker .page9-tooltip-desc { display: none; }

  /* @fold12's resting instruction line (page7.js hintEl, `.is-hint` set by the
     picker's sync() on page 12 only). Sits at the frame's top edge; the extreme
     columns start P9_HINT_H_M below it, and a picked event's frame overlays
     them. Takes the frame's resting grey via currentColor. */
  .page9-tooltip.is-picker.is-hint .p7-inspect-hint {
    display: block;
    width: 100%;
    font-weight: 400;
    font-family: var(--font-body);
    font-size: 15px;
    line-height: 19px;
    color: currentColor;
    text-align: center;
  }

  /* A press-and-hold is also iOS's own gesture for "select text / show the
     callout menu", and it fires on the surrounding page even though a <canvas>
     has no text of its own — the Copy / Browse-for-Me bar would pop up on top
     of the loupe mid-drag. Suppressing both on the graphic column kills it at
     the source. Mobile-only and scoped to the column: the text column keeps
     normal selection, and desktop is untouched.

     `#page-9` is in the list because the scroll column's own (transparent)
     section sits ON TOP of the canvas — that's the element the finger actually
     lands on, so suppressing it on the canvas alone isn't enough. It's the only
     section listed; the rest of the article keeps normal selection. */
  /* #page-6 / #page-7 (@hidden-hover / @fold6): the reader's own press-and-hold on
     the 8 squares (fold7Hold*, js/groups.js) is live through both, and a hold
     there landed on the title card's text and selected it.
     #page-10 / #page-13 (@fold9 / @fold12): the event picker is live on those
     folds too now (p7InspectPage), so the same hold lands on those sections'
     own title text. Every section the picker serves has to be in this list —
     the scroll column's transparent section is what the finger actually
     touches, so suppressing it on the canvas alone is never enough. */
  .graphic-col,
  #canvas {
    -webkit-touch-callout: none;
    -webkit-user-select: none;
    user-select: none;
  }

  /* The loupe — a 96px circular window onto the canvas, held above the finger
     so the fingertip itself doesn't cover the dots being read. Its own canvas,
     blitted 4x from the main one (drawImage), so there is no second render
     path to keep in sync with draw(). */
  .p7-loupe {
    display: none;
    position: fixed;
    z-index: 1001;
    width: 96px;
    height: 96px;
    border-radius: 50%;
    background: #fff;
    /* The ring is a box-shadow, not a border, deliberately: a border would add
       to the element's box and the 96px CSS size would no longer match the
       96px backing store the blit is scaled to. */
    box-shadow: 0 0 0 2px rgba(0, 0, 0, 0.85), 0 6px 20px rgba(0, 0, 0, 0.18);
    pointer-events: none;
  }
  .p7-loupe.is-visible { display: block; }
  .page9-cat-tooltip {
    width: min(200px, 100vw - 32px);
  }

  /* @hidden-hover's touch demo (js/groups.js fold7TouchEl + fold7LoupeEl): the
     picker's press-and-hold played for the reader. The glass is a real
     .p7-loupe (same disc, ring and shadow), painted by fold7PaintLoupe; the
     hand is this zero-size anchor at the fingertip. transform/opacity and the
     glass's left/top are written every frame, so no transitions here. */
  .fold7-touch {
    position: fixed;
    left: 0;
    top: 0;
    width: 0;
    height: 0;
    pointer-events: none;
    /* UNDER the title blocks (explicit instruction, both languages) — the
       phone twin of desktop's .fold7-cursor rule: 1003 sits below the cards'
       1004/1005 and above the docked tooltip's 1000 and the squares. */
    z-index: 1003;
    opacity: 0;
  }
  .fold7-loupe.is-dismissed { visibility: hidden; }
  /* A pointing hand, fingertip (viewBox 10,2 of 24) on the disc's centre. */
  .fold7-touch-pointer {
    position: absolute;
    left: 0;
    top: 0;
    width: var(--cursor-size, 24px);
    height: var(--cursor-size, 24px);
    transform: translate(-41.7%, -8.3%);
    background: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cpath d='M8.5 3.5a1.5 1.5 0 0 1 3 0v6.2a1.4 1.4 0 0 1 2.8.3v.5a1.4 1.4 0 0 1 2.8.3v.6a1.4 1.4 0 0 1 2.8.4V16c0 3.3-2.4 5.5-5.6 5.5h-1.3c-2 0-3.3-.8-4.4-2.3L5 14.9a1.4 1.4 0 0 1 2.1-1.8l1.4 1.4z' fill='%23fff' stroke='%23111' stroke-width='1.2' stroke-linejoin='round'/%3E%3C/svg%3E") no-repeat center / contain;
  }
  .fold7-touch.is-dismissed { visibility: hidden; }

  /* The other half of the device-dependent card copy — see .copy-mobile above. */
  .copy-desktop { display: none; }
  .copy-mobile  { display: inline; }

  /* The title starts at calc(50% + 8px), so its fixed 185px box runs past the
     right edge below ~430px wide. Cap it against the half-viewport it
     actually has (50vw - 20px leaves a 12px margin) instead of shrinking the
     type — the hero's title/subtitle-to-dot-column gaps are pixel-tuned to
     this font size. At 430px and up the min() still resolves to 185px, so
     wider screens are untouched. */
  .page0-title {
    /* Phone hero title: Discordia like desktop (phone harness, 2026-09-29); the
       `top` below is solved for Discordia's metrics at 32px/1.45. */
    left: calc(50% + 8px); /* phone keeps its +8 at its 20px column gap (desktop: +8.5 at 26px, 2026-09-27) */
    width: min(185px, 50vw - 20px);
    /* 42px is a desktop-scale display size that eats a third of a phone
       screen — 32px still reads as the hero and keeps the same 3-line break. */
    font-size: 32px;
    /* Mobile gets its OWN leading, unlike every earlier revision of this block,
       which let both hero texts inherit the desktop 1.31/1.52. That inheritance
       was the bug: line-height is unitless, so dropping the title to 32px took
       its leading down to 41.92 while the subtitle stayed at 27.36 — the 2:1
       nesting that locks the two baseline grids together on desktop (55.02 /
       27.36 = 2.011) fell to 1.532, and the subtitle's lines walked against the
       title's by ~12.8px per line down the block. 1.45 and the subtitle's 1.465
       below were picked by eye on 2026-09-12 with the mobile hero `manual/`
       harness (wiki/Dev-Workflow.md) against its live baseline rulers. */
    line-height: 1.45;
    /* Re-solved for the leading above — a line-height change always moves the
       last baseline, so this number and the one above are ONE setting. -227.8
       puts the title's last baseline 18.5px above its dot column's top edge,
       measured TRIMMED (baseline to dot, not box bottom to dot — see the
       desktop rule). The 50% cancels the viewport height out of the solve, so
       the value holds at any phone height; only the 390px WIDTH it was tuned
       at matters, since that is what fixes the 3-line wrap. */
    /* --page0-half (page0BuildHeight, page1.js: half the viewport PINNED at
       first build, the same number the dot columns are built from), not 50%:
       the title stays
       put when the bottom bar collapses — the columns are built past the
       bottom edge (PAGE0_BAR_ALLOWANCE_PX) and the extra dots just emerge
       from under the bar. --page0-drop is 0 on the phone now (kept for the desktop path);
       --page0-trim moves the text down with a top-trimmed column
       (PAGE0_TRIM_MOBILE) so the baseline-to-dot gap below survives. */
    top: calc(var(--page0-half, 50vh) - 230.1px + var(--page0-drop, 0px) + var(--page0-trim, 0px)); /* phone harness 2026-09-29: Discordia, last baseline 20px above its dots (was -228.6 / 18.5px Hadassah) */
  }

  /* The subtitle keeps its 18px and its 125px width here — only the leading and
     the top are re-tuned, in step with the title above (see that comment for
     why mobile needs its own leading at all). Still 4 lines at this width. */
  .page0-subtitle {
    /* Phone keeps its 125px box and -10px offset at its 20px column gap (desktop: 139 / -11.5 at 26px, 2026-09-27). */
    width: 125px;
    left: calc(50% - 10px);
    line-height: 1.41; /* phone hero harness 2026-09-29 (was 1.465) */
    /* Same trimmed solve as the title: -167.8 puts the last baseline 20.5px
       above its own dot column's top edge. Re-solve this whenever the leading
       above moves — the two are one setting. */
    top: calc(var(--page0-half, 50vh) - 165.9px + var(--page0-drop, 0px) + var(--page0-trim, 0px)); /* re-solved 2026-09-29 for line-height 1.41: last baseline 20.5px above its dots (was -168.8) */
  }

  /* The pinned title card rests 4.4vh (~37px on an 844px phone) from the top
     by default, which crowds the מקרא bar ending around 46px. Pushed to a flat
     48px so the two read as separate bands without drifting apart. Flat px, not
     a fraction: the thing being cleared is itself fixed-px. page9UpdateFromScroll
     reads this value back off the card, so the stick threshold follows
     automatically.

     The measured gap is not 60 − 52: once the card sticks its frame is
     transparent (border + fill faded), so what the eye reads is the *ink* — the
     first title line, a further 2px border + 16px padding-block down, ≈22px
     under the bar's bottom edge. Lower this and the subtitle's own negative
     margin-top below travels with it. */
  :root {
    /* 24, manual/-baked 2026-09-14: the מקרא card no longer sits above the
       title, so the whole @fold12 header block moved up. The tray band follows
       (top 96 here, P9_TRAY_TOP_M in page9.js — keep the two in step). */
    --card-top: 40px;   /* 16px lower than the first bake (24), both languages: the pinned title clears the language button. Rides with .page9-tray's top and P9_TRAY_TOP_M (page9.js) */
  }

  /* Figma node 294-1279 ("dragcard" in the full @fold12 frame): 16px Assistant
     SemiBold, a uniform 10px padding, 6px gap, 32px tall. The height only lands
     on 32 because the text box is trimmed to its cap height — `line-height:
     12px` reproduces that (the glyphs simply paint outside their 12px line box,
     which is why 16px type still fits). Nothing here is chosen to make the
     labels fit the screen: the row scrolls (see #page9ZoneBelow below), so the
     pill is free to be its Figma size. */
  .page9-pill {
    font-size: 16px;
    /* Desktop's computed weight (400) — the base rule's 600 read too heavy. */
    font-weight: 400;
    line-height: 12px;
    padding: 8px 10px;
    gap: 6px;
  }

  /* No pill shadow at all on mobile — Figma node 294-1279 is a flat chip, and
     the base rule's lift states describe a pointer that isn't here: hover has
     no meaning on touch, and dragging is replaced by tap (page9.js). The rest
     shadow goes too, so the pill reads as a flat outlined chip in the band.
     The two state selectors must be repeated in full — `.page9-tray
     .page9-pill:hover` outranks a bare `.page9-pill` here, so listing only the
     base selector would leave a shadow appearing under a stray touch-hover. */
  .page9-pill,
  .page9-tray .page9-pill:hover,
  .page9-pill.dragging {
    box-shadow: none;
  }

  /* Tap replaces drag under this breakpoint (page9.js's click handler), so the
     pill never leaves the tray — the selected state is shown in place instead
     of by the pill moving into #page9ZoneAbove.
     A LIGHT GREY fill, not the black one desktop's dropped pill uses: the
     selection square inside it is the thing that has to read as "ticked", and
     on black the square could only be white — inverted from every other mark on
     the fold, so a *selected* pill looked like the odd one out. Grey lets the
     square go solid black, i.e. the same colour as the label beside it, and
     keeps the pill's own text dark throughout.
     THE SAME GREY as the מקרא sheet's rows (.fold6-mlegend-row) — this was its
     own #E3E3E3, two points off that row's #E1E1E1, which is under the
     threshold of seeing and had no reason recorded. Unified onto the row's
     value, the one that carries a tuning note. Both are mobile-only, so
     desktop is untouched either way. */
  .page9-pill.is-extreme {
    background: var(--line-light);
    border-color: var(--line-light);
    color: var(--ink);
  }
  /* The 6 grip dots advertise a drag that doesn't exist here. Must repeat the
     base rule's two-class selector (.page9-pill .page9-handle, which sets
     `display: grid`): a media query adds no specificity, so a bare
     `.page9-handle` here loses the cascade and the dots stayed visible. */
  .page9-pill .page9-handle {
    display: none;
  }
  /* Replaces the desktop hover as the way into the category description: touch
     has no hover, so the tooltip needs a real target. It is an 18px circle,
     taking the place the grip dots vacated above. Note it is a flex ITEM of the
     pill, so once it grew past the label's trimmed 12px line box it became what
     sets the pill's content height — the pill is 6px taller than its padding
     alone would make it. `all: unset` first because
     it is a <button> — the UA's own font, border, padding and background would
     otherwise fight every value below. It inherits .page9-pill's color, so it
     flips to white with the pill's .is-extreme state for free. */
  .page9-pill .page9-pill-info {
    all: unset;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    flex-shrink: 0;
    width: 18px;
    height: 18px;
    /* Anchor for the ::before hit extender below. */
    position: relative;
    border-radius: 50%;
    border: 1px solid currentColor;
    font: 600 10px var(--font-body);
    line-height: 1;
    color: inherit;
    opacity: 0.75;
    cursor: pointer;
    /* The ⓘ must look identical whether or not it has just been tapped. Two UA
       behaviours break that on a phone and neither is covered by `all: unset`,
       which only resets the element's own resting properties:
         - the grey/blue flash mobile browsers paint over a tapped <button>,
         - the focus ring it keeps afterwards (a tap leaves a <button> focused).
       The tooltip it opens is the feedback; the button itself stays still. */
    -webkit-tap-highlight-color: transparent;
  }
  .page9-pill .page9-pill-info:focus,
  .page9-pill .page9-pill-info:focus-visible,
  .page9-pill .page9-pill-info:active {
    outline: none;
    background: none;
    opacity: 0.75;
  }
  /* Invisible tap-target extender: a finger landing near the 18px ⓘ should
     open the tooltip, not classify the pill. The pseudo-element is part of the
     button for hit-testing, so p9CategoryTooltipInit's
     `e.target.closest(".page9-pill-info")` guard (page9.js) catches these taps
     with no JS change — and the pill's own tap-to-classify path already
     excludes anything matching that selector. inset: -4px grows the touchable
     circle to ~26px each way without moving a pixel of layout (the visual 18px
     circle is untouched; only the hit box grows). */
  .page9-pill .page9-pill-info::before {
    content: "";
    position: absolute;
    inset: -4px;
  }

  /* ── The selection circle: @fold12's beat, on the pill's RIGHT edge ──
     Tap replaces drag here, so .is-extreme is the ONLY thing that says a
     category has been classified — the pill never moves. This is that readout:
     an empty 18px ring at rest, filled with a ✓ once selected. It is a <span>
     with pointer-events:none (page9.js), so the whole pill stays the single tap
     target and the circle only advertises what tapping does.
     First DOM child after the (display:none) handle, so in this RTL flex row it
     lands on the right, in the slot the grip dots vacate — the ⓘ stays on the
     left. */
  .page9-pill .page9-pill-check {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    flex-shrink: 0;
    /* A hard SQUARE, not a circle — the ⓘ beside it stays round, and the two
       reading differently is the point: one informs, one records a choice.
       12px with a 0 corner, tuned by eye on device 2026-09-13 (was an 18px
       circle, then an 18px/4px rounded rect). At 12 it no longer outgrows the
       label's own 12px line box, so unlike the ⓘ it does NOT set the pill's
       height. Still variables, because that is what the check-mark harness
       drives; the fallbacks are the shipped values. */
    width: var(--p9-check-size, 12px);
    height: var(--p9-check-size, 12px);
    border-radius: var(--p9-check-radius, 0px);
    border: 1px solid currentColor;
    opacity: 0.75;
    /* No type here on purpose: this span renders NO text — see the "no ✓ glyph"
       note below. It carried `font: 600 11px 'Assistant'` + `line-height: 1`
       from the days it held a tick; with the glyph gone both styled nothing and
       only suggested a character that isn't there. The box is sized by the
       explicit width/height above, so neither affected layout. */
    pointer-events: none;
  }
  /* No ✓ glyph, per explicit instruction — the FILL is the whole signal. An
     empty outline means unselected, a solid block means selected, and at 12px a
     tick inside it was a scribble competing with the label rather than reading
     as a state. Removed — don't reintroduce: a `::before` carrying "\2713". */
  /* Selected: the square fills solid BLACK on the pill's light grey
     (.page9-pill.is-extreme above) — the same ink as the label next to it, so
     the mark reads as part of the pill rather than as a hole punched in it. No
     glyph needed inside it; the fill is the whole signal. */
  .page9-pill.is-extreme .page9-pill-check {
    background: var(--ink);
    border-color: var(--ink);
    opacity: 1;
  }

  /* ── The ⓘ and the selection circle: @fold12's beat, and its LAST one ──
     Same treatment as desktop V2's grip handle, per explicit instruction: at
     @fold11 the pill fits its text content, and at @fold12 it expands around
     them. Collapsing takes all THREE properties for the same reason the handle
     does — `scale(0)` is paint-time and would leave the 18px box (and the
     pill's own 6px flex gap) sitting in the row — so `width: 0` retracts the
     box and the negative margin swallows the gap. The margin goes on whichever
     side that child's gap is drawn: the circle is the row's first visible item
     so its gap is toward the label (inline-END); the ⓘ is the last, so its gap
     is on the label side from the other direction (inline-START).

     THE EXPANSION IS SEQUENCED AFTER THE CONVOY, not run with it — that is what
     `:not(.training)` buys. An animating width reflows the whole nowrap row
     sideways, and if it runs during the train a cabin still in its vertical
     phase gets dragged horizontally by its neighbours growing: layout drag plus
     the phase's own `y` is a diagonal, the "angular flying" the two-phase
     motion exists to prevent. Measured with the two overlapping, the widths
     took 27-29 distinct values across the run and `offsetLeft` wandered dozens
     of px; sequenced, every pill holds one `offsetLeft`/`offsetTop`/`width`
     from the convoy's first frame to its last, and the pills widen as their own
     beat once the train has parked.

     `.training` also forces the collapsed state with NO transition, which is
     what makes the reverse clean: leaving @fold12 the affordances retract
     instantly on the first frame, so p9TrainToggle measures both ends of that
     run in the same (collapsed) layout the run itself will hold. */
  .page9-sticky:not(.engaged) .page9-tray .page9-pill-info,
  .page9-sticky:not(.engaged) .page9-tray .page9-pill-check,
  .page9-sticky.training .page9-tray .page9-pill-info,
  .page9-sticky.training .page9-tray .page9-pill-check {
    width: 0;
    overflow: hidden;
    transform: scale(0);
  }
  .page9-sticky .page9-tray .page9-pill-check { margin-inline-end: -6px; }
  .page9-sticky .page9-tray .page9-pill-info  { margin-inline-start: -6px; }
  .page9-sticky.engaged:not(.training) .page9-tray .page9-pill-info,
  .page9-sticky.engaged:not(.training) .page9-tray .page9-pill-check {
    width: 18px;
    margin-inline-start: 0;
    margin-inline-end: 0;
    transform: scale(1);
    transition: width var(--p9-pop-ms) cubic-bezier(0.37, 0, 0.63, 1),
                margin-inline-start var(--p9-pop-ms) cubic-bezier(0.37, 0, 0.63, 1),
                margin-inline-end var(--p9-pop-ms) cubic-bezier(0.37, 0, 0.63, 1),
                transform var(--p9-pop-ms) cubic-bezier(0.37, 0, 0.63, 1);
    transition-delay: calc(var(--p9-pop-i, 0) * var(--p9-pop-stagger));
  }
  /* The circle's expanded width follows its own size knob. AFTER the rule
     above, which sets a flat 18px for both children — same specificity, so
     source order is what lets the check-mark harness move this one alone. */
  .page9-sticky.engaged:not(.training) .page9-tray .page9-pill-check {
    width: var(--p9-check-size, 12px);
  }
  /* No transition at all while the train runs — the collapse must be a snap on
     the reverse, never an animation the FLIP would measure mid-flight. */
  .page9-sticky.training .page9-tray .page9-pill-info,
  .page9-sticky.training .page9-tray .page9-pill-check {
    transition: none;
  }

  /* Nothing is ever dropped into it, and its :empty hint would sit over the
     dot grid. */
  #page9ZoneAbove {
    display: none;
  }

  /* Figma node 294-1272 flushes @fold12's title and subtitle to the RIGHT edge
     rather than centering them. `text-align` can't do it: .text-card already
     inherits `text-align: right`, and what centers the title is the *box* —
     .text-card-frame is `width: fit-content; margin: 0 auto`. Scoped to
     .page9-title-row: every other fold's title stays centered. Only @fold12's
     is specified this way.

     The flush is a STUCK-STATE look, not the card's look: while the card is
     still scrolling up it is an ordinary centered title block like every other
     fold's, and it only travels to the edge once it pins at the top. So the
     shift rides .is-stuck alongside the border fade.

     Done as a transform rather than by releasing the auto margin
     (`margin-inline: 0 auto`), because `auto` is not an animatable value: the
     margin version can only snap the whole box sideways the instant the class
     lands. --p9-title-flush is the measured distance from centered to
     flush-right, written by page9UpdateFromScroll (js/page8-9-scroll.js) —
     it depends on the wrapped title's own width, which CSS can't express. */
  .page9-title-row .text-card .text-card-frame {
    transform: translateX(0);
    /* @fold12's title is a single short line; the shared 16px vertical padding
       read as an oversized fill around it, so this one frame runs slimmer
       top/bottom (width untouched). The subtitle's negative margin below is
       derived from this value — change one, change both. */
    padding-block: 8px;
    /* The frame's 29px side padding would hold the text 29px inside the card's
       own 24px gutter — 53px off the glass, against Figma's ~15px. Zeroed only
       once the card sticks, which is the state the Figma frame shows and the
       state the dashed border has faded in; while the card is still scrolling
       the box keeps its padding and reads as a proper box. Transitioned
       alongside that fade so the text glides to the edge instead of jumping. */
    transition: background var(--fade-ms) ease-out, padding var(--fade-ms) ease-out,
                transform var(--fade-ms) ease-out;
  }
  /* The ghost is a clone parked OUTSIDE .page9-title-row (a .layout child), so
     the slimmer padding above doesn't reach it — restated, so it is the same
     box as the card heading for it. It stays centred (the base rule's
     left: 50%); only the real card travels right. 1005 matches the card's own
     mobile z-index further down: the docked frame (1000) shares this top edge. */
  .text-card-frame.fold12-ghost {
    padding-block: 8px;
    z-index: 1005;
    /* Outside .text-card it also loses the card's rtl + right alignment, which
       left the Hebrew ranged left with its «?» on the wrong end. */
    direction: rtl;
    text-align: right;
  }
  .page9-title-row .text-card.is-stuck .text-card-frame {
    padding-inline: 0;
    /* Positive X is physically rightwards whatever the writing direction, and
       right is the flush edge here. Falls back to 0 (= stays centered) if the
       measurement hasn't been written yet. */
    transform: translateX(var(--p9-title-flush, 0px));
  }
  /* Same flush-right treatment; overrides the base rule's `text-align: center`
     (same specificity, later in the sheet).

     The base rule pulls the subtitle up by 21px to cancel the frame's bottom
     padding, which on a phone leaves the two line boxes overlapping by 3px.
     Desktop keeps that -21px; here the gap is a specified **2px**, and the
     margin is whatever produces it: the frame contributes 8px padding-block
     (slimmed above) + 2px border below the title's line box, so 10 + margin =
     2 → -8px. Change the spec, change this to (gap - 10). Note it's a
     line-box gap — optically,
     title descender to the subtitle's cap height, it reads ~9px larger.

     Small enough that the header block still clears the pill band at its
     original top — nothing below has to move. */
  .page9-title-row .page9-header-subtitle {
    text-align: right;
    margin-top: -8px;
  }

  .page9-tray {
    /* Full-bleed on mobile: the tray is a band, not a floating card, so it
       spans the whole viewport width. Overrides the base rule's
       `width: max-content`, which would otherwise shrink it to its pills. */
    width: 100vw;
    max-width: 100vw;
    /* No side padding: the scrolling pill run must be able to reach both screen
       edges (its own 12px inset lives on #page9ZoneBelow so it scrolls away).
       Figma node 294-1272's own gaps from the band's two rules to the label and
       the pill row are 23/24; both are tightened further — 8px above so the run
       sits close under the subtitle, 9px below (16px pill→rule optically) so it sits close over its own
       rule, instead of floating between the two. Shrinking the band moves
       everything under it up with it: p9DockTopM() derives the docked tooltip's
       spot from the tray's live height, so nothing needs re-syncing by hand. */
    padding: 8px 0 9px;
    gap: 10px;
    /* NOT a bottom sheet on mobile: Figma node 294-1272 pins the pills as a
       band directly under the title card, above the docked tooltip frame (which
       steps down out of the way — p9TooltipDropTrigger, js/groups.js) and above
       the dot grid. 96px is P9_TRAY_TOP_M in page9.js, which derives the
       tooltip's drop spot and the grid's top from it; the two must stay in step
       (js/fold11.js's slide-out mirrors it too).
       THE BAND DOES NOT SLIDE. Its transform is the at-rest value in BOTH
       states and it is simply invisible until `.pills-in` — the same thing
       desktop V2 does, and for the same reason twice over. One: the entrance
       IS the pills popping (scale 0→1); a band that also flew down from above
       the top edge read as the whole column "animating in from the top", which
       is not an entrance anyone asked for and got louder the taller the column
       got. Two: it is load-bearing for the convoy. The slide took 0.85s, and a
       brisk scroll reached @fold12 while it was still running — so the FLIP
       measured the pills mid-air, up off the screen, and every cabin then flew
       in on a diagonal instead of going up and then left.
       Removed — don't reintroduce: `translate(-50%, calc(-100% - 116px))` plus
       a `transform` transition here. */
    top: 96px;   /* = P9_TRAY_TOP_M (page9.js); 16px lower than the first bake (80), with --card-top */
    bottom: auto;
    transform: translate(-50%, 0);
    /* One rule, on the bottom edge only (Figma node 294-1272) — it separates the
       pill run from the tooltip frame below. Nothing divides it from the
       subtitle above, which reads as part of the same header block.
       Drawn as an ::after rather than a real border-bottom, because it is a
       BEAT: the band arrives at @fold11 carrying nothing but the pills, and the
       rule draws itself in under them at @fold12 as the train forms — same
       right-to-left scale-in desktop V2 uses. A real border would be there from
       the moment the band was, and it would also sit under the wrapped block's
       full height instead of under the finished single row. */
    border-radius: 0;
    border: none;
    /* `visibility` is the whole reveal now that the transform never moves. The
       delay outlasts js/fold11.js's own inline slide-out, which is the one
       thing that DOES still translate this band (on the way out at @fold13). */
    visibility: hidden;
    transition: visibility 0s linear var(--tray-ms);
  }
  /* Figma node 294-1272 has no "סוגי פעולות" label on the band — the subtitle
     directly above already names the gesture, and the label cost 12px of type
     plus the band's 10px gap. Hidden rather than removed from p9BuildPanel so
     desktop, which still shows it, keeps one code path. */
  .page9-tray-title {
    display: none;
  }
  /* @fold11's block sits at the band's own `top`, per explicit
     instruction — the pills arrive at the TOP of the screen and stay there; the
     convoy reshapes the run in place rather than carrying it up from lower down.
     (A 66vh waiting spot below the card was tried and rejected.) At @fold11's
     own crossing the centred card starts just under the four-row block; it
     scrolls up past it as the fold plays out, which is the card leaving, not a
     collision to design around. */
  /* ── Mobile engage, split across two folds to match desktop V2 ──
     The band arrives on @fold11's own crossing (.pills-in, page8CheckScroll in
     js/page8-9-scroll.js) carrying NOTHING but the pill labels, and @fold12's
     stick (.engaged) brings everything else — the drop zone, the pinned
     header, the docked frame's step-down and, on the pills themselves, the ⓘ
     and the selection circle further down. Desktop has done exactly this since
     the H2 review (see the .page9-layout-v2.pills-in block above); mobile used
     to hold the whole band back to .engaged because every one of those rules is
     scoped to .page9-layout-v2, which mobile is not.

     .pills-in deliberately does NOT take the base rule's
     `pointer-events: auto` — that stays on .engaged, so a tap on a pill at
     @fold11 can't classify anything a fold before the drop zone exists.
     The docked "לחצו והחזיקו" frame is untouched here: it steps down on
     p9TooltipDropTrigger, fired from the same `isStuck` crossing as .engaged,
     so it keeps its @fold10 resting spot right through @fold11. ── */
  .page9-sticky.pills-in .page9-tray,
  .page9-sticky.engaged .page9-tray {
    visibility: visible;
    transition: visibility 0s linear 0s;
  }
  /* The pop itself, same numbers and same stagger as desktop V2's: each pill
     starts at scale(0) and pops in 280ms (PAGE0_POP_MS), delayed by its
     --p9-pop-i × 60ms. That variable is P9_TRAY_GRID_V2[idx].col - 1
     (page9.js) — the same number mobile's flex `order` uses — so index 0 is the
     RIGHTMOST pill and the crest travels leftward, exactly like the band on a
     desktop. Scrolling back up mirrors it: the leftmost shrinks first.
     No ::after rule here — mobile's band draws its one rule as a real
     border-bottom (above), which is part of the band rather than a beat. */
  .page9-tray {
    --p9-pop-ms: 280ms;
    --p9-pop-stagger: 60ms;
  }
  .page9-sticky .page9-tray .page9-pill {
    transform: scale(0);
    transition: transform var(--p9-pop-ms) cubic-bezier(0.37, 0, 0.63, 1)
                calc((9 - var(--p9-pop-col-i, 0)) * var(--p9-pop-stagger));
  }
  .page9-sticky.pills-in .page9-tray .page9-pill {
    transform: scale(1);
    transition-delay: calc(var(--p9-pop-col-i, 0) * var(--p9-pop-stagger));
  }
  /* While the convoy is running (p9TrainRun, page9.js) every pill's transform is
     written per frame, so the pop transition above has to get out of the way —
     the house rule for anything JS repaints every frame. */
  .page9-sticky.training .page9-tray .page9-pill {
    transition: none;
  }
  /* The BAND's own 0.85s slide goes too, and that one is load-bearing rather
     than tidiness: p9TrainToggle adds `.pills-in` right before it measures, to
     cover an engage that never got a tick over @fold11's crossing (a jump or a
     fast scroll). With the transition live, the tray would still be animating
     out of its hidden pose at measure time and the FLIP would read the pills
     off the top edge — which is exactly the "they fly in from the top" bug.
     Suppressed, the class lands instantly and the measurement is the real
     column. Scoped to the tray itself, so the band rule's own ::after scale-in
     over --p9-train-total is untouched. */
  .page9-sticky.training .page9-tray {
    transition: none;
  }
  /* …and the row's own scroller has to stop clipping for the duration. At rest
     #page9ZoneBelow is `overflow-x: auto` — the whole point of the finished
     band — so any pill translated outside its box is cut off. That box IS the
     row, so through the convoy seven of the ten pills were flying through
     clipped space and simply vanished mid-journey. `visible` for the run; the
     scroller comes back the moment the train settles. Both axes, since the
     block the pills leave from is taller than the row they arrive in.
     The extra `.page9-tray` is for specificity alone: the reverse run is
     `:not(.engaged)` all the way through, and that rule (further down) sets
     `overflow-x: hidden` at the same weight — without the third class the train
     would be clipped leaving @fold12 but not arriving. */
  .page9-sticky.training .page9-tray #page9ZoneBelow {
    overflow: visible;
  }

  /* ── @fold11: the pills arrive as a VERTICAL LINE ──
     The band's finished shape is one horizontally-scrolling row (below), where
     only ~3 of the 10 pills are on screen at a time — so a pop-in there plays
     most of the fold's content off-screen. For the arrival beat the run stands
     up into a single centred column instead: all ten visible, popping top to
     bottom (the flex `order` that sequences the finished row right→left is the
     same number that sequences this column top→bottom, so one stagger serves
     both — see `--p9-pop-i`).
     It also sets up @fold12's convoy for free. The column's TOP pill is already
     on the finished row's line, so it slides straight sideways; every pill
     below it rises to that line and then sweeps sideways — "first up, then to
     the left" — which is exactly the bezier `p9TrainToggle` already draws
     through `{block x, row y}`, with no special-casing. One pill per block row
     means the convoy's own row ordering degenerates to plain top-to-bottom.
     `align-items: center` rather than `stretch`: the pills are different widths
     and a stretched column would read as a stack of bars, not as a queue. */
  .page9-sticky:not(.engaged) #page9ZoneBelow {
    flex-direction: column;
    flex-wrap: nowrap;
    /* CENTRED. The column is its own composition and reads as one — it is not
       a rehearsal of the row's geometry.
       That leaves exactly ONE cabin travelling right: the pill whose slot is
       the row's right-hand end, which is also the LAST to depart. Every other
       slot lies left of the centred column, so every other cabin goes up and
       then left, as specified. That one exception is handled in p9TrainToggle
       by running its legs the other way round — across first, then up — which
       is what keeps it from climbing through the slot its neighbour has already
       parked in (the whole collision, measured: it was the only overlapping
       pair a centred column produced).
       Removed — don't reintroduce: right-aligning the column on the row's own
       start gutter. It made every path leftward for free, and cost the column
       its centre. */
    align-items: center;
    justify-content: flex-start;
    gap: 14px;   /* tuned by eye on device, 2026-09-13 (was 6) */
    overflow-x: hidden;
    padding-inline: 12px;
  }
  /* @fold11's column starts ON @fold12's pill line — the band's own `top`, the
     same top the finished row sits at, so the column's TOP pill and the row's
     pills share a level. That is what makes the convoy read: the top cabin is
     already where it is going vertically, so its climb is a no-op and it simply
     slides, and every cabin below it rises to a line that was visible all along.
     The column runs 130→648 and the legit strip starts at 790, so it clears the
     dots without needing to be centred against them.
     Removed — don't reintroduce: spanning the tray from the top of the screen to
     --p9-legit-top and centring the column in it. It bought vertical centring and
     cost the two folds their shared line. */

  /* Top to bottom = the finished row LEFT to RIGHT, the mirror of --p9-order-row
     (page9.js publishes both). The furthest-travelling cabin leads, so nothing
     ever overtakes a pill already parked in the row. */
  .page9-sticky:not(.engaged) #page9ZoneBelow .page9-pill {
    order: var(--p9-order-col, 0);
  }

  /* The band's rule — @fold12's beat, scaled in from the RIGHT over the convoy's
     own full length (--p9-train-total, published by p9TrainRun) so its leading
     edge travels with the train. Linear, for the same reason desktop V2's is:
     it has to keep pace with a constant stagger, not ease against it. */
  .page9-tray::after {
    content: "";
    position: absolute;
    left: 0;
    right: 0;
    bottom: 0;
    height: 1px;
    background: rgba(90, 90, 90, 0.18);
    transform: scaleX(0);
    transform-origin: right center;
    /* Its own clock, not the convoy's: it is no longer drawn WITH the train but
       after it, as the first thing of "the rest". Linear so its leading edge
       travels evenly, the same reason desktop V2's does. */
    transition: transform var(--p9-rule-ms, 560ms) linear;
  }
  .page9-sticky.engaged:not(.training) .page9-tray::after {
    transform: scaleX(1);
  }
  /* P9_TRAY_GRID (page9.js) puts 5 hand-placed columns in each of the two tray
     rows — even at the reduced pill size above, a 5-pill row is wider than a
     phone. `display: contents` dissolves both wrappers, hoisting all 10 pills
     into #page9ZoneBelow's own layout so they form ONE continuous run (Figma
     node 293-947: a single row of pills that runs off both screen edges and is
     scrolled horizontally).
     The hand-tuned column *slots* don't survive that — order falls back to
     plain DOM order (row 1's five, then row 2's five), which is why
     p9MeasureTrayLayout clears its inline grid-track/height writes under this
     breakpoint. Each pill's inline `grid-column` is simply ignored in a flex
     container. */
  .page9-tray-row {
    display: contents;
  }
  /* Base rule is a flex COLUMN (one item per tray row); with the rows dissolved
     above it becomes the single row itself. `nowrap` + `overflow-x: auto` is
     the whole mechanism: the ten pills never wrap, so the tray is exactly one
     pill tall and the overflow is swiped through. The document is RTL, so the
     run starts scrolled to its right end — the first pill is at the right edge
     and the rest continue off to the left, as in the Figma frame.
     The scrollbar is hidden because this is a bottom sheet on a phone, where
     the cut-off pill at the edge is the affordance. */
  #page9ZoneBelow {
    flex-direction: row;
    flex-wrap: nowrap;
    justify-content: flex-start;
    /* Separate from @fold11's column gap (14px, further up) on purpose: the
       column is read as a stacked list and takes more air than this run, which
       is read as one continuous band. Both tuned by eye on device, 2026-09-13
       (both were 6). */
    gap: 8px;
    width: 100%;
    overflow-x: auto;
    overscroll-behavior-x: contain;
    -webkit-overflow-scrolling: touch;
    scrollbar-width: none;
    /* The tray zeroes its own side padding (below) so the run can reach both
       screen edges; the inset that keeps the end pills off the glass lives
       here instead, INSIDE the scroller, so it scrolls with the content.

       Logical, not physical: the document is RTL, so `start` is the RIGHT edge —
       where the run rests and where the first pill's edge shows. That edge is
       set to the title card's own gutter so the first pill lines up exactly
       under the flush-right title, rather than sitting 12px further out. The
       gutter is re-derived from --card-w's own min() instead of hard-coding 24,
       because between 528px and 600px the card stops growing and the gutter
       widens. The far (left) end keeps the plain 12px — it scrolls off screen. */
    padding-inline: calc((100vw - min(480px, 100vw - 48px)) / 2) 12px;
  }
  #page9ZoneBelow::-webkit-scrollbar {
    display: none;
  }
  /* Flex would otherwise shrink every pill to fit the viewport rather than
     letting the run overflow, and a squeezed pill wraps its label to two lines. */
  .page9-pill {
    flex-shrink: 0;
    white-space: nowrap;
  }

  /* @fold3's group labels. At the desktop 18px/nowrap the longest label
     ("מתנגדי הרפורמה המשפטית") runs ~184px, so the two camps' label runs
     collide across center well before 393px — the labels are what @fold3's
     rect column is centered against (campFold3X, js/update-groups.js), so
     this is a real overlap, not just tight spacing. Smaller type plus a
     max-width lets them wrap to two lines as in the Figma mobile frame,
     keeping each camp's run inside its ~120px half-budget.
     groupLabelWidth() measures a hidden span carrying this same class, so
     the wrapped width feeds the layout math automatically — but it caches,
     so bootstrap.js clears that cache on resize. */
  /* Labels wrap on mobile instead of running off the edge as one nowrap line.
     `width: max-content` is load-bearing, not decoration: .group-item is an
     absolutely-positioned ZERO-width box, so with `max-width` alone the label's
     shrink-to-fit width resolves against ~0 available space and collapses to
     its longest WORD (~35px) — the left legend column was rendering four-line
     labels stacked on top of each other. max-content asks for the natural
     single-line width first, and the cap wraps it at 100px (a group may raise
     its own cap via `labelCapMobile` in GROUPS, written inline by
     updateGroups — it wins over this value). It also makes
     groupLabelWidth/groupLabelHeight (js/groups.js), which measure a hidden
     span carrying this same class, agree with what actually renders — the row
     pitches are derived from those measurements. */
  .group-label {
    font-size: 16px;
    white-space: normal;
    width: max-content;
    max-width: 100px;
    line-height: 1.15;
    /* Only matters once the labels WRAP, which is why it lives in the mobile
       block: the document is dir=ltr, so a Hebrew label's paragraph direction
       is LTR even though the characters themselves lay out RTL by bidi. On one
       line that's invisible; on two or three it breaks the run in the wrong
       place and puts trailing punctuation (the ״ in באיו״ש) on the wrong end of
       a line. rtl makes the line-breaker treat the label as the Hebrew
       paragraph it is. text-align is NOT set here — it's written inline per
       label in updateGroups, because which edge should be flush depends on
       which side of the swatch that label currently sits on. */
    direction: rtl;
  }

  /* Worn only for the duration of @fold4's fly hand-off (js/update-groups.js).
     The base rule's translateY(-50%) makes `top` address the block's CENTER, so
     the label's drawn position is a function of its own measured HEIGHT — and
     during the flight the opening wrap cap drops it from 3 lines to 2 to 1,
     jumping the whole label by half a line each time. That was the stutter, and
     why only the tallest label showed it. Dropping the transform makes `top`
     address the block's TOP EDGE instead, so JS can place the FIRST LINE
     directly and the block sheds its lines downward, off the end nobody is
     reading. No measurement is involved at all, so a re-wrap cannot move the
     text.
     Horizontally it does the same on the other axis: translateX(-100%) anchors
     the box's RIGHT edge — the edge facing the swatch, and the one the text is
     flush against — so JS can stop deriving `left` from the box's measured
     offsetWidth. That width jumps every time the cap re-breaks the text (the
     longest line suddenly gets longer) and it is read a frame stale, which is
     the larger half of the same stutter. With the right edge pinned, the extra
     width sheds leftward, off the far end, and the visible text stays put. */
  .group-label.is-mfly-topanchor {
    transform: translateX(-100%);
  }

  /* Shorter scrub than desktop's 780vh: a touch flick covers far more of the
     page per gesture, so the desktop range makes crossing the dataset feel
     endless on a phone. vh, NOT dvh: dvh re-resolves when the URL/bottom bar
     collapses, which grew this section by ~600px mid-scrub and jumped the
     visible date back by months (scrollY holds still while the section top and
     height both move). vh is pinned to the large viewport, so the scrub range
     never changes under the reader's finger. */
  .text-section.page7-scrub {
    /* 560vh -> 510vh with the same dead-scroll trim as desktop
       (460vh + P7_SCRUB_END_LEAD_VH * 100vh). */
    min-height: 510vh;
  }

  /* ── The מקרא bar ──────────────────────────────────────────────────────
     Replaces the six-row mini-legend on a phone: a persistent top bar with the
     two CAMP names, and above them a button that drops the full legend (all six
     groups + the ACLED note) down underneath it. Pinned to the top of the
     viewport rather than to the canvas, because it has to outlive every fold
     from @fold4 on — including the pinned timeline, which owns its own layout.
     Opacity and pointer-events are written per frame by updateGroups. */

  /* THE MOBILE STACK, bottom to top (explicit instruction, and the order every
     rule in this block serves):

       canvas … → the groups (1003) → the title blocks (1004+) → the מקרא card (1006)

     The card sits at the TOP of the viewport now, where a title block shares
     its space — so it has to win, or the one control the reader can press
     renders behind the copy and cannot be seen or aimed at. (While the card was
     a bottom sheet nothing reached it and the order was the other way round:
     1002, under the cards. That is the line to restore along with the sheet.)
     1006 also keeps it above the docked event tooltip's 1000, as it always was.
     Out-stacking works from inside .text-col only because .text-col has no
     z-index of its own. Mobile-only: the bar doesn't exist above the
     breakpoint. */
  .section-text.text-card { z-index: 1004; }
  /* The groups band. The overlay is a direct .layout child (index.html) —
     that is what makes this number bite at all; nested in .graphic-col it was
     trapped in that column's stacking context and could never reach past the
     bar. Desktop keeps the base z-index: 0 and is unaffected. */
  .groups-overlay { z-index: 1003; }
  /* @fold4's flight is NOT in this band — it parks inside the מקרא layer, after
     the panel, so it paints in front of the legend the whole way. */
  /* Only the plain sections' cards need to be positioned for that z-index to
     bite — @fold12's and @fold13's are already sticky/fixed inside their own
     wrappers, and the child combinator keeps this rule from clobbering that. */
  .text-section > .section-text.text-card { position: relative; }

  /* @hidden-hover, @fold6 and @fold11: their title blocks paint over the docked
     tooltip, not under it. The docked tooltip is `position: fixed;
     z-index: 1000` (it has to beat everything on the pinned timeline, where
     there is no card at all — #page-9 is an empty scrub spacer), so every fold
     whose card shares the screen with it needs to out-stack that 1000
     explicitly. @fold11's card (#page-12) is the one that reads as "the fold8
     title" on device: it scrolls up over the still-docked frame while the
     timeline is pinned behind it, and its --card-top (48px) sits above the
     frame's 62px, so without this it slid underneath. */
  #page-4 > .section-text.text-card,
  #page-6 > .section-text.text-card,
  #page-7 > .section-text.text-card,
  #page-8 > .section-text.text-card,
  #page-12 > .section-text.text-card,
  .page9-title-row .text-card { z-index: 1005; }
  /* …except @hidden-hover's reader frame, which is placed ON that card by design
     (tooltipFold7ReaderPx, js/fold8-tooltip.js — the copy has done its job once
     a finger is down) and so has to out-stack it. 1006, not higher: the מקרא
     layer below is 1006 too and comes LATER in .layout, so it still wins the
     tie and stays above the frame, exactly as it is everywhere else. */
  .page9-tooltip.is-docked.is-over-card { z-index: 1006; }
  /* The מקרא legend sits UNDER both (see the stack comment above): above the
     docked tooltip's 1000, below the cards' 1004/1005 and the groups' 1003. */
  .fold6-mlegend-layer { z-index: 1006; }

  /* The bar is the mobile twin of the desktop ACLED note: a tinted, borderless
     card (.fold6-mlegend-card, same radius/tint as .fold6-note-card) painted
     behind a title row — the מקרא button with a chevron on the card's left
     edge — and, when open, the legend rows under it. The bar's own padding is
     the card's outset (FOLD6_CARD_PAD, 8px, the desktop card's number): at full
     size the card is simply the bar's box, and the collapsed pose is the button
     plus that same outset, so the JS sizing the card only ever measures the
     button and the bar. */
  .fold6-mlegend {
    display: block;
    position: absolute;
    /* The title is draggable (js/groups.js): a vertical swipe on it must scrub
       the sheet, not scroll the page. */
    touch-action: none;
    /* A FLOATING ACLED-NOTE CARD pinned to the TOP of the viewport, its closed
       מקרא pill in the top-RIGHT corner (both chosen in a compare/ pass — see
       Groups-and-Legend.md, which also keeps the flush bottom sheet this
       replaced, values and all, so it can be restored).
       `top` is written by fold6PlaceMobileLegend() from
       FOLD6_MLEGEND_EDGE_GAP_PX; the two side gaps are below. The 6px bottom
       padding is the card's outset under the rows (FOLD6_MLEGEND_PAD_BOTTOM_PX
       in js/groups.js measures the closed card off it — keep them in sync). */
    /* THE RIGHT EDGE LINES UP WITH THE TITLE BLOCKS (explicit instruction), and
       it is DERIVED from `--card-w` rather than hard-coded 24px: between 528px
       and 600px the card stops growing and the gutter widens, so a literal
       would drift out of alignment exactly there. Same expression as
       #page9ZoneBelow's padding-inline, which lines the first @dragcard up
       under the same edge. --mlg-gap-left is free to differ; it only shows
       while the card is open and spanning the bar. */
    --mlg-gutter: calc((100vw - min(480px, 100vw - 48px)) / 2);
    /* THE SIDE GAPS, and the pose they belong to (js/groups.js:
       FOLD6_MLEGEND_POSE — keep the class here in step with it).
       `.is-sheet`: FULL BLEED, no gutter at all — the sheet spans the screen.
       `.is-card`:  16px OUTSIDE the title blocks' gutter each side, so the card
       is a touch wider than the copy it sits over. Derived from the gutter, not
       written as a literal: the gutter widens between 528px and 600px where
       --card-w stops growing, and a literal drifts exactly there. Matches
       FOLD6_MLEGEND_OVERHANG_PX, which insets the CLOSED button back to flush. */
    --mlg-gap-right: 0px;
    --mlg-gap-left: 0px;
    /* The sheet's top corners (Figma 345:2295). The floating `.is-card` pose
       rounds all four with the same value. */
    --mlg-radius: 20px;
    /* Where the sheet's CONTENT sits: the bar's own side padding plus the rows
       panel's. Everything inside the sheet — the title, both hairlines and the
       ACLED section — lines up on this, because it is the group cards' own box
       (explicit instruction). Keep it in step with those two paddings. */
    --mlg-content-inset: 16px;
    /* THE OPEN CARD'S WIDTH, derived from the two gaps rather than assumed. The
       columns are solved from it (below), so moving either gap re-solves them
       for free — the alternative, restating `--card-w` down there, silently
       stopped matching the moment a gap was nudged and the left column clipped
       again. */
    --mlg-card-w: calc(100vw - var(--mlg-gap-left) - var(--mlg-gap-right));
    left: var(--mlg-gap-left);
    right: var(--mlg-gap-right);
    /* 13 above the title and 8 under the last card, off the Figma sheet —
       FOLD6_MLEGEND_PAD_BOTTOM_PX (js/groups.js) mirrors that 8. */
    padding: 13px 12px 12px;
    box-sizing: border-box;
    opacity: 0;
    text-align: center;
  }
  .fold6-mlegend.is-card {
    --mlg-gap-right: calc(var(--mlg-gutter) - 16px);
    --mlg-gap-left: calc(var(--mlg-gutter) - 16px);
  }

  /* THE one frame, in every state: closed it is the מקרא button, open it is the
     legend. Sized and placed every frame of an open/close by
     fold6MLegendPaintCard (js/groups.js). First child of the bar, so it paints
     under the title and the rows. */
  .fold6-mlegend-card {
    position: absolute;
    box-sizing: border-box;
    left: 0;
    bottom: 0;
    /* A SHEET rounds its top corners only — its bottom edge IS the screen's.
       The floating card clears every edge, so it rounds all four. */
    border-radius: var(--mlg-radius, 8px) var(--mlg-radius, 8px) 0 0;
    /* The desktop ACLED note's skin (.fold6-note-card): a tint, no border, no
       shadow. OPAQUE, though — the note's own rgba(0,0,0,.035) is translucent,
       and this card has to cover the title block and the canvas behind it or
       the dashed frame shows straight through and reads as sitting on top.
       #F4F3F6 is that same 3.5% black composited over --bg (#FDFCFF). */
    background: var(--mlg-fill, #f4f3f6);
    border: 0;
    box-shadow: none;
    pointer-events: none;
    /* The bar's arrival pop (fold6SetMobileLegendVisible) scales the collapsed
       card together with the button. */
    transform-origin: center;
  }
  .fold6-mlegend.is-card .fold6-mlegend-card {
    border-radius: var(--mlg-radius, 8px);
  }
  /* A small lift while it is the closed BUTTON, so it reads as something to
     press rather than a patch of tint. It goes the moment the card starts
     opening (`.is-open` lands on pointerdown): at full size a shadow only
     smears the card's own edge across the veil behind it. A sheet keeps the
     lift open too — its edge meets the artwork rather than a veil-backed
     gutter — but never under its bottom edge, which is off-screen. */
  .fold6-mlegend:not(.is-open) .fold6-mlegend-card {
    box-shadow: 0 -1px 0 rgba(0, 0, 0, 0.2);
  }
  .fold6-mlegend.is-sheet.is-open .fold6-mlegend-card {
    box-shadow: 0 -4px 6.2px rgba(0, 0, 0, 0.07);
  }

  /* THE GRAB HANDLE (Figma: "Line 17"). Drawn on the CARD rather than on the
     title, because the card's top edge is the sheet's in every state — closed
     the card IS the מקרא button and the handle sits 8px inside its top, open it
     stays 8px inside the sheet's. 12×2, round-capped, so a 1px radius. */
  .fold6-mlegend-card::after {
    content: "";
    position: absolute;
    /* Its gap from the card's top — a variable so a manual/ harness can drive
       it; fold6MLegendPaintCard writes it from FOLD6_MLEGEND_HANDLE_TOP_PX. */
    top: var(--mlg-handle-top, 8px);
    left: 50%;
    transform: translateX(-50%);
    width: 12px;
    height: 2px;
    border-radius: 1px;
    background: rgba(0, 0, 0, 0.16);
  }
  /* THE HAIRLINE UNDER THE TITLE (Figma: "Line 18") — full bleed, so it is on
     the card (which spans the sheet) rather than on the rows block, which sits
     inside the bar's 12px side padding. Open only: closed, there is nothing
     under the title to divide it from. */
  .fold6-mlegend.is-open .fold6-mlegend-card::before {
    content: "";
    position: absolute;
    /* How far down the sheet the rule under the title sits, and how thick both
       rules are — variables so a manual/ harness can drive them. */
    top: var(--mlg-rule-top, 45px);
    /* FULL BLEED, edge to edge (explicit instruction) — unlike the second
       hairline further down, which sits on the group cards' own box. This one
       closes off the title band, so it reads as the sheet's own rule. */
    left: 0;
    right: 0;
    height: var(--mlg-rule-w, 1px);
    background: var(--mlg-rule, rgba(0, 0, 0, 0.12));
  }

  /* Removed — don't reintroduce: `.fold6-mlegend-tab` (`fold6MobileTabEl`), the
     מקרא pill as a SEPARATE element joined to the card's top edge like a tab.
     There is one frame in every state now (explicit instruction — "it should
     just be part of the frame"): `.fold6-mlegend-card` IS the closed pill, and
     opening grows that same box. */

  /* The title row — same type as .fold6-note-title on desktop. No chevron here
     (explicit instruction): unlike the desktop note, this bar is the only thing
     on screen at its fold and its own opening is the affordance, so the button
     is the bare title and centring it centres the title. */
  .fold6-mlegend-btn {
    position: relative;
    direction: rtl;
    font: 14px var(--font-body);
    font-weight: 600;
    line-height: 1.4;
    color: var(--muted-ui);
    background: none;
    border: 0;
    /* No padding at all — the bare title is the whole control, and the pill is
       centred on it by fold6MLegendPaintCard (tuned by eye, manual/ harness). */
    padding: 0;
    cursor: pointer;
    /* Same treatment as the pill's ⓘ: this is a real <button>, so a tap draws
       the UA's own highlight over it — a grey halo — and leaves a focus ring
       behind afterwards. The card opening is the pressed state; nothing else
       should flash. */
    -webkit-tap-highlight-color: transparent;
  }
  .fold6-mlegend-btn:focus,
  .fold6-mlegend-btn:focus-visible {
    outline: none;
  }
  /* Removed — don't reintroduce: the grab handle, a 28×1.5px #d4d3d8 pill on
     ::before, centred 4px above the button's box. The floating card carries the
     bare title; the card's own open IS the affordance. (Its twin, a chevron,
     was judged in the same compare/ pass and declined with it.) */

  /* The veil over the page behind the open legend (js/groups.js:
     fold6MobileVeilEl), its opacity driven per frame from the card's own open
     progress. Picked in a compare/ pass: a DARKENED PAGE GROUND rather than
     neutral black, which cast the warm #FDFCFF page grey, and no backdrop blur
     (it costs a full-viewport filter on a canvas that repaints every frame). */
  .fold6-mlegend-veil {
    position: fixed;
    inset: 0;
    background: rgba(40, 36, 52, 0.35);
    pointer-events: none;
    opacity: 0;
  }

  /* The close button — top-LEFT of the open card, opposite the מקרא title
     (explicit instruction). Absolutely placed off the bar's own padding so it
     sits on the card's top band whatever the card is doing; `top` is written
     per frame by fold6MLegendPaintCard, which rides it with the title. A bare
     glyph, in the title's own grey, at the title's size — it is a quiet way out
     of the card, not a second piece of chrome competing with מקרא. */
  .fold6-mlegend-close {
    position: absolute;
    left: 14px;
    width: 24px;
    height: 24px;
    padding: 0;
    border: 0;
    background: none;
    color: var(--muted-ui);
    font: 24px/1 var(--font-body);
    cursor: pointer;
    opacity: 0;
    -webkit-tap-highlight-color: transparent;
  }
  .fold6-mlegend-close:focus,
  .fold6-mlegend-close:focus-visible { outline: none; }
  /* The glyph is a pseudo-element rather than text content so nothing can
     select it and no font fallback can swap it for a different-looking ×. */
  .fold6-mlegend-close::before {
    content: "\00d7";
  }

  /* The rows. No frame of its own any more — the card behind the bar is the
     frame — just the gap under the title row and a little inner breathing room.
     Its opacity rides the card's HEIGHT step (fold6MLegendPaintCard), so the
     rows fade in as the card makes room for them and are gone before it
     collapses back to the title. */
  .fold6-mlegend-panel {
    /* The cards start 61px down the sheet (Figma): 13 of bar padding + the
       title's 19.6 line + this + the 2px below leaves them exactly there.
       The hairline crosses at 45, drawn on the card. */
    margin-top: 26.4px;
    padding: 2px 4px 0;
    text-align: initial;
    /* The open panel sits in a position:fixed full-viewport layer over a canvas
       that repaints every frame. Without its own compositor layer the browser
       re-rasterises it along with the canvas on each scroll tick — the stutter.
       `translateZ(0)` promotes it so it is only re-composited, and
       `contain: paint` stops anything inside it from invalidating outside its
       box. */
    transform: translateZ(0);
    contain: paint;
  }

  /* Same two-column split as the desktop mini-legend, same sides (coalition
     right) — again by dir=rtl plus source order, not by float. The panel is now
     full-width, so the two columns sit one per half rather than bunched in the
     middle. */
  /* «איסוף הנתונים» (js/groups.js: fold6MobileDataHeadEl / BodyEl). The
     divider is the shared .fold6-mlegend-divider; the header is the desktop
     note title's type (.fold6-note-title) with its chevron, the body the
     desktop note's (.fold6-note). Collapsed by default — the body's height and
     opacity are written per frame by fold6MDataPaint, so no transition here. */
  /* «הצגת גודל האירועים» row in the מקרא panel (js/groups.js:
     fold6MobileScopeEl) — the desktop .p7-scope-btn's type and ring, in flow
     under the groups. `is-on` fills the ring, as on desktop. */
  .fold6-mlegend-scope {
    display: flex;
    flex-direction: row-reverse;
    align-items: center;
    justify-content: flex-start;
    gap: 8px;
    width: 100%;
    margin: 8px 0 0;
    /* THE GROUP CARDS' SHAPE, OUTLINE ONLY (chosen in a compare/ pass against a
       filled card and against adding a rule above it — the gap alone separates
       it from the six cards). `box-sizing` is load-bearing: this is a <button>,
       so without it the 1px outline pushes it past the cards it lines up with. */
    box-sizing: border-box;
    padding: 12px 13px;
    border: 1px solid #c9c9c9;
    border-radius: 10px;
    background: none;
    direction: ltr;
    font: 14px/17px var(--font-body);
    color: var(--ink-soft);
    -webkit-tap-highlight-color: transparent;
  }
  .fold6-mlegend-scope[hidden] { display: none; }
  .fold6-mlegend-scope::before {
    content: "";
    flex: 0 0 auto;
    box-sizing: border-box;
    width: 10px;
    height: 10px;
    border-radius: 50%;
    border: 1px solid rgba(0, 0, 0, 0.45);
    transition: background-color var(--ui-fade-ms) ease, border-color var(--ui-fade-ms) ease;
  }
  .fold6-mlegend-scope.is-on::before {
    background-color: var(--ink-soft);
    border-color: var(--ink-soft);
  }

  /* THE ACLED CARD — the group cards' shape, OUTLINE only (chosen in a compare/
     pass against a filled card and against keeping the rule above it). It wraps
     the heading AND the body, so the outline hugs the heading while collapsed
     and grows around the text as it opens: the body's height animates to 0, and
     a border drawn on each of the two siblings instead drew two boxes with a
     seam between them. Same 1px/#c9c9c9/10px as «הצגת גודל האירועים» above it.
     `box-sizing` matters here for the same reason it does there. */
  .fold6-mlegend-data-card {
    box-sizing: border-box;
    width: 100%;
    margin: 8px 0 0;
    padding: 12px 13px;
    border: 1px solid #c9c9c9;
    border-radius: 10px;
    background: none;
  }
  .fold6-mlegend-data-card[hidden] { display: none; }

  .fold6-mlegend-data-head {
    position: relative;
    display: block;
    width: 100%;
    /* No box of its own any more — the wrapper above is the card. The left
       padding is only the room the chevron sits in. */
    margin: 0;
    padding: 0 0 0 26px;
    border: 0;
    background: none;
    direction: rtl;
    text-align: right;
    /* 500, picked by eye — it heads a section rather than the panel. */
    font: 500 14px/1.4 var(--font-body);
    color: var(--muted-ui);
    -webkit-tap-highlight-color: transparent;
  }
  .fold6-mlegend-data-head::after {
    content: "";
    position: absolute;
    /* left 0 = flush with the heading's edge, i.e. the card's 13px padding
       away from the outline — the same distance the text keeps on the right
       (harness pick 2026-09-22; was 12px on top of the padding, ~25px in).
       The 1.25px lift compensates for the ink: only two sides of the box are
       drawn, so the pointing-down L's visual centre sits below the box centre.
       1px stroke, a hair lighter than the desktop note's 1.5. */
    left: 0;
    top: calc(50% - 1.25px);
    width: 6px;
    height: 6px;
    border-left: 1px solid currentColor;
    border-bottom: 1px solid currentColor;
    transform: translateY(-50%) rotate(calc(-45deg + var(--note-open, 0) * 180deg));
    color: #7a7a7a;
  }
  .fold6-mlegend-data-head[hidden], .fold6-mlegend-data-body[hidden] { display: none; }
  .fold6-mlegend-data-body {
    /* Inside the card, so it only needs the gap under the heading — the card's
       own padding supplies the sides and the bottom. */
    padding-top: 0;
    height: 0;
    opacity: 0;
    overflow: hidden;
  }
  .fold6-mlegend-data-body p {
    white-space: pre-line; /* FOLD6_NOTE_TEXT's \n */
    margin: 0;
    padding-top: 4px;
    direction: rtl;
    text-align: right;
    font: 14px/1.4 var(--font-body);
    color: var(--ink-note);
  }

  .fold6-mlegend-rows {
    direction: rtl;
    display: flex;
    justify-content: space-around;
    gap: 12px;
  }


  /* Exactly like @fold3 on screen: only the CAMP TITLE is centered over its
     column. The group rows are NOT — they all share the column's start (right,
     under dir:rtl) edge, so the swatches line up in one vertical run and the
     labels are right-aligned, the way updateGroups leaves them at @fold3
     (textAlign "right" while sideT is 0). */
  .fold6-mlegend-col {
    display: flex;
    flex-direction: column;
    align-items: flex-start;
    /* Content-width columns capped per camp (below) — the cap is what the
       labels wrap inside of. `min-width: 0` keeps a long label from pushing the
       column past that cap instead of wrapping in it. */
    flex: 0 1 auto;
    min-width: 0;
  }

  /* ONE width for both camps, and it is DERIVED, not chosen:
     160 = label wrap 118 + dot 8 + dot-to-label gap 10 + 2x12 row padding.
     The two camps used to carry different caps (124 / 160) because their
     longest keeper labels are ~1px apart and no shared width separated them —
     a rule for bare text. The rows are cards now, and cards in one panel have
     to read as one set, so both columns take the same width and every row
     fills it. Change the wrap width or the padding and this number is the sum
     again; it is not independently tunable.
     `width` as well as `max-width`: the column is a flex item, and max-width
     alone lets the shorter camp shrink-wrap under its cap, which puts the two
     back at different widths. */
  .fold6-mlegend-col.is-coalition,
  .fold6-mlegend-col.is-change {
    /* SOLVED from the card's own width, never a literal (explicit instruction —
       "all of the group cards inside it visible fully"). A flat 160px is wider
       than half the card once the card is inset to the title blocks' gutter:
       two columns + the 12px gap came to 332 inside 318, so the left column
       hung 6px outside the card and clipped its three group cards.
       The column is half the card's own width (`--mlg-card-w`, derived from the
       two side gaps, so nudging either re-solves this) minus the chrome between
       the two: the bar's 12px
       side padding twice, the panel's 4px twice, and the 12px column gap = 44.
       Keep --mlg-col-chrome in step with those three if any of them moves.
       `width` as well as `max-width`, and both in px rather than a percentage:
       `fold6MFlyMeasure` (js/groups.js) reads this computed max-width to work
       out the wrap the @fold4 flight has to freeze the labels at, and a
       percentage would come back un-resolved. */
    --mlg-col-chrome: 44px;
    width: calc((var(--mlg-card-w) - var(--mlg-col-chrome)) / 2);
    max-width: calc((var(--mlg-card-w) - var(--mlg-col-chrome)) / 2);
    flex: 0 0 auto;
    /* The rows stretch to the column instead of shrink-wrapping their text —
       this is what makes every card the same width. */
    align-items: stretch;
  }

  /* Removed — don't reintroduce: `.fold6-mlegend-camp`, the camp name heading
     each column (700/14px, centred over its own column, 8px to the first card).
     The legend carries no camp headings (explicit instruction — a compare/ pass
     against keeping their space and against a hairline between the camps); the
     two columns stand on their own. js/groups.js no longer builds them, which
     is also what takes the on-canvas camp headers out of the @fold4 flight. */

  /* EACH GROUP IS A CARD. The rows are the legend's filter buttons, so they
     have to read as tappable controls rather than lines of text — the fill and
     radius are the ACLED note's own (rgba(0,0,0,0.035), 16px), chosen in a
     compare/ pass against a hairline box, a chip and the house dash.
     Every card is the same width (the column, above) and at least the same
     height, so the six read as one set. */
  .fold6-mlegend-row {
    display: flex;
    /* centred, not flex-start: with a fixed card height a top-aligned label
       left a visible hole under the one-line groups. */
    align-items: center;
    /* dot -> label. FOLD6_MFLY_GAP_PX (js/groups.js) mirrors this — it is the
       gap @fold4's fly animates the row down to, so the two move together. */
    gap: 10px;
    margin-bottom: 6px;
    width: 100%;
    box-sizing: border-box;
    /* Both axes as variables, and the vertical one is read by `min-height`
       below too — so anything tuning the padding must move the VAR, not the
       `padding` shorthand, or the floor keeps the old value and the card stops
       getting shorter however far the padding is wound down. */
    --mlg-row-pad-y: 4px;
    --mlg-row-pad-x: 13px;
    padding: var(--mlg-row-pad-y) var(--mlg-row-pad-x);
    /* EVERY CARD THE SAME HEIGHT (explicit instruction — מפגינים חרדים, the one
       one-line label, was the odd one out). This is only the FLOOR, covering
       the two-line case before JS has measured: the real levelling is
       fold6MEqualiseRows (js/groups.js), which hands all six the tallest one's
       measured height, because the line count is not fixed — at 320px one label
       wraps to three. Solved from the type rather than written as a literal so
       it follows the padding and the font. */
    --mlg-row-lines: 2;
    min-height: calc(var(--mlg-row-lines) * 1.15 * 14px + 2 * var(--mlg-row-pad-y));
    /* Grey, a shade off the legend's own tint behind it, with a 1px seat under
       each card (Figma 345:2312, darkened by eye). */
    background: var(--line-light);
    border: none;
    border-radius: 10px;
    box-shadow: 0 1px 0 rgba(0, 0, 0, 0.07);
  }
  /* The card is a filter BUTTON on the folds that can answer one. Only the
     cursor and the dim say so — no hover treatment, since there is no hover on
     a phone and the sheet is dragged by the same finger that taps. */
  .fold6-mlegend-row.is-armed { cursor: pointer; }
  /* Filtered out. Matches .group-item.is-filtered-off's own 0.28 so the two
     breakpoints read identically, and transitions for the same reason that one
     does — it is a pure state flip, not a per-frame JS repaint. */
  .fold6-mlegend-row.is-filtered-off { opacity: 0.28; }
  /* 140ms, NOT the 260 the canvas rows use. These are buttons the reader taps,
     and a press wants to answer at once; 260 is the legend system's fade tempo
     and read as the button lagging behind the finger. The dots' own re-pack is
     a separate, much longer animation — this is only the button's own dim.
     Mobile-only: .group-item keeps 260 on desktop, where it fades rather than
     being pressed. */
  .fold6-mlegend-row { transition: opacity 140ms ease; }
  /* The PRESS, written on pointerdown (js/groups.js) and gone by the release.
     The real state (the dim above) cannot be decided until the finger lifts —
     the same gesture may be a drag of the sheet — so without this the button
     answers only after the release plus a 140ms fade. No transition here on
     purpose: a press has to be there the instant the finger is. */
  .fold6-mlegend-row.is-pressed { transition: none; background: #d2d2d2; }

  .fold6-mlegend-label { text-align: right; }

  /* 6px square + a 4px nudge down: the swatch centers on the label's INK, not
     on its taller line box — the same optical correction the canvas legend
     makes with groupLabelInkShift. */
  .fold6-mlegend-swatch {
    flex: none;
    /* 8px, mirrored by FOLD6_MFLY_SWATCH_PX (js/groups.js) — that constant is
       the size @fold4's fly lands the legend swatch at, so it must move with
       this or the dot arrives one size and snaps to another. */
    width: 8px;
    height: 8px;
    /* 1, not 4: the row centres its children now, so this is only the last
       optical nudge onto the label's text top rather than the old offset that
       pushed the dot onto a top-aligned label's first line. */
    margin-top: 1px;
  }

  /* Labels wrap as necessary (explicit instruction) — inside the column's half
     of the panel, so a long group name stacks lines instead of widening the
     column and squeezing the other camp. The swatch stays on the FIRST line
     (.fold6-mlegend-row is align-items: flex-start), exactly like the canvas
     rows at @fold3. */
  .fold6-mlegend-label {
    font: 14px var(--font-body);
    line-height: 1.15;
    color: var(--ink-note);
  }
}

/* ===== Desktop (>600px) stacking: title blocks over tooltips ===== */
@media (min-width: 601px) {
  /* Title blocks paint OVER every tooltip, across the board (explicit
     instruction). The hover event tooltip (.page9-tooltip) is 1000 and the
     category tooltip (.page9-cat-tooltip) 1003 — that one's above-the-cards
     number is a MOBILE ladder (see its own comment), and this override outranks
     it here — so the cards clear both at 1004. Out-stacking works from inside
     .text-col only because .text-col has no z-index of its own. */
  .section-text.text-card,
  .page9-title-row .text-card { z-index: 1004; }
  /* The plain sections' cards need a position for that z-index to bite —
     @fold12's and @fold13's are already sticky/fixed inside their own wrappers,
     and the child combinator keeps this from clobbering that. */
  .text-section > .section-text.text-card { position: relative; }
  /* The dragged pill's ghost is NOT a tooltip — it rides the cursor and must
     stay visible over everything, cards included. */
  .page9-pill-ghost { z-index: 1005; }
  /* The mini-legend's ACLED note layer paints OVER the title blocks (explicit
     instruction: the note sits in front of whatever it collides with). 1005
     clears the cards' 1004 and the event tooltip's 1000; the category tooltip
     (1006) still rides above it. The layer itself stays
     pointer-events: none, so only .fold6-note-link catches clicks. Desktop only:
     the note is hidden on mobile. */
  .fold6-note-layer { z-index: 1005; }
  /* @fold5's demo cursor glides UNDER the title block (explicit instruction):
     1003 sits below the cards' 1004 and still above the event tooltip's 1000
     and the squares it points at. Mobile's hand (.fold7-touch) has the same
     1003 in the ≤600px block. */
  .fold7-cursor { z-index: 1003; }
}

/* @fold8's legend filter: one click strip per legend row, inside that
   column's hover box (js/groups.js → fold6LegendFilterEl). Full box width,
   the row's own pitch tall. */
.fold6-legend-filter {
  position: absolute;
  left: 0;
  right: 0;
  cursor: pointer;
}
/* A group filtered out of the timeline. Set on .group-item, which never
   receives an inline opacity, so this can't be fought by the per-frame writes;
   opacity on the row multiplies down into swatch and label together. */
.group-item.is-filtered-off {
  opacity: 0.28;
}
/* Hovering a filtered-out row lifts it a step (.28 → .4) — "this one comes
   back on a click" — without reading as switched back on. */
.group-item.is-filtered-off.is-filter-hover {
  opacity: 0.4;
}
/* Hovering a click strip (@fold8+) says "this row is a button": the hovered
   row's label lifts to black and its swatch grows in place (transform, so the
   per-frame width/height/top writes are untouched), while every other row
   steps back to .45 — the filtered-off rows stay at their own .28. Pure state
   flips, so these are the one place .group-item / .group-swatch transition;
   the opacity one also softens the is-filtered-off flip above, on purpose. */
.group-item { transition: opacity 260ms ease; }
.group-swatch { transition: transform 260ms ease; }
.group-item.is-filter-hover .group-label { color: #000; }
/* The hovered row's label is STRUCK THROUGH — a preview of what the click does
   (filter the group out) — and a filtered-out row keeps the line for as long as
   it stays out, so the legend shows which groups are hidden, not just what a
   click would do. The hover strips and .is-filtered-off are the desktop
   mini-legend's; mobile's מקרא rows have their own rules in the 600px block. */
@media (min-width: 601px) {
  .group-item.is-filter-hover .group-label,
  .group-item.is-filtered-off .group-label { text-decoration: line-through; }
}
/* @fold7's crossing flashes that same line across every row once — it fades in
   over --filter-flash-in-ms, holds, and fades out over --filter-flash-out-ms
   (fold9FilterFlash, js/groups.js, swaps the two classes). Rows already struck
   (filtered off) keep their own solid line, and while ANY row is hovered the
   flash steps aside: only the hovered row is struck, as on @fold8. Desktop only. */
@media (min-width: 601px) {
  .groups-overlay.is-filter-flash-in:not(.is-filter-hover-any) .group-item:not(.is-filter-hover):not(.is-filtered-off) .group-label {
    text-decoration: line-through;
    animation: filter-flash-in var(--filter-flash-in-ms, 100ms) ease-in-out both;
  }
  .groups-overlay.is-filter-flash-out:not(.is-filter-hover-any) .group-item:not(.is-filter-hover):not(.is-filtered-off) .group-label {
    text-decoration: line-through;
    animation: filter-flash-out var(--filter-flash-out-ms, 450ms) ease-in-out both;
  }
}
@keyframes filter-flash-in {
  from { text-decoration-color: transparent; }
  to   { text-decoration-color: currentColor; }
}
@keyframes filter-flash-out {
  from { text-decoration-color: currentColor; }
  to   { text-decoration-color: transparent; }
}
.group-item.is-filter-hover .group-swatch { transform: scale(1.5); }
.groups-overlay.is-filter-hover-any .group-item:not(.is-filter-hover):not(.is-filtered-off) {
  opacity: 0.45;
}

/* MOBILE: while the reader's own press-and-hold is live — the timeline/@fold12
   picker (p7InspectInit, page7.js) or @fold5's user hold (fold7HoldArm,
   js/groups.js) — the title blocks SNAP out, and snap back on release. No
   transition on purpose. The demo's own (non-user) tooltip never sets this. */
@media (max-width: 600px) {
  body.is-tip-holding .text-card { visibility: hidden; }
}


/* ── English version (en/index.html, <html class="lang-en">) ────────────────
   Same look as the Hebrew page; only the reading direction of the title blocks
   and the event tooltip flips. Everything else stays on the shared rules. */
.lang-en .text-card,
.lang-en .page9-tooltip {
  direction: ltr;
  text-align: left;
}
.lang-en .page9-tooltip-date,
.lang-en .page9-tooltip-desc {
  text-align: left;
}

/* Language switch (all three pages) — ONE button, globe + the current
   language's letters, that opens a drop-down of the three languages
   (js/lang-switch.js). #langWrap carries the corner position AND the card:
   the look is the legend's «איסוף הנתונים» note card — the same tint over the
   page paper, 16px corners — and when open the card simply grows to hold the
   rows (compare/ pick 2026-10-03 over an outlined box, a bare glyph, and a
   darkened-page dialog). Top-left corner; the dev fold badge and its picker
   hang BELOW it (see the end of this file). Above the mobile legend layer
   (1006), below the gate. */
.lang-wrap {
  position: fixed;
  top: 12px;
  left: 12px;
  z-index: 1007;
  direction: ltr;
  border-radius: 16px;
  /* OPAQUE, like .fold6-note-card: the tint over --bg, so nothing scrolling
     under the card shows through it. */
  background: linear-gradient(rgba(0, 0, 0, 0.035), rgba(0, 0, 0, 0.035)), var(--bg);
}
/* The card's TOP edge sits on the corner logo's top edge (--se-top on
   .seventh-eye: 8px phone, 24px desktop) — explicit instruction 2026-10-03.
   left 24px on desktop mirrors the logo's 24px from the right (--se-right) —
   both moved 20 → 24 on 2026-10-02, with the legend columns
   (FOLD6_LEGEND_INSET_*, js/groups.js) — one shared desktop edge gap. */
.lang-wrap { top: 8px; }
@media (min-width: 601px) {
  .lang-wrap { top: 24px; left: 24px; }
}
.lang-switch {
  display: inline-flex;
  align-items: center;
  gap: 5px;
  margin: 0;
  cursor: pointer;
  /* The note title's own type: 14px Assistant 600 in its #767676 grey
     (.fold6-note-title); 13px on the phone, where the old letters were 13. */
  font: 600 13px/1 var(--font-body);
  color: var(--muted-ui);
  background: transparent;
  border: 0;
  border-radius: 16px;
  padding: 7px 12px;
  transition: color var(--ui-fade-ms) ease;
}
@media (min-width: 601px) { .lang-switch { font-size: 12px; } }
.lang-switch:hover, .is-open > .lang-switch { color: var(--ink); }
.lang-switch-globe { width: 14px; height: 14px; display: block; }
.lang-switch-code { display: block; line-height: 1; }
/* PHONE: the globe alone. AFTER the base rule above — same specificity, so
   source order is what makes it win. */
@media (max-width: 600px) {
  /* 6px around a 16px globe — manual/-baked 2026-10-03. No pressed look on a
     tap (explicit instruction): no tap highlight, and the colour change is
     desktop's hover only, so a touch leaves the glyph as it was. */
  .lang-switch { padding: 6px; gap: 0; -webkit-tap-highlight-color: transparent; }
  .lang-switch:hover, .is-open > .lang-switch { color: var(--muted-ui); }
  .lang-switch-globe { width: 16px; height: 16px; }
  .lang-switch-code { display: none; }   /* phone: the globe alone */
}

/* The rows sit INSIDE the same card, in flow, so the card expands around them.
   Each row is the «הצגת גודל האירועים» control (.p7-scope-btn): 14px Assistant
   at rgba(0,0,0,.81), an empty 10px ring 8px before it that FILLS dark for the
   current language — no tick, no bold. Hover darkens the ring's edge exactly
   as the scope button's does, but WITHOUT its 3.5px swell (explicit
   instruction): the ring stays 10px. */
/* The card GROWS around the rows rather than snapping open — the 0fr → 1fr
   grid trick, 180ms (the scope button's own flip tempo), both breakpoints.
   js/lang-switch.js never sets `hidden` (it would cut the transition);
   aria-hidden + visibility carry the closed state. */
.lang-menu {
  display: grid;
  /* BOTH axes collapse: a 0fr row alone still lends the card the widest row's
     width, so a closed card sat as wide as «العربية». */
  grid-template-rows: 0fr;
  grid-template-columns: 0fr;
  padding: 0;
  visibility: hidden;
  transition: grid-template-rows var(--ui-fade-ms) ease, grid-template-columns var(--ui-fade-ms) ease,
              padding var(--ui-fade-ms) ease, visibility 0s var(--ui-fade-ms);
}
.lang-menu[hidden] { display: grid; }
.lang-menu-rows { overflow: hidden; min-height: 0; min-width: 0; }
.lang-wrap.is-open .lang-menu {
  grid-template-rows: 1fr;
  grid-template-columns: 1fr;
  padding: 0 0 6px;
  visibility: visible;
  transition: grid-template-rows var(--ui-fade-ms) ease, grid-template-columns var(--ui-fade-ms) ease,
              padding var(--ui-fade-ms) ease;
}
.lang-menu-row {
  display: flex;
  align-items: center;
  gap: 8px;
  /* The ring's centre sits on the globe's: desktop globe 14px behind 12px of
     padding → centre 19px, so the 10px ring starts 14px in. */
  padding: 6px 12px 6px 14px;
  font: 14px/17px var(--font-body);
  /* Lighter than the scope button's .81 at rest, black on hover — the label
     darkens with its ring (explicit instruction, 2026-10-03). */
  color: rgba(0, 0, 0, 0.6);
  text-decoration: none;
  white-space: nowrap;
  transition: color var(--ui-fade-ms) ease;
}
.lang-menu-row:hover { color: #000; }
.lang-menu-row[lang="ar"] { font-family: var(--font-ar); }
.lang-menu-row::before {
  content: "";
  flex: 0 0 auto;
  box-sizing: border-box;
  width: 10px; height: 10px;
  border-radius: 50%;
  border: 1px solid rgba(0, 0, 0, 0.45);
  background: transparent;
  transition: background-color var(--ui-fade-ms) ease, border-color var(--ui-fade-ms) ease;
}
.lang-menu-row:hover::before { border-color: rgba(0, 0, 0, 0.75); }
.lang-menu-row.is-current::before {
  background-color: var(--ink-soft);
  border-color: var(--ink-soft);
}
/* The CURRENT language reads black at rest and is not a choice: no hover, no
   pointer, no click (it would only reload the page) — explicit instruction
   2026-10-03. Keyboard reaches it too, so js/lang-switch.js moves focus to
   the first OTHER row. */
.lang-menu-row.is-current {
  color: #000;
  pointer-events: none;
  cursor: default;
}
/* PHONE: 16px globe behind 6px → centre 14px, so the ring starts 9px in.
   AFTER the base row rule — same specificity, source order decides. */
@media (max-width: 600px) {
  .lang-menu-row { padding-left: 9px; }
}

/* העין השביעית's logo — fixed in the top-RIGHT corner on both pages, a link to
   the7eye.org.il. The artwork is a MASK (alpha-only PNG), so the mark's colour
   is plain `background-color` on .seventh-eye-mark; the <a> around it is the
   optional backing plate. DESKTOP: the wide lockup in the corner with the line
   «פרויקט בשיתוף» to its right. PHONE: the same lockup centred at the top,
   line above, fading out on scroll. Every value is a custom property set per
   breakpoint (phone in the base rule, desktop in the 601px block), so tuning
   one never moves the other. */
.seventh-eye {
  /* PHONE: the wide lockup centred at the top of the screen with the line
     above it; it fades away once scrolling starts (see the
     600px block below). */
  --se-w: 120px;                /* width of the mark; height follows the artwork */
  --se-top: 8px;
  --se-right: 12px;             /* desktop only — the phone centres it */
  --se-ink: #393239;            /* העין השביעית's own dark grey — mark and line, both breakpoints */
  --se-color: var(--se-ink);
  --se-opacity: 1;
  --se-plate: transparent;      /* backing plate fill */
  --se-pad: 0px;                /* plate padding around the mark */
  --se-radius: 0px;
  /* The «פרויקט בשיתוף» line. */
  --se-cap-size: 14px;
  --se-cap-weight: 400;
  --se-cap-color: var(--se-ink);
  --se-cap-opacity: 1;
  --se-cap-gap: 8px;            /* between the line and the mark */
  --se-cap-spacing: 0.04em;     /* letter-spacing */
  --se-cap-font: var(--font-body);
  --se-cap-align: center;       /* right | center | left — only matters with --se-dir: column */
  --se-mark-ml: auto;           /* column only: which edge the mark follows (auto/0, auto/auto, 0/auto) */
  --se-mark-mr: auto;
  --se-cap-display: block;      /* none = the line is switched off */
  --se-dir: column;             /* column = text ABOVE the mark; row = text to its RIGHT */
  --se-items: stretch;          /* stretch with column, center with row */
  --se-cap-y: 0px;              /* optical up/down nudge of the line (row layout) */
  --se-fade-ms: 300ms;          /* phone: how long the scroll fade takes */
  position: fixed;
  top: var(--se-top);
  right: var(--se-right);
  z-index: 1007;
  display: flex;
  /* rtl on the Hebrew page, so `row` puts the text on the mark's right. */
  direction: rtl;
  flex-direction: var(--se-dir);
  align-items: var(--se-items);
  gap: var(--se-cap-gap);
  padding: var(--se-pad);
  background: var(--se-plate);
  border-radius: var(--se-radius);
  opacity: var(--se-opacity);
  line-height: 0;
  text-decoration: none;
}
.seventh-eye-caption {
  display: var(--se-cap-display);
  font: var(--se-cap-weight) var(--se-cap-size)/1 var(--se-cap-font);
  letter-spacing: var(--se-cap-spacing);
  color: var(--se-cap-color);
  opacity: var(--se-cap-opacity);
  text-align: var(--se-cap-align);
  white-space: nowrap;
  transform: translateY(var(--se-cap-y));
}
/* The WIDE lockup (name beside the eye). */
.seventh-eye-mark {
  display: block;
  margin-left: var(--se-mark-ml);
  margin-right: var(--se-mark-mr);
  width: var(--se-w);
  aspect-ratio: 984 / 239;
  background-color: var(--se-color);
  -webkit-mask: url("seventh-eye-logo-wide.png") center / contain no-repeat;
  mask: url("seventh-eye-logo-wide.png") center / contain no-repeat;
}
@media (min-width: 601px) {
  .seventh-eye {
    --se-w: 120px;
    --se-top: 24px;
    --se-right: 24px;             /* the one desktop edge gap — same as the legend and the EN button */
    --se-color: var(--se-ink);
    --se-opacity: 1;
    --se-plate: transparent;
    --se-pad: 0px;
    --se-radius: 0px;
    --se-cap-size: 14px;
    --se-cap-weight: 300;
    --se-cap-color: var(--se-ink); /* matches the idle mark; does NOT follow it on hover */
    --se-cap-opacity: 1;
    --se-cap-gap: 12px;
    --se-cap-spacing: 0.04em;
    --se-cap-font: var(--font-body);
    --se-cap-align: center;
    --se-mark-ml: 0;
    --se-mark-mr: 0;
    --se-cap-display: block;
    --se-dir: row;                /* desktop: the line sits to the mark's RIGHT */
    --se-items: center;
    /* Optical centring: Assistant's Hebrew letter body (baseline to 0.555em)
       sits 0.09em below the middle of a line-height-1 box — 1.25px at 14px. */
    --se-cap-y: -1.25px;
  }
}
/* PHONE: centred instead of cornered (same wide lockup), and gone once the
   reader scrolls — js/nav.js flips .is-scrolled past SEVENTH_EYE_FADE_SCROLL_PX.
   A pure state flip, so a CSS transition. Text, not a dot: it may fade. */
@media (max-width: 600px) {
  .seventh-eye {
    right: auto;
    left: 50%;
    transform: translateX(-50%);
    transition: opacity var(--se-fade-ms);
  }
  .seventh-eye.is-scrolled {
    opacity: 0;
    pointer-events: none;
  }
}
/* Hover (desktop, real pointers only): the mark alone turns the brand's red;
   the line beside it keeps its idle colour. A pure state flip, so a CSS
   transition. */
@media (min-width: 601px) and (hover: hover) {
  .seventh-eye-mark { transition: background-color 150ms; }
  .seventh-eye:hover .seventh-eye-mark { background-color: #EC2A2C; }
}
/* English page, DESKTOP: the SAME wide lockup as the Hebrew corner (the
   artwork the user calls "logo2" — put back 2026-10-02; the cropped stacked
   mark it briefly wore is removed, don't reintroduce), only reading
   left-to-right so the line sits to the mark's LEFT. The English phone is the
   same as the Hebrew phone: the wide lockup, line above, centred. */
@media (min-width: 601px) {
  .lang-en .seventh-eye { direction: ltr; }
}

/* @fold14's title block: «הפרויקט נוצר בשיתוף», then the STACKED artwork (eye
   above the name, uncropped) on its own line, both centred in the block. A
   mask, so its colour is --pl-color. Height and gap are set per breakpoint —
   phone in the base rule, desktop in the 601px block. */
.partner-title { text-align: center; }
.partner-logo {
  --pl-h: 84px;                 /* height of the artwork; width follows it */
  --pl-gap: 20px;               /* between the text line and the logo */
  --pl-color: var(--ink);          /* = .section-title's own colour */
  --pl-ms: auto;                /* auto / auto = centred; 0 / auto = starts at the text's edge */
  --pl-me: auto;
  --pl-opacity: 1;
  display: block;
  opacity: var(--pl-opacity);
  height: var(--pl-h);
  aspect-ratio: 600 / 788;
  margin-block-start: var(--pl-gap);
  margin-inline-start: var(--pl-ms);
  margin-inline-end: var(--pl-me);
  background-color: var(--pl-color);
  -webkit-mask: url("seventh-eye-logo.png") center / contain no-repeat;
  mask: url("seventh-eye-logo.png") center / contain no-repeat;
}
/* The words themselves, in their own span so their opacity (and, if ever
   tuned, size) can differ from the logo's without touching the frame.
   --pt-size is UNSET as shipped: the text is the shared .section-title size. */
.partner-text {
  font-size: var(--pt-size, inherit);
  opacity: var(--pt-opacity, 1);
}
@media (min-width: 601px) {
  .partner-logo {
    --pl-opacity: 1;
    --pl-h: 100px;
    --pl-gap: 24px;
    --pl-color: var(--ink);
    --pl-ms: auto;
    --pl-me: auto;
  }
}
@media (min-width: 601px) and (hover: hover) {
  .partner-logo { transition: background-color 150ms; }
  .partner-logo:hover { background-color: #EC2A2C; }
}

/* English hero text reads left-to-right. Same boxes and positions as the Hebrew
   hero; only the direction, so a word wider than the title's box («Extremists»)
   keeps its LEFT edge on the box and spills rightward, lining up with the lines
   under it. */
.lang-en .page0-title,
.lang-en .page0-subtitle {
  direction: ltr;
}


/* English pills read left-to-right: the grip handle leads on the LEFT, label
   after it. Set on the pill itself (and the drag ghost, which lives on <body>),
   so only the pill's own contents flip — the tray's placement is untouched.
   Desktop only; the phone's tick-box pills keep the shared layout. */
@media (min-width: 601px) {
  .lang-en .page9-pill,
  .lang-en .page9-pill-ghost {
    direction: ltr;
  }
}

/* English ACLED note reads left-to-right, and its heading and chevron trade
   places: heading ranged LEFT, chevron on the card's right edge. updateGroups
   places both (padding-left carries the heading on the card's moving left edge),
   hence border-box: the padding must come out of the box's fixed width. */
.lang-en .fold6-note {
  direction: ltr;
  text-align: left;
}
.lang-en .fold6-note-title {
  direction: ltr;
  text-align: left;
  box-sizing: border-box;
}
/* …but not in the phone's מקרא panel, where the heading flows and has no chevron. */
.lang-en .fold6-note-title.is-in-panel {
  padding-left: 0 !important;
}

/* English drop-zone hint. */
.lang-en #page9ZoneAbove:empty::after {
  content: "Drag extreme action types here";
}

/* English credits card (@fold16) reads left-to-right. */
/* Only the body copy ranges left — the title and the credits stay centred, as
   on the Hebrew card. */
.lang-en #page-17 .text-card-frame {
  direction: ltr;
}
.lang-en #page-17 .page12-body {
  text-align: left;
}

/* English pill tooltip reads left-to-right. */
.lang-en .page9-cat-tooltip-desc {
  direction: ltr;
}

/* The dev fold badge sits under the language switch, which owns the corner. */
.fold-number-badge { top: 46px; }
.fold-picker { top: 74px; }

/* English scope button: ring first, label to its right (it sits over the LEFT
   legend column — updateGroups places it). */
.lang-en .p7-scope-btn {
  flex-direction: row;
}

/* MOBILE, @fold2: the first title block arrives 300px of scrolling sooner
   (manual/-baked 2026-09-29). Same device as desktop's G1: the card is pulled
   up from its centred spot (334px → 34px) and the section is shortened by the
   same 300px, so the @fold2 → @fold3 card distance is unchanged. */
@media (max-width: 600px) {
  #page-1 {
    align-items: flex-start;
    padding-top: 34px;
    min-height: calc(100vh - 300px);
  }
}

/* English hero title on the PHONE. At the shared 32px «Extremists» measures
   184px against the Hebrew title's 127px, overran its 175px column and read far
   larger than the Hebrew hero. 24px brings it to 138px. `top` is re-solved for
   the new size so the last baseline stays where the shared rule puts it (20px
   above the dot column) — the two are one setting. Phone only; desktop keeps the
   shared 40px. */
@media (max-width: 600px) {
  .lang-en .page0-title {
    font-size: 24px;
    top: calc(var(--page0-half, 50vh) - 197.9px + var(--page0-drop, 0px) + var(--page0-trim, 0px));
  }
  /* …and the subtitle comes down a step with it: 16px (shared: 18px). `top`
     re-solved the same way, for the same last baseline. */
  .lang-en .page0-subtitle {
    font-size: 16px;
    top: calc(var(--page0-half, 50vh) - 154.4px + var(--page0-drop, 0px) + var(--page0-trim, 0px));
  }
}

/* English page, PHONE only. */
@media (max-width: 600px) {
  /* Camp names on two lines («The Right-Wing / Coalition», «The Change / Bloc»):
     on one line the right camp's name ran to the screen edge. The break is the
     \n in the string (js/i18n.js). */
  .lang-en .camp-header {
    /* `pre`, not `pre-line`: the \n is the ONLY break — otherwise the name also
       breaks at the hyphen and «The Right-Wing» falls onto two lines of its own. */
    white-space: pre;
    text-align: center;
    direction: ltr;
    line-height: 1.15;
  }
}

/* English page, PHONE only — left-to-right reading order. */
@media (max-width: 600px) {
  /* @fold3/@fold4 rows: the flying label is anchored by its LEFT edge (it trails
     right of its swatch — updateGroups), so it drops the shared translateX. */
  .lang-en .group-label.is-mfly-topanchor {
    transform: none;
  }
  /* The legend sheet: swatch first, label ranged left. Set per ROW, not on
     .fold6-mlegend-rows, so the two camps keep their sides. */
  .lang-en .fold6-mlegend-row {
    direction: ltr;
  }
  .lang-en .fold6-mlegend-label {
    text-align: left;
  }
  .lang-en .fold6-mlegend-data-head,
  .lang-en .fold6-mlegend-data-body p {
    direction: ltr;
    text-align: left;
  }
  /* @fold12: the ghost title block, the stuck title and the action prompt all
     range LEFT. The stuck title travels to the LEFT edge, so the measured flush
     distance is applied negated. */
  .lang-en .text-card-frame.fold12-ghost {
    direction: ltr;
    text-align: left;
  }
  .lang-en .page9-title-row .text-card.is-stuck .text-card-frame {
    transform: translateX(calc(-1 * var(--p9-title-flush, 0px)));
  }
  .lang-en .page9-title-row .page9-header-subtitle {
    text-align: left;
  }
  /* @fold12's pill row starts at the LEFT and scrolls rightward; the rule under
     it draws in from the left to match. */
  .lang-en #page9ZoneBelow {
    direction: ltr;
  }
  .lang-en .page9-tray::after {
    transform-origin: left center;
  }
}

/* English page, PHONE: @fold12's left edge is the language button's (12px), not
   the card column's 24px — the pinned title, the action prompt and the first
   pill all start on it. The title is carried there by its own stick transform
   (the measured flush distance, negated, plus these 12px). */
@media (max-width: 600px) {
  .lang-en .page9-title-row .text-card.is-stuck .text-card-frame {
    transform: translateX(calc(-1 * var(--p9-title-flush, 0px) - 12px));
  }
  .lang-en .page9-title-row .page9-header-subtitle {
    margin-left: -12px;
  }
  .lang-en #page9ZoneBelow {
    padding-inline: 12px;
  }
}

/* ── Arabic version (ar/index.html, <html class="lang-ar">) ──────────────────
   Shares the Hebrew page's RTL layout entirely; only the faces change, because
   Discordia / Hadassah / Assistant carry no Arabic glyphs. Both are Google
   Fonts, linked from ar/index.html (translate_ui_ar.py writes the <link>):
   Beiruti stands in wherever the Hebrew page uses Discordia (title blocks and
   the hero title), IBM Plex Sans Arabic wherever it uses Assistant. Sizes and
   weights stay the shared rules'. */
.lang-ar .section-title,
.lang-ar .page0-title {
  font-family: 'Beiruti', 'Discordia', serif;
}
/* Optical size match: Discordia's Hebrew letters stand 0.68em tall, Beiruti's
   Arabic body 0.39em with alefs at 0.63em (measured with fontTools), so at the
   shared 18.5px / 16px the Arabic titles read ~13% smaller. 21px / 18px lands
   them at the same visual size — the one language-gated exception to the
   .section-title single-size rule (#page-17 stays its own 36/26 exception). */
.lang-ar .section-title { font-size: 21px; }
@media (max-width: 600px) {
  .lang-ar .section-title { font-size: 18px; }
}
.lang-ar .page0-subtitle,
.lang-ar .text-card,
.lang-ar .page12-body,
.lang-ar .page12-credits,
.lang-ar .page9-header-subtitle,
.lang-ar .page9-tray-title,
.lang-ar .page9-pill,
.lang-ar .page9-pill-ghost,
.lang-ar .page9-tooltip,
.lang-ar .page9-cat-tooltip-desc,
.lang-ar .fold6-note,
.lang-ar .fold6-mlegend-row,
.lang-ar .p7-scope-btn,
.lang-ar .seventh-eye-caption,
.lang-ar .camp-header,
.lang-ar .group-label {
  font-family: var(--font-ar);
}
