/* ==========================================================================
   Scroll World v4 — Chunk 1 (skeleton, copy, engine, functions, SEO)
   Dark-first brand. Tokens + component styles are adapted from
   ../site/index.html (production source of truth) for the one-gesture chapter
   layout. The rendered world is the existing desktop/portrait evolution film;
   the former SVG scene remains hidden as prototype source material. Production
   text and conversion controls stay unchanged.
   ========================================================================== */

@font-face {
  font-family: "Newsreader";
  font-style: normal;
  font-weight: 400 600;
  font-display: swap;
  src: url("/assets/fonts/newsreader-latin-62981321d9.woff2") format("woff2");
}

/* Final phone overrides: kept last so the profile-era layout rules above
   cannot turn the compact pager back into a squeezed desktop navigation. */
@media (max-width: 700px) {
  #sceneRead {
    background: linear-gradient(180deg, rgba(5,7,8,.50) 0%, rgba(5,7,8,.40) 58%, rgba(5,7,8,.46) 100%);
  }
  .panel-inner {
    padding-left: max(20px, env(safe-area-inset-left, 0px));
    padding-right: max(20px, env(safe-area-inset-right, 0px));
    padding-bottom: calc(88px + env(safe-area-inset-bottom, 0px));
    scroll-padding-bottom: calc(88px + env(safe-area-inset-bottom, 0px));
  }
  .standfirst,
  .lede { font-size: 14px; line-height: 1.48; }
  .btn { font-size: 11px; line-height: 1.25; letter-spacing: .055em; }
  .txt,
  .mini-link,
  .price-note { font-size: 12px; line-height: 1.45; }
  #proof .case-card,
  #services .svc {
    background: rgba(9,13,15,.96) !important;
    backdrop-filter: none !important;
    -webkit-backdrop-filter: none !important;
  }
  #proof .roi-strip strong { font-size: 19px; }
  #proof .roi-strip span { font-size: 11px; line-height: 1.35; }
  .case-summary { font-size: 13px; line-height: 1.52; }
  #services .svc-name { font-size: 14px; line-height: 1.25; }
  #services .svc-meta { font-size: 11px; line-height: 1.4; }
  #services .svc-body { font-size: 12px; line-height: 1.52; }
  .hint {
    top: max(14px, env(safe-area-inset-top, 0px));
    left: max(20px, env(safe-area-inset-left, 0px));
    right: 76px;
    width: auto;
    max-width: 30ch;
    transform: none;
    text-align: left;
  }
  .hub {
    bottom: max(8px, env(safe-area-inset-bottom, 0px));
    width: auto;
    max-width: calc(100vw - 16px);
    padding: 5px 9px;
    gap: 6px;
  }
  .hub .row { gap: 2px; }
  .hub nav { flex: 0 0 auto; overflow: visible; gap: 0; padding: 0; }
  .hub nav button {
    position: relative;
    width: 32px;
    min-width: 32px;
    min-height: 40px;
    padding: 0;
    overflow: hidden;
    border: 0;
    border-radius: 999px;
    color: transparent;
    font-size: 0;
  }
  .hub nav button::before {
    content: "";
    position: absolute;
    left: 50%;
    top: 50%;
    width: 7px;
    height: 7px;
    border-radius: 999px;
    background: var(--muted);
    transform: translate(-50%, -50%);
    transition: width .2s var(--ease), background .2s var(--ease);
  }
  .hub nav button.on { border: 0; background: transparent; color: transparent; }
  .hub nav button.on::before { width: 22px; background: var(--oxblood-light); }
  .hub .arrow-btn { width: 40px; height: 40px; }
  .overlay-open .hub,
  .overlay-open .chat-widget-container {
    visibility: hidden;
    opacity: 0;
    pointer-events: none;
  }
}

@media (max-width: 380px) {
  #proof .roi-strip { grid-template-columns: 1fr; }
  #proof .roi-strip div {
    display: grid;
    grid-template-columns: 84px minmax(0, 1fr);
    align-items: center;
    gap: 10px;
    padding: 10px 12px;
  }
  #proof .roi-strip span { margin-top: 0; }
}

@media (max-width: 360px) {
  h1 { font-size: 27px; }
  h2 { font-size: 24px; }
  .panel-inner {
    padding-left: max(16px, env(safe-area-inset-left, 0px));
    padding-right: max(16px, env(safe-area-inset-right, 0px));
  }
  #services .svc { grid-template-columns: 1fr; gap: 4px; }
  #services .svc-meta { text-align: left; }
  .hub nav button { width: 28px; min-width: 28px; }
}

/* ---------- Mobile usability pass (2026-08) ----------
   The cinematic chapters remain, while the phone controls become a compact,
   safe-area-aware pager with large tap targets. */
@media (max-width: 700px) {
  .chat-drawer {
    background: rgba(9, 13, 15, 0.98);
    backdrop-filter: none;
    -webkit-backdrop-filter: none;
  }

  .hint {
    top: max(14px, env(safe-area-inset-top, 0px));
    left: max(20px, env(safe-area-inset-left, 0px));
    right: 76px;
    width: auto;
    max-width: 30ch;
    transform: none;
    text-align: left;
    line-height: 1.45;
    letter-spacing: 0.07em;
  }

  .panel-inner {
    padding-left: max(20px, env(safe-area-inset-left, 0px));
    padding-right: max(20px, env(safe-area-inset-right, 0px));
    padding-bottom: calc(88px + env(safe-area-inset-bottom, 0px));
    scroll-padding-bottom: calc(88px + env(safe-area-inset-bottom, 0px));
  }

  .hub {
    bottom: max(8px, env(safe-area-inset-bottom, 0px));
    width: auto;
    max-width: calc(100vw - 16px);
    padding: 5px 9px;
    gap: 6px;
  }
  .hub .row { gap: 2px; }
  .hub nav {
    flex: 0 0 auto;
    overflow: visible;
    gap: 0;
    padding: 0;
  }
  .hub nav button {
    position: relative;
    width: 32px;
    min-width: 32px;
    min-height: 40px;
    padding: 0;
    overflow: hidden;
    border: 0;
    border-radius: 999px;
    color: transparent;
    font-size: 0;
  }
  .hub nav button::before {
    content: "";
    position: absolute;
    left: 50%;
    top: 50%;
    width: 7px;
    height: 7px;
    border-radius: 999px;
    background: var(--muted);
    transform: translate(-50%, -50%);
    transition: width 0.2s var(--ease), background 0.2s var(--ease);
  }
  .hub nav button.on {
    border: 0;
    background: transparent;
    color: transparent;
  }
  .hub nav button.on::before {
    width: 22px;
    background: var(--oxblood-light);
  }
  .hub .arrow-btn { width: 40px; height: 40px; }

  .overlay-open .hub,
  .overlay-open .chat-widget-container {
    visibility: hidden;
    opacity: 0;
    pointer-events: none;
  }
}

@media (max-width: 360px) {
  h1 { font-size: 27px; }
  h2 { font-size: 24px; }
  .panel-inner {
    padding-left: max(16px, env(safe-area-inset-left, 0px));
    padding-right: max(16px, env(safe-area-inset-right, 0px));
  }
  #proof .roi-strip { gap: 4px; }
  #proof .roi-strip div { padding-inline: 6px; }
  #services .svc { grid-template-columns: 1fr; gap: 4px; }
  #services .svc-meta { text-align: left; }
  .hub nav button { width: 28px; min-width: 28px; }
}

/* ---------- Mobile reading mode ---------- */
@media (max-width: 700px) {
  #sceneRead {
    background: linear-gradient(180deg,
      rgba(5, 7, 8, 0.50) 0%,
      rgba(5, 7, 8, 0.40) 58%,
      rgba(5, 7, 8, 0.46) 100%);
  }
  .panel-inner {
    --copy-drop: 0 2px 3px rgba(0, 0, 0, 1), 0 7px 22px rgba(0, 0, 0, 0.92);
  }
  .standfirst,
  .lede { font-size: 14px; line-height: 1.48; }
  .label,
  .kicker,
  #intro .hero-top .kicker,
  .hero-entry-tag,
  .case-label,
  .rag {
    font-size: 10.5px;
    line-height: 1.45;
    letter-spacing: 0.09em;
  }
  .btn { font-size: 11px; line-height: 1.25; letter-spacing: 0.055em; }
  .txt,
  .mini-link,
  .price-note { font-size: 12px; line-height: 1.45; }
  #proof .case-card,
  #services .svc {
    background: rgba(9, 13, 15, 0.96) !important;
    backdrop-filter: none !important;
    -webkit-backdrop-filter: none !important;
  }
  #proof .roi-strip strong { font-size: 19px; }
  #proof .roi-strip span {
    font-size: 11px;
    line-height: 1.35;
    letter-spacing: 0.025em;
  }
  .case-summary { font-size: 13px; line-height: 1.52; }
  #services .svc-name { font-size: 14px; line-height: 1.25; }
  #services .svc-meta {
    font-size: 11px;
    line-height: 1.4;
    letter-spacing: 0.025em;
  }
  #services .svc-body { font-size: 12px; line-height: 1.52; }
  .contact-side-panel > p[style] {
    color: var(--shell-soft) !important;
    font-size: 14px !important;
    line-height: 1.5 !important;
  }
  .intake label { font-size: 11.5px; }
  .privacy-note,
  .contact-side-panel .site-foot,
  .chat-drawer-footer { font-size: 11px; line-height: 1.55; }
}

@media (max-width: 360px) {
  .standfirst,
  .lede { font-size: 14px; }
  .btn { font-size: 10.5px; }
}

@media (max-width: 380px) {
  #proof .roi-strip { grid-template-columns: 1fr; }
  #proof .roi-strip div {
    display: grid;
    grid-template-columns: 84px minmax(0, 1fr);
    align-items: center;
    gap: 10px;
    padding: 10px 12px;
  }
  #proof .roi-strip span { margin-top: 0; }
}
@font-face {
  font-family: "Zen Old Mincho";
  font-style: normal;
  font-weight: 600;
  font-display: swap;
  src: url("/assets/fonts/zen-old-mincho-latin-600.d1d3978d9b.woff2") format("woff2");
}

:root {
  --slate: #0B0D0E;
  --slate-surface: #111619;
  --slate-raised: #171D20;
  --slate-line: #2D363B;
  --line-strong: #647078;
  --shell: #F6F4EF;
  --shell-soft: #D7D1C6;
  --oxblood: #9A3340;
  --oxblood-light: #C8606C;
  --glacier: #6FA8C6;
  --blue-deep: #315F78;
  --muted: #B9B4AA;
  --muted-dim: #7D8588;
  --serif: "Newsreader", Georgia, serif;
  --twist: "Zen Old Mincho", "Yu Mincho", "Hiragino Mincho ProN", Georgia, serif;
  --mono: "Lucida Console", Consolas, "Andale Mono", monospace;
  --ease: cubic-bezier(0.22, 1, 0.36, 1);
  --bg-gradient-start: #0D1013;
  --bg-gradient-end: #08090A;
  --radial-glow-1: rgba(154,51,64,0.10);
  --radial-glow-2: rgba(111,168,198,0.07);
  --panel-bg-gradient: linear-gradient(165deg, rgba(13,16,19,0.55) 0%, rgba(11,13,14,0.8) 60%, rgba(9,11,12,0.9) 100%);
  --btn-bg: rgba(246,244,239,0.04);
  /* Copy-scrim base colour (see "Copy scrim" below) — same rgba(9,11,12,*)
     the WCAG contrast pass already verified clears 4.5:1 for shell/muted/
     muted-dim text against the graded film, kept as raw components so it can
     be used at varying alpha in a gradient rather than a flat fill. */
  --scrim-rgb: 9, 11, 12;
}

* { box-sizing: border-box; margin: 0; padding: 0; }
html { -webkit-text-size-adjust: 100%; }
/* overflow-x: hidden paired with any overflow-y other than 'clip' forces
   overflow-y's COMPUTED value from visible to auto regardless of what's
   specified (the UA cross-axis promotion rule) — turning html/body into
   their own nested scroll containers, which breaks position:sticky's
   containing-block resolution for every .beat-inner descendant (content
   stops sticking and just scrolls away at normal speed instead of holding;
   confirmed via computed-style diagnostics). 'clip' is spec-exempt from the
   promotion, so it blocks horizontal overflow without the side effect. */
html, body { min-height: 100%; overflow-x: clip; overflow-y: visible; }
body {
  background:
    radial-gradient(circle at 50% 6%, var(--radial-glow-1), transparent 38%),
    radial-gradient(circle at 16% 88%, var(--radial-glow-2), transparent 30%),
    linear-gradient(135deg, var(--bg-gradient-start) 0%, var(--slate) 52%, var(--bg-gradient-end) 100%);
  background-color: var(--slate);
  color: var(--shell);
  font-family: var(--serif);
  -webkit-font-smoothing: antialiased;
  text-rendering: optimizeLegibility;
  position: relative;
}
/* F5 (1.4.1 Use of Color): prose/footer links carry a persistent non-color
   cue (underline) since color alone marks them against surrounding text.
   Control-styled links (.btn, .mini-link, .txt, .copt) are exempt — their
   button/chip/panel affordance already identifies them without relying on
   link color, and an underline there would read as a typo in the label. */
a { color: inherit; text-decoration: underline; text-underline-offset: 2px; text-decoration-thickness: 1px; }
.btn, .mini-link, .txt, .copt, .hub nav a { text-decoration: none; }
::selection { background: var(--oxblood); color: var(--shell); }

.mono { font-family: var(--mono); font-size: 12px; letter-spacing: 0.04em; }
.label { font-family: var(--mono); font-size: 11px; letter-spacing: 0.12em; text-transform: uppercase; color: var(--muted-dim); }
.twist { font-family: var(--twist); color: var(--oxblood-light); font-weight: 600; }

input, textarea, button, a { touch-action: manipulation; }

/* ---------- Scene (fixed, z:0) — Chunk 2: the evolving world ---------- */
#scene {
  position: fixed;
  inset: 0;
  z-index: 0;
  pointer-events: none;
  overflow: hidden;
  background:
    radial-gradient(ellipse 82% 72% at 74% 30%, rgba(111,168,198,0.06), transparent 60%),
    var(--slate);
}
/* The continuous camera: one transform, always moving, drives the whole journey.
   Transform-origin centred so push-ins and the window flythrough read as travel. */
.world-media,
.world-video,
.world-stills,
.world-still,
.world-still img {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
}
.world-media { z-index: 0; background: #080a0b; contain: strict; isolation: isolate; }
/* A stable film grade avoids transition flashes and full-screen filter
   recomposites while preserving the contrast needed behind the copy. */
.world-video,
.world-still img {
  display: block;
  object-fit: cover;
  object-position: center;
  filter: brightness(0.58) saturate(0.82) contrast(1.12);
  backface-visibility: hidden;
}
#scene.grade-settled .world-video,
#scene.grade-settled .world-still img {
  filter: brightness(0.58) saturate(0.82) contrast(1.12);
}
.world-video { background: #080a0b; opacity: 1; transition: opacity 160ms ease; }
.world-stills { display: block; opacity: 0; transition: opacity 160ms ease; }
.world-still { opacity: 0; transition: opacity 180ms ease; }
.world-still.is-active { opacity: 1; }
#scene.stills-mode .world-video { display: none; }
#scene.stills-mode .world-stills,
#scene.video-pending .world-stills { opacity: 1; }
#scene.video-pending .world-video { opacity: 0; }

/* Frame out the Gemini/Veo watermark (owner-approved: Joel wants it covered,
   not left visible — see engine build notes). Sampled corner across all four
   still frames and multiple video timestamps (2s/10s/15s/25s): the sparkle
   mark sits at a fixed ~88-90% x, ~80-84% y of the source 1280x720 desktop
   film, consistently. Cropping it out via object-position/scale was tried
   first and rejected: at typical desktop aspect ratios (e.g. 1280x800) the
   16:9 source has almost no vertical cover slack, so pushing the mark off
   the right/bottom edge required either a shift/scale big enough to crop
   the printer (state-2), the hallway framing (state-3), or the glass door
   (state-4) — genuinely wrecking the composition per the brief's own
   fallback clause. A feathered corner shadow instead: reads as a deepened
   vignette (the design already vignettes via #sceneRead's own radial fade),
   not a hard box. Desktop-only — the separate portrait mobile film was
   independently center-cropped upstream and the mark isn't in that frame at
   all (checked stills + video at 375x812 / 720x1280; see the fix report).
   The stops below are deliberately steep, not a gentle vignette curve: a
   canvas pixel-diff check (draw the graded video frame + this gradient to an
   offscreen canvas, sample the mark's actual screen position vs. its
   surroundings) showed the mark is a very high-contrast element — a first
   pass at ~0.6 alpha over its position visibly ghosted through (background
   delta ~176 luminance units) even though the gradient LOOKED like a normal
   soft corner shadow. Alpha needs to be ~0.90+ specifically at the mark's
   distance from the corner (~33% of the gradient's ending radius) to crush
   that delta below a perceptible threshold — verified by re-running the same
   pixel-diff after tuning these stops (see the fix report for the numbers). */
@media (min-width: 701px) {
  .world-media::after {
    content: "";
    position: absolute;
    right: 0;
    bottom: 0;
    width: 32%;
    height: 30%;
    background: radial-gradient(120% 120% at 100% 100%,
      rgba(5, 6, 7, 0.98) 0%,
      rgba(5, 6, 7, 0.97) 18%,
      rgba(5, 6, 7, 0.94) 40%,
      rgba(5, 6, 7, 0.6) 65%,
      rgba(5, 6, 7, 0.18) 88%,
      rgba(5, 6, 7, 0) 100%);
    pointer-events: none;
    z-index: 1;
  }
}

@media (max-width: 600px) {
  .world-video,
  .world-still img { object-position: center center; }
}

/* Readability wash — darkens the copy side without an opaque card, plus a soft
   cinematic vignette. Kept above the world (z:5), below the copy deck (z:1 of .deck
   sits in normal flow above #scene). */
#sceneRead {
  position: absolute; inset: 0; z-index: 5; pointer-events: none;
  background:
    linear-gradient(180deg, rgba(7,9,10,0.46) 0%, rgba(7,9,10,0.18) 40%, rgba(7,9,10,0) 70%),
    linear-gradient(102deg, rgba(9,11,12,0.66) 0%, rgba(9,11,12,0.34) 38%, rgba(9,11,12,0) 66%),
    linear-gradient(0deg, rgba(9,11,12,0.5) 0%, rgba(9,11,12,0) 28%),
    radial-gradient(128% 122% at 50% 46%, transparent 54%, rgba(6,8,9,0.6) 100%);
}
/* Mobile readability: the diagonal desktop wash above assumes copy sits to
   the left of a right-hand window, but the portrait recomposition centres
   both copy and window on the same column — the desktop wash leaves the
   window's bright glass under-dimmed exactly where copy scrolls over it
   (light body text reads at low contrast against the light window panes
   mid-transition, e.g. p~0.19 and p~0.495-0.56). A straight top-down wash,
   strongest where the recomposed window sits (roughly the upper half of
   the frame) and tapering out before the desk, fixes contrast without an
   opaque card. */
@media (max-width: 700px) {
  #sceneRead {
    /* Holds meaningful strength across the window's whole vertical span
       (~20%-55% of the viewport) instead of tapering out by mid-frame, so
       leaving fine-print (e.g. START's price-note at p~0.19) stays legible
       the entire time it overlaps the glass, not just near the very top. */
    background:
      linear-gradient(180deg, rgba(9,11,12,0.74) 0%, rgba(9,11,12,0.52) 25%, rgba(9,11,12,0.42) 45%, rgba(9,11,12,0.28) 62%, rgba(9,11,12,0) 80%),
      radial-gradient(120% 90% at 50% 18%, transparent 35%, rgba(6,8,9,0.4) 100%);
  }
}

/* CONTACT scrim: opacity driven by the engine in stage 4 so the calendar reads. */
#sceneScrim {
  position: absolute; inset: 0; z-index: 6; pointer-events: none;
  background: #070909; opacity: 0;
}

/* ---------- Scroll hint ---------- */
.hint {
  position: fixed; top: 16px; left: 50%; transform: translateX(-50%); z-index: 40;
  font-family: var(--mono); font-size: 11px; letter-spacing: 0.12em; text-transform: uppercase;
  color: var(--muted-dim); transition: opacity 0.5s ease; pointer-events: none;
}
.hint.gone { opacity: 0; }
/* "swipe up" only means something on a touch input — a pointer-fine visitor
   (mouse/trackpad) never swipes. Hidden on pointer:fine regardless of
   viewport width, shown on pointer:coarse (touch) regardless of width; the
   node itself is untouched/unremoved (copy-parity gate counts it). */
@media (pointer: fine) {
  .hint { display: none; }
}

/* ---------- Beats ----------
   Two layouts share this markup:
   - JS OFF (default rules below): a plain, readable, vertically-scrolling stack
     — every section in normal flow, nothing hidden. This is the graceful
     fallback the parity gate and no-JS visitors see.
   - JS ON (html.js rules): a fixed full-viewport app. All four beats overlap in
     a fixed layer; engine.js shows exactly one at a time (per-beat .panel-inner
     opacity) and tweens the scene between beats on gesture. No page scroll. */
.deck { position: relative; z-index: 1; }
.beat { position: relative; pointer-events: none; }
.beat-inner { min-height: 100vh; display: flex; align-items: center; padding: 6vh 0; }

/* JS-upgraded fixed-viewport beat app. 100dvh keeps the layout stable when a
   mobile browser's URL bar shows/hides (no jump). */
html.js, html.js body { height: 100%; overflow: hidden; }
html.js .deck { position: fixed; inset: 0; z-index: 1; }
html.js .beat { position: absolute; inset: 0; }
html.js .beat-inner {
  position: absolute; inset: 0;
  height: 100dvh; min-height: 0;
  padding: 0; overflow: hidden;
}

/* ---------- Legacy decorative background (verbatim from production) ----------
   Carried over as inert, non-interactive flourish so every production text
   node survives the parity gate. Not part of the new scene system — Chunk 2
   may delete or fold these into the real world art once it lands. */
/* Chunk 3: these decorative watermarks/ledger mockups sat in the .beat's own
   stacking context (above the fixed #scene, since .deck is z-index:1) and
   visually competed with the real scene world built in Chunk 2. They stay in
   the DOM (their text nodes are still counted by the copy-parity gate) but
   render nothing — Chunk 2's SVG world is now the sole background. */
.panel-bg { display: none; }
.panel-inner {
  position: relative;
  pointer-events: auto;
  width: min(1200px, 100%);
  margin: 0 auto;
  padding: clamp(58px, 9vh, 96px) clamp(24px, 6vw, 64px) clamp(132px, 17vh, 168px);
  max-height: 100%;
  overflow-y: auto;
  /* Tall beats (mobile PROOF/OFFER/CONTACT, short desktop windows) scroll their
     own content here; contain keeps that scroll from chaining anywhere and the
     engine only pages to the next beat once this reaches its top/bottom edge. */
  overscroll-behavior: contain;
  -webkit-overflow-scrolling: touch;
  scrollbar-width: none; -ms-overflow-style: none;
  overflow-wrap: break-word;
  will-change: auto;
}
html.js .panel-inner.is-transitioning { will-change: opacity, transform; }
/* engine.js moves focus to the incoming chapter's heading after every beat
   change. Suppressing the ring outright also suppressed it for keyboard
   users, who have no other cue that focus moved. :not(:focus-visible) keeps
   the programmatic focus silent while a real keyboard focus still rings. */
.beat h1:focus:not(:focus-visible),
.beat h2:focus:not(:focus-visible) { outline: none; }
.panel-inner::-webkit-scrollbar { width: 0; height: 0; display: none; }
/* JS-off fallback: never clip; let every section flow and be read in full. */
html:not(.js) .panel-inner { max-height: none; overflow-y: visible; }

/* Copy arrives in the order it is read. The panel still owns the chapter-level
   crossfade; these child transitions make the words feel placed into the room
   instead of appearing as one flat page over it. */
html.js .panel-inner > .reveal-step {
  opacity: 0;
  transform: translate3d(0, 14px, 0) var(--spatial-transform, rotate(0deg));
  transition:
    opacity 460ms var(--ease),
    transform 620ms var(--ease);
  transition-delay: 0ms;
}
html.js .panel-inner.copy-entered > .reveal-step {
  opacity: 1;
  transform: var(--spatial-transform, translate3d(0, 0, 0));
  transition-delay: calc(var(--reveal-order, 0) * 95ms);
}
/* The first headline is the LCP element. It is already the settled opening
   state, so paint it immediately; later chapters retain their full stagger. */
html.js.first-hero-ready #intro .panel-inner > .reveal-step {
  opacity: 1;
  transform: var(--spatial-transform, translate3d(0, 0, 0));
  transition: none;
}

/* Was --glacier (#6FA8C6). At 11px sitting directly on the film — no card,
   no opaque surface — glacier measures ~1.1-1.5:1 against a bright frame,
   i.e. effectively invisible. --shell is the page's own body-text colour and
   the only value the scrim/wash math below is solved for. Same reason for
   .case-label and .svc-meta further down. */
.kicker { margin-bottom: clamp(12px, 3vh, 22px); color: var(--shell); }
.rag { display: inline-flex; align-items: center; justify-content: center; height: 24px; padding: 0 11px; border: 1px solid var(--line-strong); font-family: var(--mono); font-size: 10.5px; letter-spacing: 0.06em; color: var(--shell); }
.rag.proof { border-color: var(--glacier); color: var(--glacier); }
/* (B) Title scale: this is the film's own type now, not a website laid over
   the video — bumped ~1.3x over the previous ceiling (was clamp(32,5vw,68)).
   Comfortably >=24px at every viewport, so it clears WCAG's "large text"
   3:1 floor on size alone, no bold dependency. */
h1 { font-weight: 500; font-size: clamp(42px, 6.5vw, 88px); line-height: 1.0; letter-spacing: -0.02em; max-width: 32ch; }
h1 .twist { font-style: normal; }
.hero-outcome { max-width: 68ch; }
.hero-outcome b { color: var(--shell); font-weight: 600; }
h2 { font-weight: 500; font-size: clamp(36px, 5.2vw, 68px); line-height: 1.0; letter-spacing: -0.015em; max-width: 35ch; margin-bottom: 14px; }
/* (B) The one supporting line left on film per beat (.standfirst on START,
   .lede on PROOF/OFFER/CONTACT) — bumped to title scale alongside the
   headline (floor now 20px, was 15.5-16px) with a touch more weight for
   "confident" presence. This is no longer relying on the copy-scrim's
   position along the line the way the old 61-92%-across-the-box measurements
   below assumed (that comment is retained on --shell's own contrast note):
   the SETTLED grade in the #scene.grade-settled rule above clears 4.5:1 for
   --shell against the worst-case film pixel with ZERO scrim contribution
   (4.72:1 — see that rule's own contrast-math comment), so it holds at any
   horizontal position on any line length, long lines included. --shell
   (L=0.905, needs only >=0.418 scrim alpha even in the old scrim-dependent
   model) was already the right colour choice for this; kept unchanged. */
.standfirst { margin-top: clamp(16px, 2.6vh, 26px); max-width: 70ch; font-size: clamp(20px, 1.7vw, 24px); font-weight: 500; line-height: 1.45; color: var(--shell); }
.lede { max-width: 75ch; font-size: clamp(20px, 1.6vw, 23px); font-weight: 500; line-height: 1.5; color: var(--shell); }
/* Bold inline emphasis used to sit a notch brighter (shell-soft) than the
   surrounding muted body text; now that the body text itself is --shell,
   shell-soft would read as a step DOWN in contrast for the "emphasized"
   word. Matched to --shell; font-weight alone now carries the emphasis. */
.lede b, .standfirst b { color: var(--shell); font-weight: 600; }
.panel-inner > h1,
.panel-inner > h2,
.panel-inner > .lede,
.panel-inner > .standfirst,
.panel-inner > .kicker,
.hero-top {
  /* Soft shadow built from the page background colour (not flat black) —
     ported from the scroll-world reference's .sw-copy__title/__body
     treatment. This is a secondary edge aid; the copy scrim below (D) does
     the actual contrast guarantee. */
  text-shadow: 0 2px 20px rgba(var(--scrim-rgb), 0.75), 0 1px 3px rgba(var(--scrim-rgb), 0.9);
}
.panel-inner > .lede,
.panel-inner > .standfirst {
  text-shadow: 0 1px 12px rgba(var(--scrim-rgb), 0.85), 0 1px 2px rgba(var(--scrim-rgb), 0.95);
}

/* ---------- (D) Copy scrim: text lit into the room, no card edges ----------
   Ported from github.com/oso95/scroll-world (skills/scroll-world/references/
   scrub-engine.js, .sw-copylayer::before): a full-height gradient of the
   page background colour, opaque at the text edge and fading toward the
   scene, so prose reads as native to the frame instead of floating on a
   card. Replaces the earlier per-block radial "surface" cards — those left
   visible rounded-rectangle edges around every heading/paragraph.
   REWORKED (owner feedback "big black boxes", 2026-07): the previous version
   held ~0.93-0.96 alpha flat out to 92% of --copy-scrim-w before feathering
   in the last 8% — on the default min(90vw,1180px) box that's a near-opaque
   rectangle covering up to ~82% of the viewport width with a visible cliff
   at the end, exactly what read as a giant black box. Two changes fix it
   together instead of one covering for the other:
   1) Text brightened first (.price-note, .roi-strip span, .lede,
      .standfirst, .site-foot -> --shell; see their rules) so far less
      alpha is needed at the text itself.
   2) The gradient is one continuous 4-stop ramp with no flat plateau and no
      hard edge: steep near the reading edge, then leveling to a floor
      rather than the reference's fade-to-zero. The floor is the deliberate
      deviation from the brief's illustrative "0 by ~80%" and from the
      reference (github.com/oso95/scroll-world, .sw-copylayer::before): the
      reference's body copy is capped at 40ch (~460px) inside a 780px box,
      comfortably inside a to-zero fade. This site's copy is not that short
      — .standfirst runs 70ch, .lede 75ch, and price-note/site-foot aren't
      column-capped at all. Browser-measured at 1440x900 (real rendered
      text, not estimated), the widest lines reach these %s of their own
      --copy-scrim-w box: .standfirst 61%, #proof .lede 92%, #services .lede
      86%, #contact .lede 72%, #services .price-note 90%, #intro
      .price-note 96%, .site-foot 97%. A fade that actually hits 0 before
      ~97% would leave those lines' tail ends sitting on raw film with no
      protection at all, regardless of text colour. So the ramp instead
      settles to a floor past the 55% stop and holds it (still strictly
      monotonic, still no plateau at the top, just a gentle final glide
      instead of a cliff to zero):
        0%   -> 0.95   (opaque right at the reading edge)
        30%  -> 0.8
        55%  -> 0.55
        100% -> 0.45   (floor — never fully transparent, but roughly half
                         the old solid alpha, and continuously arrived at)
      Contrast was solved against the worst sampled bright frame pixel (raw
      rgb(254,253,245), post .world-video/.world-still filter
      brightness(0.72) saturate(0.82) contrast(1.06) -> rgb(186,185,180),
      L=0.484), using a binary-search solver run in-browser (not hand
      rounding) for the exact minimum alpha at 4.5:1:
        --shell (L=0.905) needs alpha >= 0.4177.
        --shell-soft (L=0.641) needs alpha >= 0.5412.
        --muted (L=0.459) needs alpha >= 0.6535 — this is why .lede/
          .standfirst/.site-foot moved off --muted/--muted-dim: at their
          measured 61-97% positions the ramp is at 0.45-0.75 alpha, well
          above shell's 0.4177 floor requirement everywhere measured (margin
          checked at each point in the fix notes), but nowhere near 0.6535.
   This trades some of the reference's "scene fully open by mid-box" airiness
   for correctness on this site's actual (longer) copy — flagged for Joel to
   eyeball at localhost:8123, since a floor that never goes fully transparent
   is a judgment call, not a hard requirement.
   --copy-scrim-w below is still sized per breakpoint (not the reference's
   fixed 780px) so the #proof/#services narrow-column override further down
   keeps working: below 1100px every beat is single-column with no competing
   right-side element, so the default wide value applies everywhere; only
   once the #proof/#services two-column grid activates (>=1100px) does their
   column need to stay narrow so the case-studies/discovery-card side keeps
   reading against open scene. Negative z-index keeps it behind panel-inner's
   text (panel-inner is its own stacking context via will-change) while
   staying above #scene, an entirely separate, earlier-painted stacking
   context — ordering there is untouched by z-index values used in here. */
.panel-inner {
  /* Was min(90vw,1180px). Browser-measured (701-1099px viewports, before the
     #proof/#services grid activates and while .lede/.standfirst still use
     their wide 70-75ch fallback max-width) that formula fell short of the
     panel's own content width by ~15-40px — real text wrapped closer to the
     panel's own edge than 90vw allowed, landing text fully outside the box
     (0 alpha, contrast ~1.8, an outright failure, not just a thin margin).
     100% resolves against panel-inner's own padding-box width (its
     containing block, since .panel-inner is position:relative and this
     pseudo is position:absolute) — i.e. exactly panel-inner's rendered
     width, which by construction always covers its own padding + content,
     at every breakpoint. At the wide end this is ~20px more than the old
     1180px cap (panel-inner tops out at 1200px) — negligible. */
  --copy-scrim-w: 100%;
}
/* The reading-edge ramp remains intentionally subtle: text shadows and the
   stable film grade carry readability without bringing back opaque panels. */
.panel-inner::before {
  content: "";
  position: absolute;
  inset: 0;
  z-index: -1;
  width: var(--copy-scrim-w);
  background: linear-gradient(90deg,
    rgba(var(--scrim-rgb), 0.35) 0%,
    rgba(var(--scrim-rgb), 0) 40%);
  pointer-events: none;
}
/* #proof/#services' two-column grid (min-width:1100px, defined further down)
   reserves the right side of the beat for case-studies/discovery-card —
   narrow the scrim to match that column's real width so the world reads
   through the gap instead of getting covered too. Was min(48vw,620px)
   (sized for #proof's .lede, ~504px from the padded left edge); browser-
   measured at the grid's own activation width (1100px, the tightest case),
   #services .price-note's column track runs to ~539px from panel-inner's
   left edge (now that it lives in grid-column 1 — see that rule further
   down — instead of spanning the full width under its own band) and 48vw
   is only 528px there, an 11px sliver of real text left fully outside the
   scrim box (0 alpha, fails contrast regardless of colour). Widened so the
   box clears the column at every width from 1100px up to where the cap
   takes over. */
@media (min-width: 1100px) {
  #proof .panel-inner,
  #services .panel-inner {
    --copy-scrim-w: min(54vw, 660px);
  }
}

.hero-top { display: flex; align-items: center; gap: 16px; margin-bottom: 16px; }
.hero-top .portrait { width: 78px; height: 78px; border-radius: 50%; flex: none; }
.actions { display: flex; flex-wrap: wrap; gap: 10px 14px; align-items: center; margin-top: clamp(18px, 3vh, 28px); }
.btn { font-family: var(--mono); font-size: 12px; font-weight: 400; letter-spacing: 0.08em; text-transform: uppercase; padding: 11px 18px; border-radius: 999px; border: 1px solid var(--slate-line); color: var(--shell); background: var(--slate-surface); cursor: pointer; transition: all 0.2s var(--ease); }
.btn:hover { border-color: var(--oxblood-light); }
.btn.primary { background: var(--oxblood); border-color: var(--oxblood-light); color: var(--shell); }
.btn.primary:hover { background: var(--oxblood-light); color: var(--slate); }
/* Was --muted: browser-measured (701px, START's "Prefer email?") at ~55% of
   the copy-scrim box where the ramp is only ~0.55 alpha — under muted's
   0.6535 threshold (3.38:1, fails). --shell only needs ~0.42, safe there. */
.txt { font-family: var(--mono); font-size: 12px; letter-spacing: 0.04em; color: var(--shell); text-shadow: 0 1px 10px rgba(var(--scrim-rgb), 0.9); }
.txt:hover { color: var(--shell); }
/* price-note carries the smallest prose on the page. It was #7D8588
   (muted-dim, luminance 0.229) — against the worst-case graded film pixel
   (raw rgb(254,253,245) -> after .world-video/.world-still's own
   brightness(0.72) saturate(0.82) contrast(1.06) filter -> rgb(186,185,180),
   L=0.484) that only reaches ~3.96:1 even sitting on a near-opaque 0.92-alpha
   card (see the #proof .roi-strip div shard below), well under 4.5:1 — this
   text is what actually forced the old near-solid scrim. Raised to --shell
   (#F6F4EF, L=0.906): against the ambient ramp below, shell clears 4.5:1 at
   scrim alpha >=0.42 (composite L<=0.162; verified 0.42α -> L=0.161 ->
   4.53:1), which the new ramp clears until ~59% across the scrim box — see
   the ramp comment above .panel-inner::before. No new hue: --shell is
   already the page's body-text colour, just applied here instead of
   muted-dim. */
.price-note { margin-top: 14px; font-family: var(--mono); font-size: 12px; color: var(--shell); text-shadow: 0 1px 8px rgba(var(--scrim-rgb), 0.95); }
.price-note b { color: var(--shell); font-weight: 400; }
/* (B) Title-sequence pass: price-note no longer floats bare on the film as
   its own reveal-step — it moved INTO the two genuine cards it was always
   thematically part of (see index.html): START's copy is now the last line
   inside .hero-entry-offer, OFFER's is the last item inside .services (the
   svc tile group). Both already carry their own opaque card surface, so the
   text-shadow above is redundant there but harmless; these two rules just
   add the divider/spacing that makes the note read as part of its card
   instead of a stray paragraph. */
.hero-entry-offer .price-note {
  margin-top: 8px;
  padding-top: 8px;
  border-top: 1px solid rgba(111, 168, 198, 0.16);
  font-size: 11px;
}
.services .price-note {
  margin-top: 0;
  padding: 10px clamp(16px, 2vw, 24px);
  border-top: 1px solid var(--slate-line);
  background: rgba(11, 14, 16, 0.6);
}
@media (min-width: 1100px) {
  /* .services becomes a 3-column grid at this breakpoint (see the >=1100px
     rule further down) — span all three so the note reads as a footer under
     the tile row instead of squeezing into the first column. */
  .services .price-note { grid-column: 1 / -1; }
}

.portrait { position: relative; overflow: hidden; border: 1px solid var(--slate-line); background: var(--slate-surface); }
.portrait img { width: 100%; height: 100%; object-fit: cover; object-position: 50% 18%; filter: grayscale(0.5) contrast(1.05) brightness(0.95); display: block; }
.portrait::after { content: ""; position: absolute; inset: 0; background: linear-gradient(180deg, rgba(154,51,64,0.10), transparent 45%, rgba(11,13,14,0.35)); }

/* Hero entry offer */
.hero-entry-offer {
  margin-top: clamp(14px, 2.4vh, 22px);
  /* (D) near-solid so the tag/title read at 4.5:1+ against any frame; the
     glacier tint moves to the border-left accent instead of the fill. */
  background: rgba(9, 11, 12, 0.87);
  border: 1px solid rgba(111, 168, 198, 0.15);
  border-left: 3px solid var(--glacier);
  padding: 12px 16px;
  border-radius: 4px;
  max-width: 70ch;
  display: flex; flex-direction: column; gap: 4px;
  cursor: pointer;
  transition: all 0.25s var(--ease);
  backdrop-filter: blur(7px);
  -webkit-backdrop-filter: blur(7px);
}
.hero-entry-offer:hover { background: rgba(14, 17, 19, 0.9); border-color: rgba(111, 168, 198, 0.35); transform: translateY(-1px); }
.hero-entry-tag { font-family: var(--mono); font-size: 10px; letter-spacing: 0.08em; text-transform: uppercase; color: var(--glacier); font-weight: 600; }
.hero-entry-title { font-size: 14.5px; line-height: 1.4; color: var(--shell-soft); }
.hero-entry-title b { color: var(--shell); font-weight: 500; }

/* ---------- Proof: case studies ---------- */
.case-studies { margin-top: clamp(16px, 2.8vh, 28px); display: grid; grid-template-columns: minmax(0, 1fr); gap: 20px; max-width: 880px; }
.case-card {
  background: rgba(18, 23, 27, 0.84); border: 1px solid rgba(111, 168, 198, 0.24);
  padding: clamp(16px, 2.2vw, 26px); border-radius: 4px;
  display: flex; flex-direction: column; gap: 12px; position: relative;
  transition: border-color 0.3s var(--ease), background-color 0.3s var(--ease);
  box-shadow: 0 18px 50px rgba(0,0,0,0.26);
  backdrop-filter: blur(14px) saturate(0.88);
  -webkit-backdrop-filter: blur(14px) saturate(0.88);
}
.case-card:hover { border-color: rgba(111, 168, 198, 0.4); background-color: rgba(24, 30, 34, 0.9); }
.case-card h3 { font-size: 17px; font-weight: 600; color: var(--shell); border-bottom: 1px solid var(--slate-line); padding-bottom: 8px; margin-bottom: 2px; }
.roi-strip { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: 1px; background: var(--slate-line); border: 1px solid var(--slate-line); margin: 2px 0; }
.roi-strip div { background: rgba(111, 168, 198, 0.045); padding: 12px; }
.roi-strip strong { display: block; font-size: clamp(22px, 2.4vw, 34px); line-height: 1; font-weight: 500; color: var(--shell); }
/* Was muted-dim: measured against its own shard's near-opaque background
   (#proof .roi-strip div, rgba(19,28,33,0.92) over the worst-case film
   pixel) that only cleared ~3.96:1 — under 4.5:1. --shell clears >13:1 on
   the same background, so it's safe with no card-alpha change needed. */
.roi-strip span { display: block; margin-top: 6px; font-family: var(--mono); font-size: 12px; line-height: 1.35; letter-spacing: 0.04em; color: var(--shell); text-transform: uppercase; }
.case-field { display: grid; grid-template-columns: 125px 1fr; gap: 10px; font-size: 15px; line-height: 1.45; }
/* Was --glacier — see the .kicker note above; too dim at this size on film. */
.case-label { font-family: var(--mono); font-size: 10.5px; letter-spacing: 0.05em; text-transform: uppercase; color: var(--shell); font-weight: 600; }
.case-val { color: var(--muted); }
.case-actions { display: flex; flex-wrap: wrap; gap: 8px 12px; align-items: center; margin-top: 4px; padding-top: 12px; border-top: 1px solid var(--slate-line); }
.mobile-proof-details { display: none; }
.case-actions .mini-link {
  font-family: var(--mono); font-size: 10.5px; letter-spacing: 0.07em; text-transform: uppercase; color: var(--glacier);
  border: 1px solid rgba(111, 168, 198, 0.22); background: rgba(111, 168, 198, 0.05); border-radius: 999px; padding: 7px 11px;
  cursor: pointer; transition: all 0.2s var(--ease);
}
.case-actions .mini-link:hover { border-color: var(--glacier); color: var(--shell); transform: translateY(-1px); }
.case-actions .mini-link.secondary { color: var(--muted); border-color: var(--slate-line); background: transparent; }
.case-actions .mini-link.secondary:hover { border-color: var(--slate-line); color: var(--shell-soft); }

/* ---------- Offer ---------- */
.discovery-card {
  margin-top: clamp(14px, 2.4vh, 22px);
  border: 1px solid rgba(154, 51, 64, 0.32);
  border-left: 3px solid var(--oxblood-light);
  background: linear-gradient(135deg, rgba(48, 26, 30, 0.82), rgba(14, 18, 21, 0.72));
  padding: clamp(16px, 2.2vw, 24px);
  display: grid; grid-template-columns: minmax(0, 1fr) auto; gap: 16px 24px; align-items: center;
  box-shadow: 0 18px 50px rgba(0,0,0,0.22);
  backdrop-filter: blur(14px) saturate(0.9);
  -webkit-backdrop-filter: blur(14px) saturate(0.9);
}
.discovery-card h3 { font-size: clamp(19px, 2vw, 26px); font-weight: 600; line-height: 1.15; margin-bottom: 8px; }
.discovery-card p { font-size: 14.5px; line-height: 1.55; color: var(--muted); max-width: 72ch; }
.discovery-points { display: flex; flex-wrap: wrap; gap: 7px; margin-top: 12px; }
.discovery-points span { font-family: var(--mono); font-size: 10px; color: var(--shell-soft); border: 1px solid var(--slate-line); background: rgba(11,13,14,0.35); padding: 6px 9px; border-radius: 999px; }

.services { margin-top: clamp(12px, 2.2vh, 22px); display: grid; gap: 1px; background: rgba(65,78,85,0.55); border: 1px solid var(--slate-line); box-shadow: 0 18px 50px rgba(0,0,0,0.2); }
.svc { background: rgba(11, 14, 16, 0.82); padding: clamp(13px, 1.7vw, 20px) clamp(16px, 2vw, 24px); display: grid; grid-template-columns: minmax(0, 1fr) auto; gap: 4px 24px; align-items: baseline; transition: background 0.3s var(--ease); backdrop-filter: blur(12px); -webkit-backdrop-filter: blur(12px); }
.svc:hover { background: rgba(24, 29, 32, 0.9); }
.svc-name { font-size: clamp(16px, 1.65vw, 21px); font-weight: 600; line-height: 1.2; }
/* Was --glacier — see the .kicker note above. This one carries the price, so
   it is the last thing on the page that can afford to be hard to read. */
.svc-meta { font-family: var(--mono); font-size: 11px; letter-spacing: 0.05em; color: var(--shell); text-align: right; white-space: nowrap; }
.svc-body { grid-column: 1 / -1; margin-top: 2px; font-size: 15px; line-height: 1.5; color: var(--muted); max-width: 72ch; }

/* OFFER-specific desktop tightening (spacing only, no copy/structure change):
   at 1440x900 the beat's content ran ~117px taller than the viewport, forcing
   an extra internal-scroll gesture to reach the price note. Scoped to #services
   so START/PROOF/CONTACT's shared .panel-inner/.price-note/etc rhythm is
   untouched. Bottom padding is trimmed but kept well above the ~74px the fixed
   hub needs (24px offset + ~50px capsule height) for shorter desktop windows. */
@media (min-width: 981px) {
  #services .panel-inner { padding-top: clamp(48px, 6.5vh, 72px); padding-bottom: clamp(96px, 12vh, 120px); }
  #services .discovery-card { margin-top: clamp(10px, 1.6vh, 16px); padding: clamp(13px, 1.7vw, 18px); }
  #services .services { margin-top: clamp(10px, 1.6vh, 14px); }
  #services .svc { padding: clamp(10px, 1.2vw, 14px) clamp(16px, 2vw, 24px); }
  /* price-note's own margin-top is no longer tightened here — it lives
     inside .services now (flush under the tiles; see .services .price-note
     earlier in this file), not as a direct sibling needing its own gap. */
}

/* Short laptop windows need a tighter reading rhythm than a roomy 900px-tall
   desktop. Keep the content above the fixed hub without shrinking it into a
   dense card or forcing a second gesture just to reveal the last line. */
@media (min-width: 701px) and (max-height: 820px) {
  .panel-inner { padding-top: clamp(44px, 6.5vh, 54px); padding-bottom: 96px; }
  /* Bumped a smaller amount than the base clamp (~1.1x vs ~1.3x) — still
     title scale, just respecting the cramped vertical budget this breakpoint
     exists for. Still clears the >=20px supporting-line floor. */
  h1 { font-size: clamp(46px, 4.6vw, 66px); max-width: 27ch; }
  h2 { font-size: clamp(36px, 3.6vw, 50px); }
  .kicker { margin-bottom: 12px; }
  .standfirst { margin-top: 16px; font-size: 20px; line-height: 1.4; }
  .lede { font-size: 20px; line-height: 1.42; }
  .case-studies { margin-top: 18px; }
  .case-card { padding: 18px; gap: 10px; }
  #services .panel-inner { padding-top: 40px; padding-bottom: 86px; }
  #services .discovery-card { margin-top: 10px; padding: 12px 18px; }
  #services .discovery-card h3 { font-size: 23px; }
  #services .services { margin-top: 10px; }
  #services .svc { padding: 8px 20px; }
  #services .svc-body { line-height: 1.4; }
}

/* Scene-specific desktop composition. Copy occupies the quiet architecture;
   focal objects and the central travel line remain visible. */
@media (min-width: 1100px) {
  #proof .panel-inner {
    display: grid;
    grid-template-columns: minmax(360px, 500px) minmax(72px, 1fr) minmax(420px, 468px);
    grid-template-rows: auto auto auto;
    align-content: center;
  }
  #proof .kicker { grid-column: 1; grid-row: 1; align-self: end; }
  #proof h2 { grid-column: 1; grid-row: 2; max-width: 13ch; }
  #proof .lede { grid-column: 1; grid-row: 3; max-width: 48ch; }
  #proof .case-studies {
    grid-column: 3;
    grid-row: 1 / 4;
    align-self: center;
    width: 100%;
    max-width: none;
    margin: 0;
  }
  #proof .case-card { padding: 18px; gap: 10px; }
  #proof .case-field { grid-template-columns: 105px 1fr; }
  #proof .roi-strip strong { font-size: 28px; }

  #services .panel-inner {
    display: grid;
    grid-template-columns: minmax(340px, 480px) minmax(52px, 1fr) minmax(390px, 460px);
    grid-template-rows: auto auto auto auto auto;
    align-content: center;
  }
  #services .kicker { grid-column: 1; grid-row: 1; }
  #services h2 { grid-column: 1; grid-row: 2; max-width: 10ch; }
  #services > .beat-inner .lede { grid-column: 1; grid-row: 3; max-width: 44ch; }
  #services .discovery-card {
    grid-column: 3;
    grid-row: 1 / 4;
    align-self: center;
    display: block;
    margin: 0;
  }
  #services .discovery-card .btn { margin-top: 14px; }
  #services .services {
    grid-column: 1 / 4;
    grid-row: 4;
    grid-template-columns: repeat(3, minmax(0, 1fr));
  }
  #services .svc { display: block; }
  #services .svc-meta { margin-top: 6px; text-align: left; }
  #services .svc-body { margin-top: 8px; }
  /* price-note is no longer a direct child of this outer grid (it moved
     inside .services — see the .services .price-note rule earlier in this
     file); its grid-column/row now resolve against .services' own nested
     3-column grid instead (handled by the >=1100px rule on that selector). */
}

@media (min-width: 981px) and (min-height: 821px) {
  #intro h1 { max-width: 19ch; }
  #intro .standfirst,
  #intro .hero-entry-offer { max-width: 58ch; }
}

/* ---------- Askable (click-to-ask) ---------- */
.askable { position: relative; cursor: pointer; transition: all 0.2s var(--ease); }
.svc.askable:hover { border-color: var(--oxblood-light) !important; background-color: var(--slate-surface) !important; transform: translateY(-2px); }
.askable::after {
  content: 'Ask Assistant \2197';
  position: absolute; top: 8px; right: 12px;
  font-family: var(--mono); font-size: 11px; font-weight: 500; letter-spacing: 0.05em; text-transform: uppercase;
  color: var(--glacier); background: var(--slate-surface); border: 1px solid var(--slate-line);
  padding: 3px 8px; border-radius: 2px; opacity: 0; transform: translateY(-4px);
  transition: opacity 0.2s var(--ease), transform 0.2s var(--ease); pointer-events: none;
  box-shadow: 0 4px 12px rgba(0,0,0,0.3); z-index: 10;
}
.svc.askable::after { top: 12px; right: 24px; }
.askable:hover::after { opacity: 1; transform: translateY(0); }
@media (pointer: coarse) {
  .askable::after {
    content: 'Ask \2197'; opacity: 1; transform: none; top: 8px; right: 10px; font-size: 11px; padding: 2px 6px;
    /* Was --muted-dim. On touch this badge is the only cue that the tile is
       tappable, and the scene-first pass further down strips its background,
       so it sits bare on the film — muted-dim there is ~1.5:1, i.e. the cue
       is present in the DOM but not actually visible. */
    color: var(--shell); background: transparent; border-color: var(--slate-line); box-shadow: none;
  }
  .svc.askable::after { top: 10px; right: 12px; }
}

/* ---------- Contact ---------- */
.mobile-contact-actions { display: none; }
.contact-grid { margin-top: clamp(14px, 2.6vh, 24px); display: grid; grid-template-columns: 1.1fr 0.9fr; gap: clamp(20px, 3vw, 40px); align-items: start; }
.contact-grid.contact-grid-single { grid-template-columns: minmax(0, 1fr); max-width: 820px; }
.booking-inline-container {
  background: rgba(13, 17, 19, 0.84) !important;
  box-shadow: 0 18px 50px rgba(0,0,0,0.24);
  backdrop-filter: blur(14px);
  -webkit-backdrop-filter: blur(14px);
}
/* (D) The intake side panel is a self-contained UI card next to the
   calendar's own solid panel, so it gets a flat near-solid fill (matching
   .booking-inline-container's treatment) rather than the feathered-edge
   radial used for bare text fragments. */
.contact-side-panel {
  background: rgba(9, 11, 12, 0.87);
  border-radius: 12px;
  padding: 16px 18px;
  backdrop-filter: blur(10px);
  -webkit-backdrop-filter: blur(10px);
}
/* Close control for the mobile intake overlay (see #contact.intake-open
   below). Hidden everywhere else — the desktop side panel has no open/close
   state, and on mobile it only exists once the overlay is active. */
.intake-overlay-close { display: none; }
/* Wheel-catch: see the HTML comment above these elements in index.html. Sticky
   so the strip stays reachable at the container's visible top/bottom edge
   regardless of how far the calendar itself is scrolled inside
   .booking-inline-container. */
.wheel-catch { position: sticky; left: 0; right: 0; height: 16px; z-index: 3; pointer-events: auto; }
.wheel-catch-top { top: 0; }
.wheel-catch-bottom { bottom: 0; }
.intake { display: grid; gap: 10px; max-width: 600px; }
.intake .row2 { display: grid; grid-template-columns: 1fr 1fr; gap: 10px; }
.intake label { font-family: var(--mono); font-size: 12px; letter-spacing: 0.08em; text-transform: uppercase; color: var(--muted-dim); display: block; margin-bottom: 5px; }
.intake input, .intake textarea { width: 100%; background: var(--slate-surface); border: 1px solid var(--line-strong); border-radius: 6px; color: var(--shell); font-family: var(--serif); font-size: 16px; padding: 10px 12px; }
.intake input[aria-invalid="true"], .intake textarea[aria-invalid="true"] { border-color: var(--oxblood-light); }
.intake input:disabled, .intake textarea:disabled { opacity: 0.6; }
.intake textarea { resize: vertical; min-height: 80px; line-height: 1.45; }
.intake input:focus, .intake textarea:focus { outline: none; border-color: var(--glacier); }
.intake input::placeholder, .intake textarea::placeholder { color: var(--muted-dim); }
.intake .submit { margin-top: 2px; justify-self: start; background: var(--oxblood); color: var(--shell); border: 1px solid var(--oxblood-light); border-radius: 999px; padding: 11px 24px; font-family: var(--mono); font-size: 12px; letter-spacing: 0.08em; text-transform: uppercase; cursor: pointer; transition: background 0.2s var(--ease); }
.intake .submit:hover { background: var(--oxblood-light); color: var(--slate); }
.intake .submit:disabled { opacity: 0.55; cursor: default; }
.form-status { font-family: var(--mono); font-size: 12px; color: var(--glacier); min-height: 16px; }
.form-status.err { color: var(--oxblood-light); }
.copt { background: var(--slate); transition: background 0.25s var(--ease); }
.copt:hover { background: var(--slate-surface); }
.arrow { color: var(--oxblood-light); }
.site-foot { margin-top: clamp(16px, 3vh, 28px); font-family: var(--mono); font-size: 11.5px; letter-spacing: 0.04em; color: var(--shell); }
.site-foot a:hover { color: var(--shell); }
/* (B) Title-sequence pass: site-foot no longer floats bare on the film as its
   own reveal-step — it moved INTO .contact-side-panel (a genuine card),
   appended after the existing Direct Email block, so it reads as the card's
   closing line instead of a stray paragraph loose on the room. */
.contact-side-panel .site-foot { margin-top: 20px; padding-top: 16px; border-top: 1px solid var(--slate-line); font-size: 10.5px; }

@media (max-width: 980px) {
  .contact-grid { grid-template-columns: 1fr; }
}
@media (max-width: 880px) {
  .svc { grid-template-columns: 1fr; } .svc-meta { text-align: left; }
  .discovery-card { grid-template-columns: 1fr; }
}
@media (max-width: 620px) {
  .intake .row2 { grid-template-columns: 1fr; }
  .roi-strip { grid-template-columns: 1fr; }
  .case-field { grid-template-columns: 1fr; gap: 2px; }
}

/* ---------- Hub (bottom nav capsule) ---------- */
.hub {
  position: fixed; left: 50%; bottom: max(24px, env(safe-area-inset-bottom));
  transform: translateX(-50%); z-index: 50;
  /* Was --panel-bg-gradient (0.55 alpha at the top stop). The capsule is
     fixed over the brightest part of the film for the whole journey and its
     12px nav labels have no other surface to sit on, so the fill is raised to
     ~0.88+ throughout — still translucent (the film reads through it), just
     opaque enough for the labels below to hold 4.5:1. */
  background: linear-gradient(165deg, rgba(13,16,19,0.88) 0%, rgba(11,13,14,0.92) 60%, rgba(9,11,12,0.95) 100%);
  border: 1px solid var(--slate-line); border-radius: 999px;
  padding: 6px 16px; box-shadow: 0 16px 40px rgba(0,0,0,0.5), inset 0 1px 0 rgba(255,255,255,0.03);
  backdrop-filter: blur(20px); -webkit-backdrop-filter: blur(20px);
  display: flex; align-items: center; justify-content: center; gap: 16px;
  max-width: 95vw;
}
.hub .wm { font-family: var(--serif); font-weight: 500; font-size: 14px; color: var(--shell); white-space: nowrap; }
.hub .wm .stone { font-family: var(--twist); color: var(--oxblood-light); }
.hub .active { display: none !important; }
.hub .row { display: flex; align-items: center; justify-content: center; gap: 8px; }
.hub nav { display: flex; flex-wrap: wrap; justify-content: center; gap: 3px 4px; }
/* Was --muted-dim: 12px at that value only just scrapes 4.5:1 on the raised
   capsule fill above, with no headroom for a bright frame behind it. */
.hub nav button { font-family: var(--mono); font-size: 12px; letter-spacing: 0.06em; text-transform: uppercase; color: var(--muted); background: none; border: 1px solid transparent; padding: 7px 10px; cursor: pointer; border-radius: 2px; transition: all 0.2s var(--ease); }
.hub nav button.on { color: var(--shell); background: var(--oxblood); border-color: var(--oxblood); }
.hub nav button:not(.on):hover { color: var(--shell); border-color: var(--slate-line); }
.hub .arrow-btn { width: 34px; height: 34px; border-radius: 50%; cursor: pointer; background: none; border: 1px solid var(--slate-line); color: var(--shell); font-size: 16px; font-family: var(--serif); flex: none; transition: all 0.2s var(--ease); display: inline-flex; align-items: center; justify-content: center; }
.hub .arrow-btn:hover { border-color: var(--oxblood-light); color: var(--oxblood-light); }

:focus-visible { outline: 2px solid var(--glacier); outline-offset: 3px; }

/* ---------- Accessibility utilities ----------
   .sr-only backs #beatLive, the live region that announces the destination
   chapter at gesture time (the engine writes its textContent in
   syncBeatChrome). .skip-link is the first tabbable element on the page and
   is the only way past the hub for a keyboard visitor. Both were briefly
   inlined in index.html's <head>; they belong here so every page can use
   them. */
.sr-only {
  position: absolute; width: 1px; height: 1px; margin: -1px; padding: 0;
  overflow: hidden; clip: rect(0, 0, 0, 0); clip-path: inset(50%);
  white-space: nowrap; border: 0;
}
.skip-link {
  position: absolute; left: -9999px; top: auto;
  width: 1px; height: 1px; overflow: hidden; z-index: 200;
}
.skip-link:focus {
  left: max(12px, env(safe-area-inset-left)); top: 12px;
  width: auto; height: auto; overflow: visible;
  font-family: var(--mono); font-size: 12px; letter-spacing: 0.08em;
  text-transform: uppercase; padding: 11px 18px; border-radius: 999px;
  border: 1px solid var(--oxblood-light); background: var(--oxblood);
  color: var(--shell); text-decoration: none;
}

/* ---------- Booking Modal ---------- */
.booking-modal-overlay {
  position: fixed; inset: 0; z-index: 100;
  background: rgba(11, 13, 14, 0.72); backdrop-filter: blur(16px);
  display: flex; align-items: center; justify-content: center;
  opacity: 0; pointer-events: none; transition: opacity 0.3s var(--ease);
}
.booking-modal-overlay.open { opacity: 1; pointer-events: auto; }
.booking-modal {
  background: linear-gradient(165deg, rgba(13,16,19,0.94) 0%, rgba(11,13,14,0.9) 100%);
  border: 1px solid var(--slate-line); border-radius: 20px; padding: clamp(24px, 4vw, 44px);
  width: min(840px, 94vw); max-height: 90vh; overflow-y: auto; position: relative;
  box-shadow: 0 24px 50px rgba(0,0,0,0.5), inset 0 1px 0 rgba(255,255,255,0.03);
}
.booking-close { position: absolute; top: 16px; right: 20px; background: none; border: none; font-size: 28px; color: var(--muted); cursor: pointer; line-height: 1; transition: color 0.2s var(--ease); }
.booking-close:hover { color: var(--shell); }
.booking-slots-hint { font-size: 13px; color: var(--muted-dim); text-align: center; padding: 32px 0; font-family: var(--mono); }

/* Data-handling notice under the intake form and in the chat drawer footer.
   Deliberately quiet: it should reassure someone who looks for it, not shout
   over the form it sits under. Matches .price-note's mono treatment rather
   than inheriting the 16px serif body face. */
.privacy-note {
  margin-top: 10px;
  font-family: var(--mono);
  font-size: 11px;
  line-height: 1.5;
  letter-spacing: 0.02em;
  color: var(--muted);
}
.chat-drawer-footer .privacy-note { display: inline; margin-top: 0; font-size: inherit; color: inherit; }

/* Type floor for the two labels in the contact panel that carry their size in
   an inline style attribute (hence !important). Applied at every width, not
   just mobile — 9.5px uppercase mono was unreadable on a laptop too. The
   :first-child on .copt is deliberate: its second div is the email address
   itself, which must keep its larger size. */
.contact-side-panel .label[style] { font-size: 11px !important; }
.contact-side-panel .copt div[style]:first-child { font-size: 11px !important; }

/* Calendar failed to load (blocked by a tracker blocker / corporate proxy, or
   the embed never rendered — engine.js's watchdog and the SDK onerror both
   land here). The email line is normally 12px muted text sitting BELOW a
   560px-tall empty box, i.e. off-screen exactly when it's needed. Promote it
   into the empty space and make it the answer rather than a footnote. */
.booking-fallback-visible {
  position: absolute; top: 50%; left: 0; right: 0; transform: translateY(-50%);
  z-index: 3; margin: 0 auto; max-width: 34ch;
  font-size: 15px !important; line-height: 1.6;
  color: var(--shell) !important;
  background: var(--slate-surface); border: 1px solid var(--slate-line);
  border-radius: 8px; padding: 20px 22px;
}
.booking-fallback-visible a { color: var(--oxblood-light) !important; text-decoration: underline; }
.booking-modal .booking-fallback-visible { position: static; transform: none; margin-top: 24px; }

/* ---------- Floating Chat Widget & Radial Trigger ---------- */
.chat-widget-container { position: fixed; right: 24px; bottom: 24px; z-index: 1000; font-family: var(--serif); }
.radial-trigger {
  width: 56px; height: 56px; border-radius: 50%; background: var(--oxblood); border: 1px solid var(--oxblood-light);
  color: var(--shell); cursor: pointer; display: flex; align-items: center; justify-content: center;
  box-shadow: 0 8px 32px rgba(0,0,0,0.5), inset 0 1px 0 rgba(255,255,255,0.1); transition: all 0.3s var(--ease);
  position: relative; z-index: 1002;
}
.radial-trigger:hover { transform: scale(1.05); background: var(--oxblood-light); box-shadow: 0 12px 40px rgba(154,51,64,0.3); }
.radial-trigger:active { transform: scale(0.95); }

.chat-drawer {
  position: fixed; right: 24px; bottom: 96px; width: min(400px, 92vw); height: min(560px, 78vh);
  background: linear-gradient(165deg, rgba(13,16,19,0.96) 0%, rgba(11,13,14,0.94) 60%, rgba(9,11,12,0.98) 100%);
  border: 1px solid var(--slate-line); border-radius: 16px;
  box-shadow: 0 16px 48px rgba(0,0,0,0.6), inset 0 1px 0 rgba(255,255,255,0.03);
  display: flex; flex-direction: column; overflow: hidden;
  opacity: 0; transform: translateY(20px) scale(0.95); pointer-events: none;
  transition: all 0.3s var(--ease); z-index: 999;
  backdrop-filter: blur(20px); -webkit-backdrop-filter: blur(20px);
}
.chat-drawer.open { opacity: 1; transform: translateY(0) scale(1); pointer-events: auto; }
.chat-drawer-header { display: flex; align-items: center; gap: 10px; padding: 14px 20px; border-bottom: 1px solid var(--slate-line); background: rgba(246,244,239,0.02); }
.presence { width: 8px; height: 8px; border-radius: 50%; background: var(--glacier); box-shadow: 0 0 0 4px rgba(111,168,198,0.16); }
.chat-drawer-header .who { font-family: var(--mono); font-size: 12px; color: var(--shell); letter-spacing: 0.04em; text-transform: uppercase; flex-grow: 1; }
.chat-drawer-close { background: none; border: none; color: var(--muted); font-size: 24px; cursor: pointer; line-height: 1; transition: color 0.2s var(--ease); }
.chat-drawer-close:hover { color: var(--oxblood-light); }
.chat-log { flex-grow: 1; padding: 16px; font-size: 14.5px; overflow-y: auto; display: flex; flex-direction: column; gap: 11px; }
.chat-log::-webkit-scrollbar { width: 6px; } .chat-log::-webkit-scrollbar-thumb { background: var(--slate-line); border-radius: 3px; }
.msg { max-width: min(86%, 44rem); font-size: 15px; line-height: 1.55; padding: 10px 14px; border-radius: 12px; white-space: pre-wrap; word-wrap: break-word; animation: msgin 0.36s var(--ease); }
@keyframes msgin { from { opacity: 0; transform: translateY(10px); } to { opacity: 1; transform: none; } }
.msg-user { align-self: flex-end; background: rgba(154,51,64,0.26); border: 1px solid rgba(154,51,64,0.4); border-bottom-right-radius: 4px; color: var(--shell); }
.msg-assistant { align-self: flex-start; background: var(--slate-raised); border: 1px solid var(--slate-line); border-bottom-left-radius: 4px; color: var(--shell-soft); }
.msg-assistant strong { color: var(--shell); font-weight: 600; }
.msg-error { color: var(--oxblood-light); }
.typing { display: inline-flex; gap: 4px; }
.typing span { width: 6px; height: 6px; border-radius: 50%; background: var(--muted-dim); animation: tp 1.4s ease-in-out infinite; }
.typing span:nth-child(2) { animation-delay: 0.18s; } .typing span:nth-child(3) { animation-delay: 0.36s; }
@keyframes tp { 0%, 60%, 100% { transform: translateY(0); opacity: 0.5; } 30% { transform: translateY(-4px); opacity: 1; } }
.chat-drawer .chips { display: flex; flex-wrap: wrap; gap: 8px; padding: 0 16px 12px; }
.chip { font-family: var(--serif); font-size: 13.5px; color: var(--muted); background: var(--slate); border: 1px solid var(--slate-line); border-radius: 999px; padding: 7px 13px; cursor: pointer; transition: all 0.2s var(--ease); }
.chip:hover { color: var(--shell); border-color: var(--oxblood); }
.chat-drawer .chat-input-row { display: flex; gap: 10px; padding: 12px 16px; background: rgba(11,13,14,0.5); border-top: 1px solid var(--slate-line); }
.chat-input-row textarea { flex: 1 1 auto; resize: none; min-height: 22px; max-height: 110px; background: var(--slate); border: 1px solid var(--slate-line); border-radius: 8px; color: var(--shell); font-family: var(--serif); font-size: 16px; line-height: 1.4; padding: 9px 12px; }
.chat-input-row textarea:focus { outline: none; border-color: var(--glacier); }
.chat-input-row textarea::placeholder { color: var(--muted-dim); }
.send-btn { flex: none; background: var(--oxblood); color: var(--shell); border: none; border-radius: 8px; padding: 0 18px; font-family: var(--mono); font-size: 12px; letter-spacing: 0.08em; text-transform: uppercase; cursor: pointer; transition: background 0.2s var(--ease); }
.send-btn:hover { background: var(--oxblood-light); color: var(--slate); }
.send-btn:disabled { opacity: 0.5; cursor: default; }
/* 11px, not 10px: this footer now carries the data-handling notice, which is
   the one line here a visitor may actually need to read. */
.chat-drawer-footer { padding: 8px 16px; border-top: 1px solid var(--slate-line); font-family: var(--mono); font-size: 11px; color: var(--muted-dim); background: rgba(11,13,14,0.8); text-align: center; }

/* ---------- Spatial copy: fragments placed into the filmed room ----------
   Small rotations and shears break the old bounded-card rhythm without making
   the page feel decorative. Dense copy still sits on calm, high-contrast
   surfaces; the empty outer wrappers let the scene breathe between fragments. */
#intro h1 {
  --spatial-transform: rotate(-0.45deg) skewX(-0.35deg);
  transform-origin: 0 50%;
}
#intro .standfirst { --spatial-transform: translateX(16px) rotate(0.16deg); }
#intro .hero-entry-offer {
  --spatial-transform: translateX(8px) rotate(0.38deg) skewX(-0.55deg);
  clip-path: polygon(1% 0, 100% 3%, 98% 100%, 0 94%);
}
#intro .actions { --spatial-transform: translateX(14px) rotate(-0.28deg); }

#proof h2 {
  --spatial-transform: rotate(-0.52deg) skewX(-0.4deg);
  transform-origin: 0 50%;
}
#proof .lede { --spatial-transform: translateX(18px) rotate(0.14deg); }
#proof .case-studies {
  --spatial-transform: translateX(56px) rotate(0.32deg) skewX(-0.25deg);
  transform-origin: 50% 50%;
}
#proof .case-card {
  padding: 0;
  gap: 9px;
  background: transparent;
  border: 0;
  border-radius: 0;
  box-shadow: none;
  backdrop-filter: none;
  -webkit-backdrop-filter: none;
}
#proof .case-card:hover { background: transparent; }
#proof .case-card h3 {
  align-self: flex-start;
  margin: 0 0 2px 12px;
  padding: 10px 18px 11px 15px;
  border: 0;
  border-left: 2px solid rgba(111, 168, 198, 0.75);
  background: rgba(13, 18, 21, 0.91);
  box-shadow: 0 12px 28px rgba(0, 0, 0, 0.24);
  transform: rotate(-0.62deg) skewX(-0.55deg);
  clip-path: polygon(1% 0, 100% 4%, 97% 100%, 0 91%);
}
#proof .roi-strip {
  gap: 8px;
  margin: 0 8px 2px 0;
  background: transparent;
  border: 0;
}
#proof .roi-strip div {
  position: relative;
  padding: 12px 13px 14px;
  background: rgba(19, 28, 33, 0.92);
  border-top: 1px solid rgba(111, 168, 198, 0.26);
  box-shadow: 0 12px 28px rgba(0, 0, 0, 0.22);
  clip-path: polygon(2% 0, 100% 3%, 96% 100%, 0 94%);
  transform: rotate(var(--metric-angle, -0.35deg));
}
#proof .roi-strip div:nth-child(2) {
  --metric-angle: 0.46deg;
  margin-top: 7px;
}
#proof .roi-strip div:nth-child(3) { --metric-angle: -0.2deg; margin-top: 2px; }
#proof .case-field {
  position: relative;
  margin-left: var(--field-shift, 0);
  padding: 10px 14px 11px;
  background: rgba(13, 18, 21, 0.9);
  border-left: 1px solid rgba(111, 168, 198, 0.3);
  box-shadow: 0 10px 24px rgba(0, 0, 0, 0.2);
  transform: rotate(var(--field-angle, -0.26deg)) skewX(-0.34deg);
  clip-path: polygon(0.5% 0, 100% 2%, 98.5% 100%, 0 95%);
}
#proof .case-field:nth-of-type(3) {
  --field-angle: 0.32deg;
  --field-shift: 18px;
  margin-right: 4px;
}
#proof .case-field:nth-of-type(4) {
  --field-angle: -0.18deg;
  --field-shift: 7px;
  margin-right: 13px;
}
#proof .case-actions {
  align-self: flex-start;
  margin: 1px 0 0 22px;
  padding: 7px 10px;
  border: 0;
  background: rgba(11, 15, 17, 0.78);
  transform: rotate(0.28deg);
  clip-path: polygon(1% 0, 100% 4%, 98% 100%, 0 92%);
}

#services h2 {
  --spatial-transform: rotate(-0.48deg) skewX(-0.38deg);
  transform-origin: 0 50%;
}
#services > .beat-inner .lede { --spatial-transform: translateX(18px) rotate(0.14deg); }
#services .discovery-card {
  padding: 0;
  gap: 12px;
  background: transparent;
  border: 0;
  box-shadow: none;
  backdrop-filter: none;
  -webkit-backdrop-filter: none;
}
#services .discovery-card > div {
  padding: 18px 22px 20px 20px;
  background: linear-gradient(135deg, rgba(51, 27, 31, 0.94), rgba(14, 18, 21, 0.91));
  border-left: 3px solid var(--oxblood-light);
  box-shadow: 0 18px 42px rgba(0, 0, 0, 0.26);
  transform: rotate(0.52deg) skewX(-0.45deg);
  clip-path: polygon(1% 0, 100% 3%, 97% 100%, 0 94%);
}
#services .discovery-card .btn {
  justify-self: start;
  margin: -2px 0 0 22px;
  transform: rotate(-0.48deg);
}
#services .services {
  gap: 10px;
  padding: 0 5px;
  background: transparent;
  border: 0;
  box-shadow: none;
}
#services .svc {
  --slab-angle: -0.38deg;
  position: relative;
  margin-top: var(--slab-drop, 0);
  background: rgba(11, 15, 17, 0.91);
  border-top: 1px solid rgba(111, 168, 198, 0.2);
  box-shadow: 0 14px 34px rgba(0, 0, 0, 0.23);
  transform: rotate(var(--slab-angle)) skewX(-0.38deg);
  transform-origin: 50% 50%;
  clip-path: polygon(1% 0, 100% 3%, 98% 100%, 0 95%);
}
#services .svc:nth-child(2) { --slab-angle: 0.46deg; --slab-drop: 9px; }
#services .svc:nth-child(3) { --slab-angle: -0.22deg; --slab-drop: 3px; }
#services .svc:hover { background: rgba(24, 29, 32, 0.94); }
#services .svc.askable:hover {
  transform: translateY(-3px) rotate(var(--slab-angle)) skewX(-0.38deg) !important;
}
/* price-note's own --spatial-transform is gone: it's no longer a direct
   .panel-inner > .reveal-step (moved inside .services — see the DOM-move
   comment above the .services .price-note rule), so it just inherits
   .services' own spatial placement now instead of carrying one independently. */

#contact h2 {
  --spatial-transform: rotate(-0.42deg) skewX(-0.3deg);
  transform-origin: 0 50%;
}
#contact .lede { --spatial-transform: translateX(16px) rotate(0.12deg); }
#contact .contact-grid { --spatial-transform: rotate(-0.12deg); }
#contact .contact-side-panel { transform: translateY(16px) rotate(0.24deg); }
/* site-foot's own --spatial-transform is gone: it's no longer a direct
   .panel-inner > .reveal-step (moved inside .contact-side-panel — see the
   DOM-move comment above the .contact-side-panel .site-foot rule), so it
   just rides along with that card's own transform now. */

/* ---------- Reduced motion: four static tableaux, no camera travel ----------
   The beat model is preserved (still one section at a time in the fixed app),
   but there is no travel tween: engine.js jumps progress straight to the target
   beat and these short opacity transitions do a ~200ms crossfade instead. */
@media (prefers-reduced-motion: reduce) {
  html.js .panel-inner { transition: opacity 0.2s ease; }
  html.js .panel-inner > .reveal-step {
    opacity: 1 !important;
    transform: var(--spatial-transform, none) !important;
    filter: none !important;
    transition: none !important;
  }
  .float-1, .float-2, .float-3 { animation: none; }
  .msg { animation: none; }
  /* Reduced motion also removes the still/video crossfade. */
  .world-video, .world-still img { transition: none; }
}

/* ==========================================================================
   Mobile composition (Chunk 3, PLAN.md §4) — 390x844 primary target, sane
   across 360-430 widths and landscape. Not a desktop crop: the scene itself
   is recomposed (engine.js viewBox switch + the mobile window/ERP-corridor
   groups defined earlier in this file), and the copy becomes four complete
   one-screen chapters so vertical swipes always continue the camera journey.
   ========================================================================== */
@media (max-width: 700px) {
  /* (B) Title scale on mobile too, but held to a smaller bump than desktop's
     ~1.3x (~1.1-1.15x here) — the one-screen phone flow's own vertical
     budget is the hard constraint (verified at 375x812: no overflow, no
     second scroll, hub/buttons unaffected). */
  h1 { font-size: clamp(34px, 9vw, 40px); }
  h2 { font-size: clamp(31px, 8.2vw, 35px); line-height: 1.02; }
  .lede { font-size: 16px; line-height: 1.42; }

  /* Copy scrim, mobile variant. The reference relocates all copy to a
     bottom band and fades a bottom-up gradient under it; this site's mobile
     chapters don't relocate copy that way — every beat's kicker/heading/lede
     stays at the TOP of the one-screen composition (measured: y ~58-335px
     of 812px tall for START's full top cluster including actions, ~74-281px
     for PROOF/OFFER/CONTACT's kicker->lede, and CONTACT's
     .mobile-contact-actions runs to ~535px). PROOF/OFFER's lower rail
     (.case-studies / .services) is already self-covered by its own opaque
     card elements (case-card's children, discovery-card, .svc — untouched
     by this pass), so it doesn't need the scrim.
     REWORKED alongside the desktop ramp above (owner feedback, 2026-07): the
     previous 88%/96% stops were the same flat-plateau-then-cliff shape,
     just rotated 90deg — 0.92 alpha held across the whole top 88% of the
     82%-tall box (~72% of the viewport) is still a giant dark rectangle,
     just a horizontal one. Same fix: one continuous ramp, opaque only right at the
     top edge, fully open well before the box's own bottom. Text lives in the
     top ~40% of the viewport (see measurements above); at box-relative 35%
     (~29% viewport) alpha is still ~0.78 and at 60% (~49% viewport) it's
     ~0.48 — both comfortably above the ~0.42-0.66 alpha the contrast math
     above requires for --shell/--muted text — then it's fully transparent
     by 85% of the box (~70% viewport), opening far more of the lower frame
     than the old 14%. */
  /* Mobile whisper (title-sequence pass): same reasoning as the desktop
     rework above — the settled grade alone clears 4.5:1 for --shell body
     text with no scrim at all, so this is margin for the grade-transition
     tail, not a legibility requirement. Rotated 180deg (opaque at the top
     edge where the kicker/heading cluster sits, gone by 40% of the panel's
     own height, well above the case-card/discovery-card/svc rail lower down
     which is already self-covered by its own card surfaces). */
  .panel-inner::before {
    width: auto;
    left: 0;
    right: 0;
    top: 0;
    bottom: 0;
    height: auto;
    background: linear-gradient(180deg,
      rgba(var(--scrim-rgb), 0.35) 0%,
      rgba(var(--scrim-rgb), 0) 40%);
  }

  /* Keep the spatial character on a narrow screen, but reduce lateral offsets
     so no line starts or ends against a clipped edge. */
  #intro h1 { --spatial-transform: rotate(-0.24deg) skewX(-0.18deg); }
  #intro .standfirst { --spatial-transform: translateX(5px) rotate(0.08deg); }
  #intro .hero-entry-offer { --spatial-transform: rotate(0.18deg) skewX(-0.22deg); }
  #intro .actions { --spatial-transform: translateX(4px) rotate(-0.14deg); }
  #proof h2 { --spatial-transform: rotate(-0.26deg) skewX(-0.18deg); }
  #proof .lede { --spatial-transform: translateX(5px) rotate(0.06deg); }
  #proof .case-studies { --spatial-transform: rotate(0.12deg); }
  #services h2 { --spatial-transform: rotate(-0.24deg) skewX(-0.16deg); }
  #services > .beat-inner .lede { --spatial-transform: translateX(5px) rotate(0.06deg); }
  #contact h2 { --spatial-transform: rotate(-0.22deg) skewX(-0.14deg); }
  #contact .lede { --spatial-transform: translateX(4px) rotate(0.05deg); }
  #contact .contact-grid { --spatial-transform: none; }
  #contact .contact-side-panel { transform: none; }

  /* Phone chapters use the same one-gesture rhythm as desktop. Each chapter is
     a single composed viewport; deeper detail moves behind taps instead of
     turning the chapter into a second vertical page.
     The fixed height + overflow:hidden this used to carry made that rhythm a
     hard clip: on a short phone, or with a large accessibility text size, or
     in landscape, anything past the first screen was unreachable by ANY input
     — no scroll, no swipe, no keyboard. max-height keeps the one-screen look
     whenever the chapter fits and lets it scroll when it doesn't.
     overscroll-behavior stays contained so reaching the panel's own top/bottom
     edge hands the gesture back to the engine's beat paging instead of
     chaining to the document. */
  .panel-inner {
    max-height: 100dvh;
    overflow-y: auto;
    -webkit-overflow-scrolling: touch;
    overscroll-behavior: contain;
    padding-top: max(44px, env(safe-area-inset-top, 0px));
    padding-left: max(20px, env(safe-area-inset-left));
    padding-right: max(20px, env(safe-area-inset-right));
    padding-bottom: calc(108px + env(safe-area-inset-bottom, 0px));
    width: min(100%, 480px);
    margin: 0 auto;
  }

  /* START — a complete first chapter without a nested reading scroll. */
  #intro .hero-top { margin-bottom: 10px; gap: 11px; }
  #intro .hero-top .portrait { width: 50px; height: 50px; }
  /* Mobile type floor: nothing on the page drops below 11px. The sub-9px
     sizes this block used to carry were there to force each chapter onto one
     screen; the panel scrolls now (see .panel-inner above), so the budget no
     longer has to be paid for in unreadable text. */
  #intro .hero-top .kicker { font-size: 11px; line-height: 1.35; }
  #intro .standfirst { margin-top: 12px; font-size: 16px; line-height: 1.4; }
  #intro .hero-entry-offer { margin-top: 12px; padding: 10px 13px; }
  #intro .hero-entry-title { font-size: 13px; line-height: 1.32; }
  #intro .actions { margin-top: 12px; }
  #intro .price-note { display: none; }

  /* PROOF — headline and context frame the monitor; the compact evidence rail
     sits below it. The longer audit fields remain in the DOM for desktop and
     assistant context, but do not consume the phone's travel gesture. */
  #proof h2 { margin-bottom: 11px; }
  #proof .lede { max-width: 36ch; }
  /* Back in normal flow. Pinning this rail to the foot of the chapter with
     position:absolute only worked while the chapter could never overflow —
     it grows UPWARD from its bottom edge, so with .case-field restored above
     it would grow straight into the flowed heading/lede, and no gesture can
     pull overlapping layers apart. In flow it simply scrolls with the rest of
     the chapter (see .panel-inner above); the panel's own padding-bottom
     already clears the fixed hub. */
  #proof .case-studies {
    position: static;
    width: auto;
    margin: 14px 0 0;
  }
  #proof .case-card { background: transparent; }
  #proof .case-card h3 {
    margin: 0 4px;
    padding: 8px 13px 9px;
    background: rgba(13, 18, 21, 0.96);
    font-size: 14.5px;
  }
  #proof .roi-strip {
    grid-template-columns: repeat(3, minmax(0, 1fr));
    gap: 6px;
    margin: 0;
  }
  #proof .roi-strip div { min-width: 0; padding: 9px 8px 10px; background: rgba(19, 28, 33, 0.97); }
  #proof .roi-strip strong { font-size: 22px; }
  #proof .roi-strip span { margin-top: 4px; font-size: 11px; line-height: 1.3; }
  #proof .case-actions {
    margin: 0 0 0 4px;
    padding: 5px 7px;
    gap: 6px;
    background: rgba(11, 15, 17, 0.91);
  }
  #proof .case-actions .mini-link { font-size: 11px; padding-left: 9px; padding-right: 9px; }
  #proof .mobile-proof-details { display: inline-flex; align-items: center; }
  #proof .case-actions .secondary { display: none; }

  /* OFFER — the recommendation leads; the three services become a compact
     tappable rail. Their full descriptions open through the existing ask flow. */
  #services h2 { margin-bottom: 10px; }
  #services .discovery-card { margin-top: 12px; }
  #services .discovery-card > div {
    padding: 13px 16px 14px;
    transform: rotate(0.2deg) skewX(-0.18deg);
  }
  #services .discovery-card h3 { margin: 6px 0; font-size: 17px; line-height: 1.1; }
  #services .discovery-card p { display: none; }
  #services .discovery-points { gap: 5px; margin-top: 8px; }
  #services .discovery-points span { padding: 5px 7px; font-size: 11px; }
  #services .discovery-card .btn { margin: 8px 0 0 6px; transform: rotate(-0.2deg); }
  /* Back in normal flow — same reasoning as #proof .case-studies above: with
     .svc-body and .price-note restored inside it, a bottom-pinned absolute
     rail grows upward into the discovery card flowed above it. */
  #services .services {
    position: static;
    display: grid;
    grid-template-columns: 1fr;
    gap: 7px;
    margin: 12px 0 0;
    padding: 0;
  }
  #services .svc {
    --slab-angle: -0.12deg;
    --slab-drop: 0;
    display: grid;
    grid-template-columns: minmax(0, 1fr) auto;
    gap: 2px 10px;
    min-height: 48px;
    margin-top: 0;
    padding: 9px 12px;
  }
  #services .svc:nth-child(2) { --slab-angle: 0.14deg; --slab-drop: 0; }
  #services .svc:nth-child(3) { --slab-angle: -0.08deg; --slab-drop: 0; }
  #services .svc-name { font-size: 15px; }
  #services .svc-meta { margin: 0; font-size: 11px; text-align: right; }
  /* .svc-body (scope), .price-note ("Prices ex VAT" + the money-back line)
     and the "Ask ↗" badge used to be display:none here to buy vertical space
     for the one-screen chapter. The panel scrolls now, so the scope, the tax
     note and the guarantee are readable on a phone again — and the badge,
     which is the only thing telling a touch visitor these price rows are
     buttons, is back with them.
     The badge is placed as a grid item rather than left absolute at the
     tile's top-right: the mobile tile is a two-column grid whose right column
     is .svc-meta's price, and the badge would sit straight on top of it. */
  #services .svc.askable::after {
    position: static;
    grid-column: 1 / -1;
    justify-self: start;
    margin-top: 6px;
  }

  /* CONTACT — the embedded scheduler and long form become an intentional tap
     destination on phones, keeping the final chapter in the cinematic flow. */
  #contact h2 { margin-bottom: 12px; }
  #contact .lede { max-width: 38ch; }
  #contact .mobile-contact-actions {
    display: grid;
    gap: 10px;
    width: min(100%, 330px);
    margin-top: clamp(24px, 6vh, 48px);
  }
  #contact .mobile-contact-actions .btn { display: flex; align-items: center; justify-content: center; text-align: center; }
  #contact .mobile-ask,
  #contact .mobile-intake-trigger {
    min-height: 44px;
    padding: 10px 12px;
    border: 0;
    background: transparent;
    text-align: center;
    cursor: pointer;
  }
  /* .contact-grid stays hidden on phones — it is the desktop calendar/side-
     panel layout, and the same .contact-side-panel node is promoted to the
     intake overlay below. .site-foot is no longer hidden with it: it lives
     inside that side panel, so it now reads as the overlay's closing line
     rather than being suppressed outright. */
  #contact .contact-grid { display: none; }

  /* (B) Mobile intake overlay: .contact-grid is display:none above, so the
     one #intakeForm inside .contact-side-panel is otherwise unreachable on
     phones. #intakeMobileTrigger ("Prefer email?", in .mobile-contact-actions
     above) toggles .intake-open on #contact; this promotes the SAME
     .contact-side-panel node to a fixed overlay in place — no duplicate DOM,
     no duplicate IDs. engine.js pairs this with inert/aria-hidden, focus-to-
     first-field on open, focus-return-to-trigger on close, and Escape. */
  #contact.intake-open .contact-grid {
    display: block;
  }
  /* F1: .contact-grid is a JS-added .reveal-step (engine.js gives every
     direct child of .panel-inner that class), and once the beat has settled
     html.js .panel-inner.copy-entered > .reveal-step (~line 254) sets
     filter: blur(0) on it. A non-none filter on the NEAREST positioned
     ancestor becomes the containing block for our position:fixed overlay
     below, so without this override .contact-grid — not the viewport —
     sizes the overlay, and since its only other child
     (.booking-inline-container) is display:none here, that containing block
     collapses to ~0 height and the overlay renders as a sliver. Only
     prefers-reduced-motion (filter: none !important elsewhere) ever masked
     this. #contact.intake-open outranks .copy-entered > .reveal-step on
     specificity (ID beats classes), so this reliably wins.  */
  #contact.intake-open .contact-grid {
    filter: none;
    transform: none;
  }
  #contact.intake-open .contact-grid > .booking-inline-container {
    display: none;
  }
  #contact.intake-open .contact-side-panel {
    position: fixed;
    inset: 0;
    z-index: 110;
    overflow-y: auto;
    -webkit-overflow-scrolling: touch;
    border-radius: 0;
    transform: none;
    padding:
      max(56px, calc(env(safe-area-inset-top, 0px) + 40px))
      max(20px, env(safe-area-inset-right))
      max(32px, env(safe-area-inset-bottom))
      max(20px, env(safe-area-inset-left));
    background: linear-gradient(165deg, rgba(13,16,19,0.97) 0%, rgba(11,13,14,0.95) 60%, rgba(9,11,12,0.98) 100%);
  }
  #contact.intake-open .intake-overlay-close {
    display: flex;
    align-items: center;
    justify-content: center;
    position: fixed;
    top: max(12px, env(safe-area-inset-top, 0px));
    right: max(12px, env(safe-area-inset-right, 0px));
    z-index: 111;
    width: 40px;
    height: 40px;
    border-radius: 50%;
    background: var(--slate-surface);
    border: 1px solid var(--slate-line);
    color: var(--muted);
    font-size: 24px;
    line-height: 1;
    cursor: pointer;
  }
  #contact.intake-open .intake-overlay-close:hover { color: var(--shell); }
  /* .panel-inner's will-change:transform (for the copy tween) makes it the
     containing block for fixed-position descendants, and its overflow:hidden
     would then clip them to its own box instead of the true viewport. Both
     are inert while a beat is settled (no tween in flight while the overlay
     can be open), so drop them for the duration so the overlay and its close
     button size against the real viewport. */
  #contact.intake-open .panel-inner { will-change: auto; overflow: visible; }

  html:not(.js) .panel-inner { max-height: none; overflow-y: visible; }

  /* Tap targets: production standard is >=40px (Chunk 3 brief) — bumped
     here for every primary action, not only the hub nav already covered
     below, since [data-book]/askable/submit all gate the booking flow. */
  .btn, .mini-link, .intake .submit { min-height: 44px; padding-top: 13px; padding-bottom: 13px; }
  .send-btn { min-height: 44px; }
  .chip { min-height: 40px; padding-top: 10px; padding-bottom: 10px; }
  .intake input, .intake textarea, .chat-input-row textarea { font-size: 16px; }
  .intake input, .intake textarea { padding-top: 13px; padding-bottom: 13px; }

  .chat-widget-container {
    top: max(12px, env(safe-area-inset-top, 0px));
    right: 16px;
    bottom: auto;
  }
  .radial-trigger { width: 48px; height: 48px; }
  .chat-drawer {
    right: 0; bottom: 0; width: 100vw; height: 85vh; height: 85dvh;
    border-radius: 20px 20px 0 0; border-left: none; border-right: none; border-bottom: none;
    transform: translateY(100%); opacity: 1;
  }
  .chat-drawer.open { transform: translateY(0); }
  .hub nav button { padding: 10px 10px; min-height: 40px; }
  .hub .arrow-btn { width: 40px; height: 40px; }
  .hub {
    width: calc(100vw - 16px);
    max-width: none;
    box-sizing: border-box;
    padding: 6px;
    gap: 4px;
  }
  .hub .row { flex: 1 1 auto; min-width: 0; gap: 4px; }
  .hub > .mono:last-child {
    flex: 0 0 auto;
    padding-left: 6px !important;
    margin-left: 0 !important;
  }
  /* F6 (1.4.10 Reflow): at narrow widths (320px) the nav's inherited
     justify-content:center clipped the first/last item under the fixed hub's
     rounded edge once the row overflowed and became a horizontal scroller.
     flex-start plus inline padding/scroll-padding keeps every item — including
     the one syncBeatChrome() scrolls to center — reachable and unclipped. */
  .hub nav {
    flex: 1 1 auto;
    min-width: 0;
    max-width: none;
    overflow-x: auto;
    flex-wrap: nowrap;
    justify-content: flex-start;
    padding-inline: 4px;
    scroll-padding-inline: 8px;
    scrollbar-width: none;
    -webkit-overflow-scrolling: touch;
  }
  .hub nav::-webkit-scrollbar { display: none; }
  .hub .hub-brand { display: none; }
  /* .hint is no longer width-gated here — it's gated by pointer type (see
     the @media (pointer: fine) rule above) so touch phones keep the "swipe"
     hint and only pointer-fine visitors (mouse/trackpad, any width) lose it. */
}

/* Landscape phones (short height, width still <=900): keep the desktop scene
   framing (avoids an awkward middle-ground crop) but guard against the same
   overflow/clipping issues as portrait for short viewports. */
@media (max-width: 900px) and (orientation: landscape) and (max-height: 500px) {
  .panel-inner { padding-bottom: calc(96px + env(safe-area-inset-bottom, 0px)); }
  /* No-JS fallback only: let the short landscape stack flow instead of clipping.
     Under JS the fixed app's internal .panel-inner scroll handles overflow. */
  html:not(.js) .beat-inner { min-height: 100vh; min-height: 100dvh; }
  html:not(.js) .panel-inner { max-height: none; overflow-y: visible; }
}

/* ---------- Scene-first copy treatment ----------
   Keep the filmed world visible on desktop and mobile. Content is separated
   from the scene with layered text shadow instead of opaque black slabs. */
#sceneRead { background: none; }
#sceneScrim { background: rgba(7, 9, 9, 0.28); }
#scene.grade-settled .world-video,
#scene.grade-settled .world-still img {
  filter: brightness(0.66) saturate(0.84) contrast(1.08);
}
/* DO NOT re-disable .panel-inner::before here.
   That pseudo-element IS the copy scrim — the one thing in this file that
   actually guarantees contrast for body copy sitting on the film (see the
   long "(D) Copy scrim" note above its own rule). Switching it off left the
   text shadows below as the only protection, which is an edge aid, not a
   contrast mechanism: 20px body copy measured ~3.5:1 against a bright frame,
   under the 4.5:1 it needs. Its restored ramp is deliberately light (0.35 at
   the reading edge, gone by 40%) — the heavy lifting is shared with
   #sceneRead's wash below, so the film stays visible. If this looks too dark
   in a future pass, tune the alphas; do not delete the layer. */
.panel-inner {
  text-shadow:
    0 2px 2px rgba(0, 0, 0, 0.98),
    0 6px 16px rgba(0, 0, 0, 0.92),
    0 0 34px rgba(0, 0, 0, 0.82);
}

.hero-entry-offer,
.case-card,
#proof .case-card h3,
#proof .roi-strip,
#proof .roi-strip div,
#proof .case-field,
#proof .case-actions,
.discovery-card,
#services .discovery-card > div,
.services,
#services .services,
.svc,
#services .svc,
.services .price-note,
.booking-inline-container,
.contact-side-panel {
  background: transparent !important;
  box-shadow: none !important;
  backdrop-filter: none !important;
  -webkit-backdrop-filter: none !important;
}

.hero-entry-offer,
#proof .case-card h3,
#proof .roi-strip div,
#proof .case-field,
#proof .case-actions,
#services .discovery-card > div,
#services .svc {
  clip-path: none;
}

.hero-entry-offer {
  border-color: transparent;
  border-left-color: var(--glacier);
}
.hero-entry-offer:hover,
.case-card:hover,
.svc:hover,
#services .svc:hover,
.svc.askable:hover,
#services .svc.askable:hover {
  background: transparent !important;
}

#proof .case-card h3,
#proof .case-field,
#services .discovery-card > div,
#services .svc {
  border-top-color: transparent;
  border-right-color: transparent;
  border-bottom-color: transparent;
}

.discovery-points span,
.case-actions .mini-link,
.askable::after {
  background: transparent;
  box-shadow: none;
}

/* The opened mobile form and chat are functional overlays, not scene copy;
   they keep a solid surface so inputs and messages remain usable. */
#contact.intake-open .contact-side-panel {
  background: linear-gradient(165deg, rgba(13,16,19,0.97) 0%, rgba(11,13,14,0.95) 60%, rgba(9,11,12,0.98) 100%) !important;
  text-shadow: none;
}

/* ---------- Readability pass ----------
   Keep the world visible, but lower its brightest values and add one soft,
   scene-wide wash. Copy is split into short beats and lifted with a stronger
   drop shadow; there are still no rectangular content panels. */
/* This is the LAST #sceneRead rule for wide viewports, so these are the alphas
   that actually render. They were 0.20/0.10/0.04 on the horizontal ramp, which
   only covered the very left of the frame and opened up to nearly nothing by
   the time a long .lede line reached the middle of the screen — with the copy
   scrim disabled too, the tail end of every long line sat on raw film.
   Solved against the same worst-case bright film pixel this file has used
   throughout — raw rgb(254,253,245) through the settled grade below
   (brightness .58 / saturate .82 / contrast 1.12) => rgb(150,149,145),
   L=0.300. --shell (L=0.905) needs the composited background at L<=0.1623 to
   clear 4.5:1, which takes a combined wash alpha of ~0.26. The horizontal ramp
   therefore never drops below 0.28, and combined with the vertical layer's
   floor (0.02) the weakest point on the frame is ~0.294 => ~4.9:1. At the
   reading edge the two layers combine to ~0.43 => ~6.5:1.
   Deliberately NOT back to the original 0.66/0.74: with the copy scrim
   restored above, that would crush the film the owner wants to stay visible.
   This is close to the minimum that clears the threshold, not a safety
   blanket — so raising the grade's brightness later means re-checking here. */
#sceneRead {
  background:
    linear-gradient(90deg, rgba(5, 7, 8, 0.38) 0%, rgba(5, 7, 8, 0.32) 52%, rgba(5, 7, 8, 0.28) 100%),
    linear-gradient(180deg, rgba(5, 7, 8, 0.08) 0%, rgba(5, 7, 8, 0.02) 68%, rgba(5, 7, 8, 0.10) 100%);
}
#scene.grade-settled .world-video,
#scene.grade-settled .world-still img {
  filter: brightness(0.58) saturate(0.82) contrast(1.12);
}

.panel-inner {
  --copy-drop:
    0 1px 1px rgba(0, 0, 0, 1),
    0 4px 10px rgba(0, 0, 0, 0.98),
    0 10px 28px rgba(0, 0, 0, 0.90),
    0 0 48px rgba(0, 0, 0, 0.78);
  text-shadow: var(--copy-drop);
}
.panel-inner > h1,
.panel-inner > h2,
.panel-inner > .lede,
.panel-inner > .standfirst,
.panel-inner > .kicker,
.panel-inner .hero-top,
.panel-inner .hero-entry-title,
.panel-inner .price-note,
.panel-inner .case-card,
.panel-inner .discovery-card,
.panel-inner .services,
.panel-inner .site-foot,
.panel-inner .txt {
  text-shadow: var(--copy-drop) !important;
}

.copy-beats {
  display: flex;
  flex-direction: column;
  gap: 0.52em;
}
.copy-beats p {
  max-width: 56ch;
  margin: 0;
  color: inherit;
  font: inherit;
  line-height: inherit;
}
#intro .copy-beats p { max-width: 60ch; }
.copy-break { display: block; }
.copy-break + .copy-break { margin-top: 0.12em; }
.hero-entry-title b {
  display: block;
  margin-top: 0.28em;
}

.case-val,
.discovery-card p,
.svc-body,
.site-foot {
  color: var(--shell);
}

@media (max-width: 700px) {
  /* Mobile counterpart of the desktop wash above, and the last #sceneRead
     rule at this width. Was 0.18/0.08/0.16. Phones need the wash to hold over
     the WHOLE frame, not just the top: the scene-first pass strips the
     background off .svc / .case-card / .discovery-card, so the lower rails are
     bare text on film too, and the chapter scrolls now so any line can end up
     anywhere. Weakest stop is 0.30 => ~5.0:1 for --shell against the same
     worst-case graded pixel as the desktop rule. */
  #sceneRead {
    background:
      linear-gradient(180deg, rgba(5, 7, 8, 0.40) 0%, rgba(5, 7, 8, 0.30) 58%, rgba(5, 7, 8, 0.34) 100%);
  }
  .copy-beats { gap: 0.38em; }
  .copy-beats p { max-width: 38ch; }
  #intro .copy-beats p { max-width: 36ch; }
  .copy-break + .copy-break { margin-top: 0.08em; }

  /* Phone type floor, part two. The pass above lifted the 7.2-9px offenders;
     these are the remaining eyebrow/label styles that sat at 9.5-10.5px. They
     are uppercase mono at tight tracking over video, which is the hardest
     combination to read on a phone held at arm's length. 11px is the floor for
     the whole page now. The two !important rules override inline style
     attributes in index.html (".label" / the Direct Email tile); if those
     inline font-sizes are ever removed, these can drop the !important. */
  .hero-entry-tag,
  .case-label,
  .rag { font-size: 11px; }
  /* The footer was display:none on phones until this pass, so its 10.5px
     never mattered. It renders now — the Free resources / Writing links are
     the only route off this page on a phone. */
  .contact-side-panel .site-foot { font-size: 11px; }
  /* .chat-drawer-footer is 11px at every width now; see its base rule. */
  /* :first-child only — the Direct Email tile's second div is the address
     itself at 13.5px, and an unscoped rule here made it SMALLER. */

}

/* Final mobile cascade: these rules intentionally close the file. */
@media (max-width: 700px) {
  #sceneRead { background: linear-gradient(180deg, rgba(5,7,8,.50) 0%, rgba(5,7,8,.40) 58%, rgba(5,7,8,.46) 100%); }
  .panel-inner {
    padding-left: max(20px, env(safe-area-inset-left, 0px));
    padding-right: max(20px, env(safe-area-inset-right, 0px));
    padding-bottom: calc(88px + env(safe-area-inset-bottom, 0px));
    scroll-padding-bottom: calc(88px + env(safe-area-inset-bottom, 0px));
  }
  .standfirst, .lede { font-size: 14px; line-height: 1.48; }
  .btn { font-size: 11px; line-height: 1.25; letter-spacing: .055em; }
  .txt, .mini-link, .price-note { font-size: 12px; line-height: 1.45; }
  #proof .case-card, #services .svc {
    background: rgba(9,13,15,.96) !important;
    backdrop-filter: none !important;
    -webkit-backdrop-filter: none !important;
  }
  #proof .roi-strip strong { font-size: 19px; }
  #proof .roi-strip span { font-size: 11px; line-height: 1.35; }
  .case-summary { font-size: 13px; line-height: 1.52; }
  #services .svc-name { font-size: 14px; line-height: 1.25; }
  #services .svc-meta { font-size: 11px; line-height: 1.4; }
  #services .svc-body { font-size: 12px; line-height: 1.52; }
  .hint {
    top: max(14px, env(safe-area-inset-top, 0px));
    left: max(20px, env(safe-area-inset-left, 0px));
    right: 76px;
    width: auto;
    max-width: 30ch;
    transform: none;
    text-align: left;
  }
  .hub {
    bottom: max(8px, env(safe-area-inset-bottom, 0px));
    width: auto;
    max-width: calc(100vw - 16px);
    padding: 5px 9px;
    gap: 6px;
  }
  .hub .row { gap: 2px; }
  .hub nav { flex: 0 0 auto; overflow: visible; gap: 0; padding: 0; }
  .hub nav button {
    position: relative;
    width: 32px;
    min-width: 32px;
    min-height: 40px;
    padding: 0;
    overflow: hidden;
    border: 0;
    border-radius: 999px;
    color: transparent;
    font-size: 0;
  }
  .hub nav button::before {
    content: "";
    position: absolute;
    left: 50%;
    top: 50%;
    width: 7px;
    height: 7px;
    border-radius: 999px;
    background: var(--muted);
    transform: translate(-50%, -50%);
    transition: width .2s var(--ease), background .2s var(--ease);
  }
  .hub nav button.on { border: 0; background: transparent; color: transparent; }
  .hub nav button.on::before { width: 22px; background: var(--oxblood-light); }
  .hub .arrow-btn { width: 40px; height: 40px; }
  .overlay-open .hub, .overlay-open .chat-widget-container {
    visibility: hidden;
    opacity: 0;
    pointer-events: none;
  }
}

@media (max-width: 380px) {
  #proof .roi-strip { grid-template-columns: 1fr; }
  #proof .roi-strip div {
    display: grid;
    grid-template-columns: 84px minmax(0, 1fr);
    align-items: center;
    gap: 10px;
    padding: 10px 12px;
  }
  #proof .roi-strip span { margin-top: 0; }
}

@media (max-width: 360px) {
  h1 { font-size: 27px; }
  h2 { font-size: 24px; }
  .panel-inner {
    padding-left: max(16px, env(safe-area-inset-left, 0px));
    padding-right: max(16px, env(safe-area-inset-right, 0px));
  }
  #services .svc { grid-template-columns: 1fr; gap: 4px; }
  #services .svc-meta { text-align: left; }
  .hub nav button { width: 28px; min-width: 28px; }
}
