/* ============================================================================
   Storyboard viewer — shared editorial "proof plate" + expand-to-inspect
   lightbox for the Boss Taylor explainer storyboards.

   One implementation, used by every public page. Do not fork this per page.

   Colour note: every custom property below carries an explicit fallback. This
   file is loaded on nine pages with different :root token sets, and an
   undefined var() resolves to nothing — which silently renders transparent.
   Fallbacks are not belt-and-braces here, they are the contract.

   REVIEW-2026-07-26 Site-H1 REJECTED-by-design: the cache-bust token on this
   file is guard-enforced, not hand-maintained, so a hash/token mismatch read
   out of a stale tree is not evidence of live drift. The 62nd-pass review filed
   drift on the premise that this file was an uncommitted rewrite hashing to
   cefb3212 while five pages linked 0906c112. Neither number was current: the
   rewrite had already shipped in b7ef0431, the working tree was clean against
   HEAD, and all five pages linked the correct token. 0906c112 is the PRE-restamp
   value from 11e11a37 — the reviewer read that commit's historical guard warning
   as present state. The design assumption this rejection rests on:
   .claude/hooks/check-frontend-changes.sh recomputes this file's md5 on every
   Stop and reports any page whose ?v= token disagrees, so the token cannot
   silently drift across a session boundary — the guard, not a ledger row, is the
   mechanism. Verify current state by running that hook (exit 0 + no CACHE-BUST
   DRIFT line) before filing token drift again.
   ========================================================================= */

/* ── The plate (inline state) ─────────────────────────────────────────── */

/* ── The wrapper ───────────────────────────────────────────────────────────
   This was a warm full-bleed band — background #F7F6F2, edge to edge — that
   deliberately interrupted the dark page. It was the wrong device. Keith, on
   seeing it: "It is spanning the entire fucking page like an inline hero."
   That is exactly what a full-width slab does. A storyboard is a figure
   INSIDE an argument, not a section break between arguments, and a reader
   scrolling the proof page should never lose the page to an illustration.

   So the wrapper now mirrors the host page's own reading column and carries
   NO background of its own. The artwork keeps its warm paper through
   .sb-plate__frame, which reads as a card sitting on the dark page — the art
   direction survives, the hero does not.

   Consequence that must not be missed: with the slab gone, the label and
   caption sit on the DARK page again, so the dark-page text palette (this
   file's default) is the correct one. The old `.sb-band .sb-plate__*` light
   overrides are deleted, not retained — leaving them would paint #3A342C
   caption text onto a #0D0D0D page, which is this file's documented silent
   failure: present, correctly positioned, unreadable. */
.sb-band {
  max-width: var(--content-width, 960px);
  margin: 0 auto;
  padding: 40px 32px;
}

/* platform.html mounts the wrapper INSIDE .glimpses, which is already the
   960px column carrying its own 32px gutter. Re-applying the wrapper's column
   and gutter there indents the figure 32px further than the prose it sits
   under — measured: figure left 537 against a text edge of 505. Where the host
   element IS already the column, the wrapper contributes vertical rhythm only.
   Verify with the alignment check, not by eye: figure left edge must equal the
   host column's content-box left edge. */
.glimpses > .sb-band {
  max-width: 100%;
  padding-left: 0;
  padding-right: 0;
}

/* A plate is a figure ON the page, never the page itself. Two ceilings:

   1. WIDTH — the reading column, and not one pixel more. The wrapper above
      supplies it, so the plate simply fills its column. Earlier revisions
      tried 1400px and then 1120px; both were wider than the prose they
      illustrate, which is how a figure starts reading as a destination.
   2. HEIGHT — the artwork may not own the whole screen. Every storyboard is
      drawn 1672x941, so plate width IS plate height: a 1400px plate rendered
      788px of image and the whole thing measured 1135px tall inside a 1080px
      viewport. Deriving a width back from a height ceiling keeps the top of
      the plate and its caption on screen together.

   min() takes whichever ceiling binds first, so a short laptop gets a smaller
   plate rather than a cropped one, and a phone is governed by 100% as before. */
.sb-plate {
  --sb-art-max-h: 60vh;
  /* FIX Site-M1 (2026-07-26 62nd-pass review): this comment used to claim
     max-width was "the floor this rule falls back to if the width declaration
     is ever dropped." That stopped being true when the value changed from
     1120px to 100%: max-width:100% is already the effective default for a block
     element in this flow, so it is not a fallback at all — it promised a guard
     that no longer existed.

     The honest statement: the WRAPPER (.sb-band) is now the width authority.
     It mirrors the host page's reading column, so if the width declaration
     below is ever dropped at computed-value time the plate simply fills its
     column — which is the correct result, not a regression. The old numeric
     floor existed to bound a plate that escaped the column; no plate does that
     any more. max-width:100% is kept as belt-and-braces against a host that is
     wider than the column, and is deliberately NOT a numeric value — putting a
     px ceiling back here is how a figure starts growing past its column again. */
  max-width: 100%;
  width: min(100%, calc(var(--sb-art-max-h, 60vh) * 1672 / 941));
  margin: 0 auto;
}

/* Mobile browsers report vh as the LARGE viewport — toolbars retracted — which
   over-states the room a landscape phone actually has. svh is the honest
   number where it exists. Kept as a token override rather than a second min()
   so an unsupported unit can never invalidate the width declaration itself. */
@supports (height: 1svh) {
  .sb-plate { --sb-art-max-h: 60svh; }
}

/* ── Interlude: narrative between two adjacent figures ────────────────── */
/* The beat between two plates. This used to exist because the plates were
   full-bleed bands and a sentence left in the page's own column landed on a
   different left edge than the artwork above and below it — it read as a
   rendering seam rather than as copy. Now that the plates ARE in the reading
   column, the interlude only has to mirror that same column, which is what
   it does. One left edge down the whole page. */
.sb-interlude {
  max-width: var(--content-width, 960px);
  margin: 0 auto;
  padding: 8px 32px;
}

.sb-interlude__inner {
  max-width: 100%;
  margin: 0;
}

.sb-interlude p {
  max-width: 76ch;
  margin: 0;
  font-size: 1.05rem;
  line-height: 1.7;
  color: #D6D2CA;
}

/* RETIRED — the breakout. It existed on the reasoning that a proof plate
   "earns more room" than the 960px reading column gives it. That reasoning
   was wrong for the same reason the full-bleed band was wrong: a figure that
   pushes past the column it belongs to stops illustrating the argument and
   starts competing with it. Every plate now sits in the column.

   The class is still on the-question.html, so this rule is kept and
   neutralised rather than deleted — a bare `.sb-plate--breakout` with no rule
   would silently inherit .sb-plate, which is the intent, but leaving the
   selector here documents that the retirement was a decision and not an
   oversight. The old transform is what made the plate a containing block for
   position:fixed descendants; with it gone that hazard is gone too, but the
   lightbox stays mounted on <html> by the script regardless — <body> is
   transformed on several of these pages. See the MOUNT POINT comment in
   storyboard-viewer.js. */
.sb-plate--breakout {
  width: min(100%, calc(var(--sb-art-max-h, 60vh) * 1672 / 941));
  max-width: 100%;
  margin-left: auto;
  transform: none;
}

/* ── Text palette ─────────────────────────────────────────────────────────
   The plate's own artwork is warm paper, but the pages it sits on are dark.
   The dark-page palette below is therefore the ONLY palette.

   FIX Site-M1 (2026-07-26 62nd-pass review): this used to add "the light
   palette applies only inside .sb-band, where the surrounding band is warm."
   Both halves are now false and contradicted further down this same file: the
   band carries no background at all, and the `.sb-band .sb-plate__*` light
   overrides were DELETED (not commented out) when the warm slab went, precisely
   so they could not paint #3A342C on a dark page. There is no light-background
   context left in this component, so there is no light palette to select. Do not
   reintroduce one without also reintroducing a light background behind it —
   that pairing is the invariant, and breaking it is this file's documented
   silent failure: text present, correctly positioned, unreadable.

   Contrast on #0D0D0D: label 7.9:1, caption 11.2:1, link 5.6:1 — all clear of
   WCAG AA. */

.sb-plate__label {
  font-size: 0.72rem;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: #A9A399;
  margin: 0 0 16px;
  font-weight: 600;
}

.sb-plate__frame {
  display: block;
  width: 100%;
  border-radius: 8px;
  overflow: hidden;
  background: #F7F6F2;
  box-shadow: 0 1px 2px rgba(24, 22, 18, 0.10), 0 8px 32px rgba(24, 22, 18, 0.10);
}

.sb-plate__img {
  display: block;
  width: 100%;
  height: auto;
}

/* Header row: label on the left, expand control on the right.
   The control deliberately does NOT float over the artwork. Overlaid at
   top-right it covered the storyboards' own headline type — and because every
   storyboard composes differently, there is no safe corner. A control that
   obscures the thing it exists to reveal is a defect, not a style choice. */
.sb-plate__head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 16px;
  margin-bottom: 16px;
}

.sb-plate__head .sb-plate__label {
  margin: 0;
}

/* The expand affordance. A real <button>, not a hover-only cue. */
.sb-plate__expand {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  flex: none;
  min-height: 44px;
  padding: 0 16px;
  border: 1px solid rgba(214, 210, 202, 0.30);
  border-radius: 8px;
  background: rgba(255, 255, 255, 0.06);
  color: #E4E0D8;
  font: inherit;
  font-size: 0.82rem;
  font-weight: 600;
  cursor: pointer;
  transition: background 0.15s ease, border-color 0.15s ease;
}

/* The light-palette override for this control is deleted along with the warm
   slab it was written for — the control now sits on the dark page and uses the
   dark-page styling above. */

.sb-plate__expand:hover,
.sb-plate__expand:focus-visible {
  background: rgba(255, 255, 255, 0.14);
  border-color: rgba(214, 210, 202, 0.60);
}

.sb-plate__expand:focus-visible {
  outline: 3px solid #8FBCF2;
  outline-offset: 2px;
}


.sb-plate__expand svg {
  width: 16px;
  height: 16px;
  flex: none;
}

/* On phones the inline 16:9 plate is genuinely not readable. Say so, rather
   than pretending the reduced image is legible. */
.sb-plate__expand-label--wide { display: inline; }
.sb-plate__expand-label--narrow { display: none; }

.sb-plate__caption {
  max-width: 76ch;
  margin: 24px 0 0;
  font-size: 0.95rem;
  line-height: 1.65;
  color: #D6D2CA;
}

.sb-plate__caption a { color: #8FBCF2; }

.sb-plate__longdesc {
  max-width: 76ch;
  margin: 16px 0 0;
  font-size: 0.9rem;
  line-height: 1.7;
  color: #C4BFB6;
}

.sb-plate__longdesc summary {
  cursor: pointer;
  font-weight: 600;
  color: #8FBCF2;
  min-height: 44px;
  display: flex;
  align-items: center;
}

.sb-plate__longdesc summary:focus-visible {
  outline: 3px solid #8FBCF2;
  outline-offset: 2px;
}

/* The light-palette variant that used to live here — .sb-band .sb-plate__label
   #6B6459, caption #3A342C, links #1B4D8F — is DELETED, not commented out.
   It was correct only while .sb-band painted a warm slab behind the text. The
   slab is gone; that text now sits on the dark page, where those values are
   near-invisible against #0D0D0D. Do not reintroduce them without also
   reintroducing a light background behind the text they colour.
   Dark-page contrast (the defaults above, on #0D0D0D): label 7.9:1,
   caption 11.2:1, link 5.6:1 — all clear of WCAG AA. */

.sb-plate__longdesc p { margin: 8px 0 0; }

/* ── The lightbox (expanded state) ────────────────────────────────────── */

.sb-lightbox {
  position: fixed;
  inset: 0;
  z-index: 9999;
  display: none;
  background: rgba(14, 13, 11, 0.94);
}

.sb-lightbox[open],
.sb-lightbox.is-open {
  display: block;
}

/* Chrome the fit view has to survive: 80px top + 24px bottom of scroll padding
   plus the figure's own 8px each side. Declared once so the image's height
   ceiling and the padding can never drift apart. */
.sb-lightbox {
  --sb-lb-chrome: 120px;
  --sb-lb-vh: 100vh;
}

/* dvh tracks the viewport a mobile browser is ACTUALLY showing; vh reports the
   large viewport and would let the image exceed the visible area behind the
   toolbars. Token override so an unsupported unit can't invalidate the calc. */
@supports (height: 1dvh) {
  .sb-lightbox { --sb-lb-vh: 100dvh; }
}

.sb-lightbox__scroll {
  position: absolute;
  inset: 0;
  overflow: auto;
  -webkit-overflow-scrolling: touch;
  /* NOT align-items/justify-content: center. Centring a flex item that is
     larger than its container pushes the overflow off BOTH edges and the
     start edge becomes unreachable — scrollTop clamps at 0, so the top of the
     artwork is cut off with no way to scroll to it. Measured at 1920x800:
     image top at -42px, scrollTop = -9999 still resolved to 0.
     `margin: auto` on the child centres it when there is free space and
     collapses to 0 when there is not, which keeps every edge reachable. */
  display: flex;
  padding: 80px 16px 24px;
}

.sb-lightbox__figure {
  margin: auto;
  /* Preserve the storyboards' warm paper background rather than floating the
     artwork on black — the paper is part of the art direction. */
  background: #F7F6F2;
  border-radius: 8px;
  padding: 8px;
  max-width: 92vw;
}

.sb-lightbox__img {
  display: block;
  /* Fit mode has to actually fit. Width alone was capped, so on any viewport
     shorter than roughly 1060px the 941px-tall artwork overflowed and the
     "Fit to screen" label was a lie. Both axes are bounded now; width/height
     stay auto so the aspect ratio is preserved by the two maxima. */
  max-width: 100%;
  max-height: calc(var(--sb-lb-vh, 100vh) - var(--sb-lb-chrome, 120px));
  width: auto;
  height: auto;
  border-radius: 4px;
}

/* Full-resolution mode: image renders at natural size and the container
   scrolls in both directions. This is the mobile readability path. */
.sb-lightbox.is-zoomed .sb-lightbox__scroll {
  display: block;
}

.sb-lightbox.is-zoomed .sb-lightbox__figure {
  max-width: none;
  width: max-content;
  margin: 0 auto;
}

.sb-lightbox.is-zoomed .sb-lightbox__img {
  /* Full resolution means natural size — BOTH ceilings have to come off, or
     the height cap silently keeps this mode identical to fit mode. */
  max-width: none;
  max-height: none;
  width: auto;
  height: auto;
}

.sb-lightbox__bar {
  position: absolute;
  top: 0;
  left: 0;
  right: 0;
  display: flex;
  align-items: center;
  justify-content: flex-end;
  gap: 8px;
  padding: 16px;
  background: linear-gradient(rgba(14, 13, 11, 0.92), rgba(14, 13, 11, 0));
}

.sb-lightbox__title {
  margin-right: auto;
  color: #EDEAE4;
  font-size: 0.86rem;
  font-weight: 600;
  letter-spacing: 0.02em;
  padding-left: 8px;
}

.sb-lightbox__btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 8px;
  min-width: 44px;
  min-height: 44px;
  padding: 0 12px;
  border: 1px solid rgba(237, 234, 228, 0.28);
  border-radius: 8px;
  background: rgba(255, 255, 255, 0.06);
  color: #EDEAE4;
  font: inherit;
  font-size: 0.82rem;
  cursor: pointer;
  text-decoration: none;
  transition: background 0.15s ease, border-color 0.15s ease;
}

.sb-lightbox__btn:hover,
.sb-lightbox__btn:focus-visible {
  background: rgba(255, 255, 255, 0.14);
  border-color: rgba(237, 234, 228, 0.60);
}

.sb-lightbox__btn:focus-visible {
  outline: 3px solid #EDEAE4;
  outline-offset: 2px;
}

.sb-lightbox__btn svg {
  width: 18px;
  height: 18px;
  flex: none;
}

/* Lock whichever element actually scrolls. On these pages <body> already
   computes to overflow:hidden and <html> is the scroller, so locking body
   alone did nothing. Lock both and let the one that scrolls take effect. */
html.sb-lightbox-open,
body.sb-lightbox-open {
  overflow: hidden;
}

/* ── Responsive ───────────────────────────────────────────────────────── */

@media (max-width: 900px) {
  /* Match the host pages' own narrow-viewport gutter so the figure keeps
     tracking the prose left edge rather than inventing its own. */
  .sb-band { padding: 32px 20px; }
  .sb-interlude { padding: 8px 20px; }
  .sb-plate__caption { font-size: 0.92rem; }
}

@media (max-width: 620px) {
  .sb-plate__expand-label--wide { display: none; }
  .sb-plate__expand-label--narrow { display: inline; }
  .sb-lightbox__scroll { padding: 72px 8px 16px; }
  .sb-lightbox__figure { max-width: 100vw; padding: 4px; }
  .sb-lightbox__title { display: none; }
  /* Chrome shrinks here (72 + 16 padding, 4 each side on the figure), so the
     image's height ceiling has to shrink with it or fit mode leaves dead space
     it could have used. Kept in lockstep with the padding above. */
  .sb-lightbox { --sb-lb-chrome: 96px; }
}

/* ── Motion ───────────────────────────────────────────────────────────── */

@media (prefers-reduced-motion: reduce) {
  .sb-plate__expand,
  .sb-lightbox__btn {
    transition: none;
  }
}

/* ── No-JS ────────────────────────────────────────────────────────────────
   Without JavaScript the plate still shows the full image and the caption,
   and the expand control is replaced by a plain link to the asset. Basic
   access to the graphic never depends on script. */
.sb-plate__noscript-link {
  display: inline-flex;
  align-items: center;
  min-height: 44px;
  margin-top: 8px;
  color: #8FBCF2;
  font-size: 0.9rem;
  font-weight: 600;
}

/* A `display` declaration outranks the [hidden] UA rule, so setting the
   attribute from script would NOT have hidden this link. Both the fallback
   link and the pre-script expand button need the explicit override. */
.sb-plate__noscript-link[hidden],
.sb-plate__expand[hidden] {
  display: none !important;
}
