/* =========================================================
   AC-v2 Section: Eternal Tales chamber backdrop
   Role: persistent sticky backdrop behind the entire Eternal Tales
   content block. As the user scrolls through the chamber wrapper
   the chamber zooms out from a focal point and the night image
   crossfades into flooded.

   Iterations
   ----------
   20.04.26 round 10: identity transform on both layers.
   24.04.26 UCB Phase 1+2: chamber wraps hero + about + ET sections.
   04.05.26 PORT from AC-v3/sandbox/our-stories-logo-calibration-240426
            (sandbox v2.10): three-snapshot scroll-driven model.
            Chamber zoom and bg-y now have Top, Mid, Bot endpoints
            per breakpoint; live --current-chamber-scale and
            --chamber-bg-y are written each frame by et-chamber.js.
            Flood ramp shifted to 0.38 -> 1.00 with ease power 4.0
            so the dawn flood arrives near the end of VotF and
            holds into TEotE. Focal lives per-BP for the zoom pivot.
   ========================================================= */

/* ---------- Wrapper: isolation root for the chamber stack ----------
   position: relative so the sticky stage wrap pins relative to
   this element. isolation: isolate creates a new stacking context
   so the chamber layers cannot leak above or below the ET block. */
.et-chamber {
  position: relative;
  /* 19.05.26: explicit z-index: 1 paired with parallax-track being
     bumped from z:-1 to z:0. The prior arrangement (chamber z:auto,
     parallax-track z:-1) put TEoTE BEHIND main-level negative z,
     which meant MAIN itself was the topmost hit-target over any
     TEoTE content - silently eating clicks on the TEoTE cover (i).
     Pinning chamber at z:1 preserves the chamber-over-TEoTE
     visual stacking while letting TEoTE descendants participate in
     normal hit-testing above MAIN's z:auto baseline. */
  z-index: 1;
  isolation: isolate;
  /* 27.05.26 EOD: clip the chamber to its own bounds. The sticky
     .et-chamber__stage-wrap inside has height:100vh + margin-bottom:
     -100vh, which means it visually extends beyond .et-chamber's
     layout bottom. On iOS Safari, rubber-band overscroll past the
     contact section was exposing the chamber backdrop (the night/
     flooded image layers) below the contact card. overflow: clip
     contains the stage-wrap visually without establishing a scroll
     container, so position: sticky on the stage-wrap still resolves
     against the page scroll (not against .et-chamber). Same pattern
     as html { overflow-x: clip } in shell.css. Browser support:
     Chrome 90+, FF 81+, Safari 16+. */
  overflow: clip;

  /* Scroll timing (global across breakpoints).
     04.05.26: chamber controller rewritten to four-phase model
     (et-chamber.js). The first three vars below are no longer
     consumed by JS (windows are computed from DOM offsets) but
     are kept in place as a reference for the legacy single-progress
     model. --chamber-ease-flood-power IS still read (Phase D).
     Eased from 4.0 -> 2.5 so dawn arrives gradually across the
     full ET section length rather than dumping near the end. */
  --chamber-zoom-end:           0.50;     /* legacy, unused */
  --chamber-kf-flood-start:     0.38;     /* legacy, unused */
  --chamber-kf-flood-end:       1.00;     /* legacy, unused */
  --chamber-ease-flood-power:   2.5;

  /* Chamber framing: Top, Mid, Bot endpoints. JS lerps these into
     --current-chamber-scale and --chamber-bg-y each frame.

     Mid recalibrated 06.05.26 (logged 05.05.26 EOD) to match the OS
     sandbox framing. Sandbox rendered the chamber image at 1920px
     width with top: -240px in section coords. Production renders the
     same image cover-fit to a 1440 x 100vh stage with object-position:
     center top, then transformed by scale + translateY around the
     focal (50%, 34%). Equivalent values:
        scale = 1920 / 1440 = 1.333
        bg-y  = 11.33vh - 240px   (-138px at 100vh = 900)
     Phase B (about-mid -> os-top) animates Top -> Mid which now
     terminates at the sandbox-calibrated framing. Phase D continues
     from this Mid out to Bot for the dawn flood. */
  /* 16.05.26 (Item C): direction flipped. Was 1.15 -> 1.00 (a 15%
     zoom-OUT from Top to Mid); now 0.85 -> 1.00 (a 15% zoom-IN from
     Top to Mid) so the chamber starts SMALLER and settles into its
     resting frame as the UCB glass clears. Mirrors the previous
     magnitude. Same direction flip applied across the per-BP
     overrides below. */
  --chamber-zoom-scale-top:     0.85;
  /* Mid framing for the OS scene. bg-y-mid is an ABSOLUTE pixel
     value (not nav-relative); the chamber sits at a fixed viewport
     position, and the OS pin/breadcrumb compensate (also fixed
     viewport positions in their CSS) so they stay aligned regardless
     of fluid nav-height changes. Caoimhin's calibrated value: -140
     at the wide zone (chamber-fill-w 1920). Narrow zone uses -138
     (per-BP override below) for the smaller chamber-fill-w 1760. */
  --chamber-zoom-scale-mid:     1.00;
  --chamber-zoom-scale-bot:     1.00;
  --chamber-bg-y-top:        -90.00px;
  --chamber-bg-y-mid:        -140.00px;
  --chamber-bg-y-bot:        0.00px;

  /* Focal point for the zoom transform-origin (single per-BP). */
  --chamber-focal-x:            50.0%;
  --chamber-focal-y:            34.0%;

  /* Start-black veil retained from earlier rounds. */
  --chamber-start-black-op:     0.40;
  --chamber-start-black-end:    0.30;

  /* Live consumer vars; init to Bot endpoint so the chamber
     renders before et-chamber.js boots. */
  --current-chamber-scale: var(--chamber-zoom-scale-bot);
  --chamber-bg-y:          var(--chamber-bg-y-bot);

  /* ---------- Phase E: chamber-out (18.05.26, revised) ----------
     Drives the chamber dissolve during VotF's tail. et-chamber.js
     computes phaseE.end = VotF.bottom - navHeight - 30px (the user-
     specified "30px before VotF bottom crosses the nav") and
     phaseE.start = phaseE.end - 50vh, so the dissolve plays across
     the last 50vh of VotF reading. No zoom, no held beat: just a
     clean opacity fade. The stage-wrap consumes --chamber-out via
     opacity below; transform is preserved but inert (zoom factor 0).
     TEoTE is pulled up via negative margin to overlap this range,
     sticky-pinned behind the chamber from phaseE.start onwards. */
  --chamber-out:             0;
  --chamber-out-hold-frac:   0;        /* held beat removed 18.05.26 */
  --chamber-out-zoom:        0;        /* zoom removed 18.05.26 */

  /* Chamber stage dimensions (per BP).
     04.05.26 (FINAL revert): chamber stays at FIXED sandbox pixel
     size per BP. Viewport-width changes do NOT scale the chamber
     image - only the scroll-driven parallax does. This guarantees
     the calibrator (which renders at fixed BP size) matches
     production exactly. Trade-off: at browser widths different
     from the BP nominal, the chamber either overflows (narrower
     browser, clipped by overflow:hidden) or has page background
     bleed on the sides (wider browser - addressed by black
     stage-wrap background). --os-scale held at 1 so all
     OS positions are sandbox-px exactly. */
  --chamber-stage-w: 1440px;
  --chamber-stage-h:  900px;
  /* 06.05.26 (logged 05.05.26 EOD): chamber-fill-w widened from
     --chamber-stage-w (1440) to max(1920px, 100vw). At 1440-1920
     viewports the stage is 1920 wide centred (clipped by stage-wrap
     overflow:hidden); at >1920 viewports the stage fills viewport.
     Eliminates the 14px black bars Caoimhin saw at 1468 and gives
     us a 1920-wide image to render so Mid framing matches the
     sandbox arithmetic exactly (sandbox showed image at width 1920
     with top: -240). */
  --chamber-fill-w:   max(1920px, 100vw);
  --os-scale:         1;
}

/* ---------- Sticky stage wrapper ---------- */
.et-chamber__stage-wrap {
  /* 04.05.26 rework: stage-wrap is a full-viewport sticky
     container. The actual chamber stage (.et-chamber__stage)
     is absolutely positioned INSIDE this wrapper at fixed
     sandbox dimensions, centred horizontally. Black background
     so no navy page bg shows above or below the chamber image. */
  /* 06.07.26 (late v14): opacity of the hero darkening scrim
     (.et-chamber__stage-wrap::before, inline styles). Seeds 0.3 (the
     opening is 30% darkened); the hero-dim script at the foot of
     <body> fades it to 0 by the time the flooded-chamber crossfade
     begins (An Oath on Stone's top). The pseudo-element inherits
     this custom property. */
  --hero-dim: 0.3;
  position: sticky;
  top: 0;
  width: 100%;
  height: 100vh;
  margin-bottom: -100vh;
  z-index: 0;
  pointer-events: none;
  overflow: hidden;
  background: #000;
  /* 18.05.26 Phase E: chamber-out zoom-in fade. Applied as the whole-
     unit transform on the sticky wrapper so the inner stage + layers +
     glass + start-black all zoom and fade together. transform-origin
     aligned to the chamber's focal so the dissolve pivots through the
     same point as the existing chamber framing zoom. */
  transform: scale(calc(1 + var(--chamber-out, 0) * var(--chamber-out-zoom, 0.35)));
  transform-origin: var(--chamber-focal-x) var(--chamber-focal-y);
  opacity: calc(1 - var(--chamber-out, 0));
}

/* ---------- Stage ---------- */
.et-chamber__stage {
  /* 04.05.26 (revised twice): width = max(sandbox-w, 100vw) so the
     chamber fills viewport when browser is wider than the sandbox
     BP width. Height stays 100vh. Centred horizontally; image
     cover-fits the stage so it scales up smoothly without leaving
     black borders at wide viewports. --os-scale (defined on
     .et-chamber) tracks the upscale factor so OS scene positions
     scale in lockstep with the chamber. */
  position: absolute;
  top: 0;
  left: 50%;
  transform: translateX(-50%);
  width:  var(--chamber-fill-w, 1440px);
  height: 100vh;
  background: #000;
  overflow: hidden;
  container-type: inline-size;
  container-name: chamber-stage;
}

/* ---------- Image layers (night + flooded) ----------
   06.05.26 (logged 05.05.26 EOD): rewritten from object-fit: cover at
   IMG box height 100% to width: 100% height: auto. The cover-fit model
   locked the IMG box to stage height (100vh), so when bg-y translated
   the IMG up by 240px the box bottom dropped to viewport-y 720 leaving
   240px of black at the bottom of the chamber. Switching to
   width-100/height-auto renders the image at its natural aspect (1920w
   x 1798h) which extends BELOW the stage; the stage's overflow: hidden
   clips it cleanly with no mid-image black bar. This matches how the
   sandbox rendered the chamber (width 1920, top: -240). */
.et-chamber__layer {
  position: absolute;
  top: 0;
  left: 0;
  width: 100%;
  height: auto;
  opacity: 0;
  will-change: opacity, transform, filter;
  user-select: none;
  -webkit-user-drag: none;
  transform-origin: var(--chamber-focal-x) var(--chamber-focal-y);
  transform: translateY(var(--chamber-bg-y)) scale(var(--current-chamber-scale));
  /* 06.05.26: blur moved here from .et-chamber__glass backdrop-filter.
     filter: blur is applied directly to the image bitmap with no
     compositing-pipeline / backdrop-sampling edge cases. JS writes
     --chamber-blur on the wrapper across Phase B (24px -> 0). */
  filter: blur(var(--chamber-blur, 0px));
}

.et-chamber__layer--night   { opacity: 1; }   /* initial state */
.et-chamber__layer--flooded { opacity: 0; }   /* JS crossfades in */

/* ---------- Start-black veil ----------
   Solid black overlay above both image layers, below any content.
   Opacity driven by JS from the start-black window. */
.et-chamber__start-black {
  position: absolute;
  inset: 0;
  background: #000;
  opacity: 0;
  pointer-events: none;
  will-change: opacity;
  z-index: 3;
}

/* ---------- Content sections inside the chamber ----------
   All chamber sections render above the sticky stage (position +
   z-index). 14.05.26 light-studio rebuild: only the dark brand
   sections stay transparent so the chamber shows through; the light
   studio sections (hero, About) are opaque and decoupled, so
   background: transparent is scoped to [data-theme="dark"] only.
   This rule previously applied transparent to every chamber section
   and the light hero + About beat it with `!important`; that was
   removed in the rebuild, so the scoping moved here instead. */
.et-chamber > .section {
  position: relative;
  z-index: 1;
}
.et-chamber > .section[data-theme="dark"] {
  background: transparent;
}

/* (18.05.26 revised: .et-chamber__outro scroll buffer removed.
   Phase E now plays across the last 50vh of VotF and ends 30px
   before VotF's bottom crosses the nav. JS computes the window
   from VotF's geometry; no outro DOM element needed.) */

/* =========================================================
   Per-breakpoint chamber framing.
   Source: AC-v3/sandbox export 04.05.26.
   ========================================================= */
/* WIDE zone (sandbox 1920-441 chamber-w 1920) covers production
   default + 991 + 576. Default already inherits chamber-fill-w
   max(1920px, 100vw). Per-BP rules below set chamber-fill-w to
   1920px explicitly so the stage stays full-1920 even as the
   viewport shrinks below 1920. Top values left at the legacy
   cover-fit-era numbers; they will look different until Top is
   recalibrated against the new layer model, but they don't break
   the OS scene because Phase B has already fully landed at Mid by
   the time the user reaches OS. */

@media (max-width: 991px) {
  .et-chamber {
    --chamber-stage-w: 991px;
    --chamber-stage-h: 700px;
    --chamber-fill-w:  1920px;
    --chamber-zoom-scale-top: 0.82;
    --chamber-zoom-scale-mid: 1.00;
    --chamber-zoom-scale-bot: 1.00;
    --chamber-bg-y-top:        -28.00px;
    --chamber-bg-y-mid:        -140.00px;   /* absolute; matches default */
    --chamber-bg-y-bot:        0.00px;
    --chamber-focal-x:        50.0%;
    --chamber-focal-y:        34.0%;
  }
}

@media (max-width: 576px) {
  .et-chamber {
    --chamber-stage-w: 576px;
    --chamber-stage-h: 900px;
    --chamber-fill-w:  1920px;
    --chamber-zoom-scale-top: 0.80;
    --chamber-zoom-scale-mid: 1.00;
    --chamber-zoom-scale-bot: 1.00;
    --chamber-bg-y-top:        85.50px;
    --chamber-bg-y-mid:        -140.00px;   /* absolute; matches default */
    --chamber-bg-y-bot:        0.00px;
    --chamber-focal-x:        50.0%;
    --chamber-focal-y:        34.0%;
  }
}

/* NARROW zone (sandbox 440-320 chamber-w 1760) covers production
   400 + 320. */
@media (max-width: 400px) {
  .et-chamber {
    --chamber-stage-w: 400px;
    --chamber-stage-h: 850px;
    --chamber-fill-w:  1760px;
    --chamber-zoom-scale-top: 0.80;
    --chamber-zoom-scale-mid: 1.00;
    --chamber-zoom-scale-bot: 1.00;
    --chamber-bg-y-top:        55.25px;
    --chamber-bg-y-mid:        -148.00px;   /* lifted 10 at 400 BP per Caoimhin (revised from 30) */
    --chamber-bg-y-bot:        0.00px;
    --chamber-focal-x:        50.0%;
    --chamber-focal-y:        34.0%;
  }
}

@media (max-width: 320px) {
  .et-chamber {
    --chamber-stage-w: 320px;
    --chamber-stage-h: 568px;
    --chamber-fill-w:  1760px;
    --chamber-zoom-scale-top: 0.78;
    --chamber-zoom-scale-mid: 1.00;
    --chamber-zoom-scale-bot: 1.00;
    --chamber-bg-y-top:        28.40px;
    --chamber-bg-y-mid:        -148.00px;   /* lifted 10 at 320 BP per Caoimhin (revised from 30) */
    --chamber-bg-y-bot:        0.00px;
    --chamber-focal-x:        50.0%;
    --chamber-focal-y:        34.0%;
  }
}

/* ---------- Reduced motion ----------
   Skip the night-hold beat: jump straight to the flooded state so
   reduce-motion users see the section in its final lit form. JS
   controller also clamps progress to 1 in reduced-motion. */
@media (prefers-reduced-motion: reduce) {
  .et-chamber__layer--night        { opacity: 0; }
  .et-chamber__layer--flooded      { opacity: 1; }
  .et-chamber__start-black         { opacity: 0; }
  .et-chamber__layer {
    transform: scale(var(--chamber-zoom-scale-bot));
  }
}

/* ---------- UCB Phase 2 sticky glass layer (24.04.26) ----------
   Replaces the per-section ::before glass previously drawn on
   .section--landing and .section--about. The glass is a single
   absolutely-positioned sibling inside .et-chamber__stage so it
   shares the chamber stage's pinned sticky context. Z-index 4
   sits above .et-chamber__start-black (z-index 3) but still
   inside the stage-wrap's stacking context, so section content
   (z-index 1) renders above the glass. */
.et-chamber__glass {
  /* 06.05.26 (definitive): backdrop-filter blur replaced with
     filter: blur on the chamber layer itself (see .et-chamber__layer
     above). The glass is now just a tinted overlay that fades. This
     completely sidesteps Chrome's backdrop-filter compositing edge
     cases (the "directional light leak" Caoimhin was seeing was a
     Chrome backdrop-filter sampling artifact, not anything in our
     CSS). The frosted look is preserved because the chamber image
     itself blurs; the glass dims it; the result is visually equivalent
     to backdrop-filter but rendered through a clean pipeline. */
  position: absolute;
  inset: 0;
  z-index: 4;
  pointer-events: none;
  background: rgba(15, 20, 25, 0.35);
  opacity: 1;
  will-change: opacity;
}

@media (prefers-reduced-motion: reduce) {
  .et-chamber__glass { opacity: 0; }
}

/* =========================================================
   Logo pin (lockup + tagline), ported 08.05.26 from
   AC-v3/sandbox/chamber-intro-080526.html (v1-LOCKED).

   Architecture:
     - Single shared lockup + tagline.
     - DOM lives INSIDE .et-chamber, immediately after
       .et-chamber__stage-wrap. Required for stacking; the
       chamber's `isolation: isolate` encapsulates everything
       inside it; placing the pin outside meant any positive
       z-index would always paint on top of the cards.
     - Within the chamber stacking context: stage-wrap = z 0
       (earlier in DOM, paints first → logo paints over the
       chamber image), .logo-pin = z 0 (later in DOM, paints
       above stage-wrap), .et-chamber > .section = z 1 (paints
       above the pin → cards cover the lockup as the user
       scrolls past the orthostat zone).
     - The cards-above-pin layering is also a hard safety mask:
       even if the opacity fade is missed (reduced motion, fast
       scroll past the trigger), the rising card opaquely covers
       the lockup + tagline.

   JS (et-chamber.js) sets per-frame:
     --logo-opacity, --chevron-opacity      (orthostat window, split
                                             09.05.26: logo + tagline
                                             share --logo-opacity, the
                                             chevron has its own var)
     --track-chamber-y                       (phase-B-only chamber tracking)
     --logo-drift-y                          (-5px linear drift across dwell)

   Per-BP --logo-x / --logo-y / --logo-w live in CSS only; JS
   does not touch them. Values carried over from the locked
   `.our-stories__lockup` calibration (production chamber framing
   stays untouched per Option A, so the production-calibrated
   lockup positions transfer with one offset adjustment for the
   new pin geometry: the old pin had `top: 100px` + `padding-top: 70px`
   = 170px baseline before --os-anchor-y; the new .logo-pin has
   `top: 0` + no padding, so --logo-y = 170 + previous --os-anchor-y).
   Caoimhín to fine-tune per BP after this port lands.
   ========================================================= */
.logo-pin {
  position: fixed;
  top: 0;
  left: 0;
  right: 0;
  height: 100vh;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: flex-start;
  pointer-events: none;
  z-index: 0;

  /* Wide-zone defaults (production default + 991 + 576 inherit
     unless overridden below). Derived from the previous
     .our-stories__lockup calibration: anchor-y 38 + pin offset
     170 = 208; lockup-w 177; lockup-x bumped 5 → 10 on 09.05.26. */
  --logo-x: 10px;
  --logo-y: 208px;
  --logo-w: 177px;
}
/* 19.05.26: flipped auto -> none. The logo-pin parent is fixed-
   positioned and covers the viewport with pointer-events: none. The
   prior `auto` rule re-enabled clicks on direct children (logo IMG,
   tagline P, scroll-cue SPAN) - but NONE of those are interactive,
   so the rule served no purpose AND it caused a real bug: while
   logo-pin children sit at opacity 0 (e.g. during TEoTE), the IMG
   still occupies its bounding box at the viewport centre and silently
   intercepts clicks on anything positioned below it - notably the
   AOoS / TEoTE (i) cover-trigger. */
.logo-pin > * { pointer-events: none; }

.logo-pin__logo {
  display: block;
  width: var(--logo-w, 177px);
  height: auto;
  max-width: none;
  /* X / Y / W are read from the pin's CSS variables. They SNAP
     into place. No transition on width / margin-top / margin-left.
     The earlier sandbox iteration had 600ms transitions on these
     three properties and produced a "genie" morph on first reveal:
     the lockup grew + slid from its CSS fallback box to the
     calibrated box while opacity was fading in. With snap-not-
     transition, the lockup is already at its final box behind an
     invisible logo before opacity starts to rise. */
  margin-top: var(--logo-y, 208px);
  margin-left: var(--logo-x, 0px);
  transform-origin: 50% 0%;
  /* Per-frame parallax (both vars written by et-chamber.js):
       --track-chamber-y : phase-B-only chamber tracking. Phase D
                           holds tracking at 0 so the chamber's
                           Mid → Bot pan can't pull the lockup up.
       --logo-drift-y    : a deliberate -5px linear drift across
                           the orthostat dwell. */
  transform: translateY(calc(var(--track-chamber-y, 0px) + var(--logo-drift-y, 0px)));
  /* 11.05.26: tight contact drop-shadow stacked on top of the soft
     14px glow. Adds definition against the chamber's mid-tones
     without erasing the existing soft halo. */
  filter: drop-shadow(0 2px 14px rgba(0, 0, 0, 0.6)) drop-shadow(0 1px 2px rgba(0, 0, 0, 0.7));
  opacity: var(--logo-opacity, 0);
  /* 09.05.26: asymmetric transition. Default = fast 120ms ease for
     EXIT (when the rising card meets the logo top; should feel
     immediate). The .is-arriving override below extends the transition
     to 500ms ease-in for ENTRY (gradual reveal). The class is toggled
     by et-chamber.js (renderLogoVisibility) on direction change. */
  transition: opacity 120ms ease;
  user-select: none;
  -webkit-user-drag: none;
}
.logo-pin.is-arriving .logo-pin__logo {
  transition: opacity 500ms ease-in;
}

.logo-pin__tagline {
  margin: 28px 0 0;
  font-family: var(--font-sans);
  font-size: 16px;
  font-weight: 700;
  color: var(--primary-text);
  letter-spacing: 0.005em;
  /* 11.05.26: tight contact text-shadow stacked on top of the soft
     6px shadow, matching the logo's stacked drop-shadow treatment. */
  text-shadow: 0 1px 6px rgba(0, 0, 0, 0.85), 0 1px 2px rgba(0, 0, 0, 0.7);
  /* 09.05.26: tagline reads --logo-opacity so it travels with the
     logo. Asymmetric transition same as the logo: fast on exit, slow
     ease-in on entry via .logo-pin.is-arriving. */
  opacity: var(--logo-opacity, 0);
  /* 11.05.26: track the chamber alongside the logo during Phase B
     so the logo (translated by --track-chamber-y up to +50px DOWN
     during the bgYTop → bgYMid lerp) doesn't push into the tagline.
     --logo-drift-y is intentionally NOT applied here; it's a 5px
     dwell-only parallax against the tagline that should remain. */
  transform: translateY(var(--track-chamber-y, 0px));
  transition: opacity 120ms ease;
}
.logo-pin.is-arriving .logo-pin__tagline {
  transition: opacity 500ms ease-in;
}

/* ---------- Scroll cue ----------
   Added 09.05.26. A minimal chevron sitting ~50px beneath the tagline
   during the orthostat dwell, signalling "more below". Opacity is
   tied to --logo-opacity so it appears + fades with the lockup with
   zero extra JS wiring. The bob animation pauses under reduced motion.

   09.05.26 (revised): repositioned from viewport-bottom-anchored
   absolute to natural flex-flow (third child of .logo-pin), so it
   sits 50px below the tagline rather than at the foot of the viewport. */
.logo-pin__scroll-cue {
  margin: 60px 0 0;   /* 09.05.26: 50 → 60 (nudged 10px down). */
  width: 24px;
  height: 24px;
  display: flex;
  align-items: center;
  justify-content: center;
  /* 09.05.26: now reads --chevron-opacity (its own variable) so it
     disappears at the chevron-trigger (card bottom 100px below
     chevron top), independently of the logo + tagline which leave
     later when the card top reaches the logo top. */
  opacity: var(--chevron-opacity, 0);
  /* 11.05.26: track the chamber alongside the logo + tagline during
     Phase B so the whole lockup moves as one unit and the chevron
     stays at its calibrated gap below the tagline regardless of
     where in Phase B the user landed. */
  transform: translateY(var(--track-chamber-y, 0px));
  /* Quick 100ms snap kept; the chevron needs to be gone before the
     card's bottom edge sweeps past it. */
  transition: opacity 100ms ease;
  pointer-events: none;
}

.logo-pin__scroll-cue-chevron {
  display: block;
  width: 14px;
  height: 14px;
  border-right: 2px solid rgba(255, 255, 255, 0.85);
  border-bottom: 2px solid rgba(255, 255, 255, 0.85);
  transform: rotate(45deg);
  animation: logoPinScrollCueBob 1.8s ease-in-out infinite;
  filter: drop-shadow(0 1px 4px rgba(0, 0, 0, 0.7));
}

@keyframes logoPinScrollCueBob {
  0%, 100% { transform: rotate(45deg) translate(0, 0); }
  50%      { transform: rotate(45deg) translate(3px, 3px); }
}

@media (prefers-reduced-motion: reduce) {
  .logo-pin__scroll-cue-chevron { animation: none; }
}

/* ---------- Per-BP logo positions ----------
   Wide values inherit from .logo-pin defaults above. Narrow zones
   override --logo-y and --logo-w to match the existing
   .our-stories__lockup per-BP calibration (anchor-y 0, lockup-w 164
   at 400 BP). These are first-pass values; Caoimhín to verify the
   painted L lands on the carved L of the orthostat at each BP. */
@media (max-width: 576px) {
  .logo-pin__tagline {
    font-size: 15px;
    /* 11.05.26: tighter gap below the logo at ≤576 so the chevron
       doesn't clip out the bottom of short portrait viewports. Default
       was 28px (.logo-pin__tagline base rule). */
    margin-top: 20px;
  }
  .logo-pin__scroll-cue {
    /* 11.05.26: tighter gap below the tagline at ≤576. Default was
       60px (.logo-pin__scroll-cue base rule). */
    margin-top: 20px;
  }
}

@media (max-width: 400px) {
  .logo-pin {
    /* Old anchor-y 0 → new --logo-y 170 (pin offset only). */
    --logo-y: 170px;
    --logo-w: 164px;
    --logo-x: 10px;
  }
}

@media (prefers-reduced-motion: reduce) {
  .logo-pin__logo,
  .logo-pin__tagline {
    transition: none;
  }
}

