/* Hero section. Requires base.css. */

.hero {
  /* Chevron geometry. One source PNG is drawn four times at different
     left offsets to fake four receding layers; the nav labels below are
     anchored to these same values, so moving an arrow moves its label.
     All four values are placeholders pending a render-and-measure pass,
     same as every arrow position on this page always has been.

     History of hand-tuning since the original pre-events values
     (-11.8%/-1%/8%, three chevrons only), each step its own request, most
     recent last — see CLAUDE.md's "The events choreography" for the full
     narrative and the --shift-* derivation this feeds on each step:
       1. back/mid/front +12% right (-11.8/-1/8 -> 0.2/11/20), opening room
          for the new --arrow-quad chevron (added at -14%).
       2. back 0.2% left (0.2 -> 0): widen back-mid gap 10.8% -> 11%.
       3. quad 1% right (-14 -> -13): narrow quad-back gap 14% -> 13%.
       4. back/mid/front 2% left together (0/11/20 -> -2/9/18): quad
          untouched, so quad-back widened 13% -> 11%; back-mid/mid-front
          held at 11%/9%.
       5. quad 1% right (-13 -> -12): narrow quad-back gap 11% -> 10%.
       6. back 0.5% right (-2 -> -1.5): narrow back-mid gap 11% -> 10.5%.
       7. quad 0.5% left (-12 -> -12.5): widen quad-back gap 10% -> 11%.
       8. back 0.5% right (-1.5 -> -1): narrow back-mid gap 10.5% -> 10%;
          widens quad-back gap 11% -> 11.5% (quad untouched this step).
       9. quad 0.5% right (-12.5 -> -12): narrow quad-back gap 11.5% -> 11%
          (back/mid/front untouched this step).
     Current: --arrow-back -1%, --arrow-mid 9%, --arrow-front 18%,
     --arrow-quad -12%. Gaps: quad-back 11%, back-mid 10%, mid-front 9%. */
  --arrow-back: -1%;
  --arrow-mid: 9%;
  --arrow-front: 18%;
  --arrow-quad: -12%;

  /* Horizontal distance from an arrow's `left` to its label, and the shared
     baseline all four labels sit on (nudged up from center to meet the
     chevron tips). In `cqh`, not `%`: the chevron's rendered width (and so
     its tip's offset from its own left edge) is driven by the stage's
     HEIGHT (`.arrow { height: 130%; width: auto; }`), not its width, so a
     width-relative `%` here only ever lined up at the exact 4032:2039
     design ratio and silently drifted at any other window shape (this is
     what used to force the aspect-lock band below to stay narrow — see its
     own comment). `cqh` ties the label to the same axis that actually
     draws the chevron, so it now lands on the true tip at any aspect.
     Values are the old `%` figures × 4032/2039 (1.97744, the ratio the two
     bases coincide at): 21.5% -> 42.515cqh, 21.4% -> 42.317cqh. The old
     `21.4`/`21.5` spread (measurement noise across separate render passes,
     not a real geometric difference) is preserved exactly, including
     --tip-offset-mid == --tip-offset-back, which .hero-nav__link--services'
     own comment below still depends on. --tip-offset-quad starts from the
     other three's converged value; needs its own render-and-measure pass
     like they did. */
  --tip-offset-back: 42.515cqh;
  --tip-offset-mid: 42.515cqh;
  --tip-offset-front: 42.317cqh;
  --tip-offset-quad: 42.515cqh;
  --nav-baseline: calc(37% - 1.2cqh);

  /* Corner "left-behind" nav stack (see `.corner-nav__link` further down).
     `vw`/`vh`, not `cqw`/`cqh` — same reason as `.logo-home`'s own fold
     rule below: `setSection` toggles every `is-*`/`nav-left-*` class in
     one synchronous loop, and `cqw`/`cqh`-based geometry was confirmed to
     misresolve for classes toggled later in that same tick. Right edge
     aligned flush with the folded logo's own *visible* right edge, on
     request, 2026-09-16. **Not** `.logo-home`'s bounding-box
     `getBoundingClientRect().right` (1572.19px at 1600x900, 1.74vw in) —
     tried first and reported still overshooting, because
     `logo_main_transparent_bg.webp`'s artwork sits inset within its own
     canvas (the same padding CLAUDE.md's "Asset naming convention"
     already documents: ~11.6% empty margin on each horizontal edge).
     Confirmed by scanning the rendered screenshot pixel-by-pixel for the
     true rightmost non-background pixel of the mark: x=1550 at 1600x900
     (vs. the nav text's own x=1570 under the old 1.74vw value — a real
     ~20px overshoot past the logo, not a rounding error). 1600-1550 =
     50px = 3.125vw. */
  --corner-nav-right: 3.125vw;
  /* First stack item (`about`) horizontally aligned with the main
     `.hero-nav__link` row's own baseline, on request, 2026-09-16.
     `--nav-baseline` is `calc(37% - 1.2cqh)`; at any in-band aspect
     (see "The stage" in CLAUDE.md) `.hero` fills the viewport exactly,
     so `1cqh` there is exactly `1vh` and this vh literal lands on the
     same line as `--nav-baseline` — the same vh/cqh-equivalence this
     file's corner-logo fold rule already relies on. Outside the band
     it drifts by the same accepted letterbox margin every other
     vw/vh-positioned corner element already carries. */
  --corner-nav-top: 35.8vh;
  /* 6.6vh, on request, 2026-09-16 — widened from the original 4.6vh (one
     `--size-nav` line plus a small gap) for clearer separation between
     stacked items. */
  --corner-nav-gap: 6.6vh;

  /* Fourth chevron. Not part of the hero — it is parked off-stage here and
     arrives from the right in every section state, so it lives with the other
     three rather than in one section's stylesheet.

     Its apex renders at --arrow-far + 20.3% of the stage width, the same tip
     offset the other three have. Pulled 0.89% left of the 74.3% the about
     reference puts it at: at 74.3% the band's lower edge cut 13.59% across the
     gallery tile's bottom instead of the reference's 8.07%, deep enough to eat
     the tile's left gutter and leave the photo's corner on bare ground. The
     gallery could not move right to meet it — its right edge is already at
     97.52%, under the `next` arrow — so the chevron came left instead.

     Kept as a bare number so it can be read as `1%` for `left` and as `1vw`
     inside a clip-path, where a percentage would resolve against the clipped
     element's own box instead of the stage. about.css derives both of its cut
     edges from it. --arrow-far-tip records where the apex lands; nothing reads
     it, it is here so the number is written down beside the offset it comes
     from. */
  --arrow-far-n: 53.11;
  --arrow-far: calc(var(--arrow-far-n) * 1%);
  --arrow-far-tip: 73.47%;

  /* Each section slides its own chevron onto the one behind it: front onto
     mid for `about`, mid onto back for `services`, and now back onto quad
     for `products` — see .arrow--back and .hero.is-products. The stage is
     full-bleed, so 1vw is 1% of it; see .arrow--front for why this cannot
     be a percentage. `events` continues the chain one lane further still:
     quad has no further chevron to land on, so it travels toward the open
     ground instead — see .arrow--quad and .hero.is-events. `contact` owns
     no lane and no chevron of its own; it plays that last leg in reverse,
     un-shifting quad back to its 0vw rest value below rather than setting
     one, which sends the quad chevron (and the `events` label riding it)
     sliding home again. Back, having already done its own job for
     `products`, stays invisible (not reset) from `events` onward — see
     events.css/contact.css's own no-theatre-exit rules for it. */
  --shift-front: 0cqw;
  --shift-mid: 0cqw;
  --shift-back: 0cqw;
  --shift-quad: 0cqw;

  /* Quad's own travel into `events` runs 100ms longer than the rest of the
     shared curtain choreography (--ease-move, base.css, 700ms) — on
     request, 2026-09-14, once the endpoint above was tuned to the user's
     liking. Deliberately a separate token, not a change to --ease-move
     itself: that variable also times front/mid/back's own travel, the
     corner-logo fold and .arrow--far's arrival, all of which are meant to
     land together — lengthening it globally would pull every other layer's
     timing along with quad's instead of just this one chevron's. Used by
     both .arrow--quad and .hero-nav__link--events (the label riding it),
     so the two stay in sync with each other, just out of sync with the
     rest of the chain during this one transition. Same easing curve as
     --ease-move, only the duration differs. */
  --ease-move-quad: 800ms cubic-bezier(0.22, 1, 0.36, 1);

  position: relative;
  /* Fills the viewport exactly (no bars) for any window whose aspect ratio
     falls between --stage-min-aspect and --stage-max-aspect. Only outside
     that band does .hero clamp to the nearer bound and letterbox/pillarbox
     the remainder in body's own matching background, rather than
     stretching arbitrarily far. container-type/-name let every stage-scoped
     vw/vh/dvh value below (and throughout the other section stylesheets) be
     expressed as cqw/cqh instead — a percentage of THIS box's actual
     rendered size, whatever that is, so it never disagrees with the `%`
     values the same elements already use.

     The nav-label tip-offsets (--tip-offset-back/-mid/-front/-quad, above)
     used to be a `%` — width-relative — even though the chevron art's own
     rendered width is driven by the box's HEIGHT (`.arrow { height: 130%;
     width: auto; }`), so they only lined up with the chevron's true tip at
     the exact 4032:2039 design ratio and drifted at any other window shape.
     That was this band's original reason for existing at all: bounding how
     far a real window could sit from the design ratio before the drift
     became visible. Fixed properly now — the tip-offsets are `cqh` (see
     their own comment above), tied to the same axis that actually draws the
     chevron, so they land on the true tip at ANY aspect ratio, not just one
     inside this band. (about.css's own chevron clip-path cuts were already
     doing the equivalent right thing — a width-relative base position plus
     a height-relative additive term for the slope — so they were never
     actually part of this problem despite an old comment nearby implying
     otherwise.)

     With the alignment concern gone, this band's only remaining job is
     avoiding letterbox bars on real windows. History: originally 1.5-2.2
     (wide enough to cover any real laptop/desktop shape bar-free). Narrowed
     to 1.85-2.2 specifically to bound the tip-offset drift above, after a
     MacBook screenshot (browser content area ~1.81:1 once the fixed-height
     browser chrome is subtracted from its ~1.54:1 panel) showed labels
     visibly detached from their chevron tips at that width — the user
     accepted letterbox bars on more windows (including a plain fullscreen
     16:9 desktop browser, ~1.78:1) in exchange for correct alignment near
     the design ratio. Reopened to 1.7 now that the drift itself is fixed by
     the tip-offset change above, not by this band: 1.7 comfortably covers
     both the MacBook's ~1.81:1 content area and a plain 16:9 window
     (~1.78:1) with no bars, while stopping short of reverting all the way
     to the original 1.5 until the 5-label nav row and the services/
     products/events bento grids (several reworked since 1.5-2.2 was last
     verified) are screenshot-checked at that width. Narrow this again if a
     future report shows bars are still visible near 1.7; widen it further
     towards 1.5 once the nav/grid layouts are confirmed clean there. */
  --stage-min-aspect: 1.7;
  --stage-max-aspect: 2.2;
  width: min(100vw, 100dvh * var(--stage-max-aspect));
  height: min(100dvh, 100vw / var(--stage-min-aspect));
  container-type: size;
  container-name: stage;
  overflow: hidden;
  background: var(--blue-deep);
}

/* ---------- Background art ---------- */

.arrow {
  position: absolute;
  top: 50%;
  height: 130%;
  width: auto;
  transform: translateY(-50%);
  pointer-events: none;
  user-select: none;
}

/* The back chevron travels for `products`: it slides left until it sits on
   the quad chevron, carrying its own label the same distance — see
   `.hero.is-products` below for the shift value. Its label rides the same
   `vw` translate for the same reason `.arrow--front`'s does. Unlike
   front/mid, back's own base rule did not always need `opacity` in its
   transition — before quad existed, back was the outermost link and never
   had a reveal to ease. Now `events`/`contact` both hide it (see their own
   no-theatre-exit rules), so it needs the same reveal-easing fix `.arrow--
   mid` already carries: opacity here lets it fade back in scrolling
   backward out of events/contact into products, instead of popping to full
   opacity — see the gotchas section of CLAUDE.md for the original version
   of this bug, fixed once already for mid. */
.arrow--back {
  left: var(--arrow-back);
  z-index: 1;
  transform: translate(var(--shift-back, 0), -50%);
  /* opacity's easing here is only half the reveal fix — see the shared
     --shift-front/--shift-mid/--shift-back comment above .hero.is-about
     for the other half. Without that pin, --shift-back also snapped back
     to its base 0cqw the instant events/contact hid this chevron, so
     reappearing scrolling backward into `products` animated both opacity
     AND transform at once — a phantom slide, not a clean fade. Pinning the
     shift removed the transform difference at the source, so this opacity
     easing is what's actually left to see. */
  transition: transform var(--ease-move), opacity var(--ease-move);
}

/* The quad chevron travels for `events`, taking over the "outermost link,
   travels toward open ground" role back used to solely own: it slides left
   toward `contact`'s own literal label position, since there is no further
   chevron beyond it — see `.hero.is-events` below. It is never hidden (no
   section fades it out the way front/mid/back eventually are), so unlike
   them it carries no `opacity` in its transition list — nothing to ease. */
.arrow--quad {
  left: var(--arrow-quad);
  z-index: 0;
  transform: translate(var(--shift-quad, 0), -50%);
  transition: transform var(--ease-move-quad);
}

/* The mid chevron travels for `services`: it slides left until it sits on
   the back chevron, carrying its own label the same distance — see
   .arrow--front just below for why both use a `vw` `translate` rather than
   `left`. */
.arrow--mid {
  left: var(--arrow-mid);
  z-index: 2;
  transform: translate(var(--shift-mid, 0), -50%);
  /* opacity is here so mid can *ease* into view arriving at services/about
     from products/contact (where it's hidden) — same reason .arrow--front
     carries opacity in its own base transition, and safe for the same
     reason: a single image with nothing else riding its transition, so it
     doesn't trip the antialiasing gotcha documented further down (that one
     was about *four* nav labels sharing one promoted layer). Confirmed by a
     hero/about MD5 diff after this change: byte-identical to the recorded
     baselines — a red herring while chasing this down turned out to be an
     unrelated headless-capture profile difference, not this rule. Without
     opacity here, the reveal fell back to an un-eased default and mid
     popped to full opacity instead of fading, entering services/about
     backward from products/contact. products.css/contact.css each add
     `transition: none` to keep their own instant "no-theatre" vanish
     unaffected by this. Opacity easing is only half of a clean reveal,
     though — see the shared --shift-front/--shift-mid/--shift-back comment
     above .hero.is-about: without also pinning --shift-mid while hidden,
     it fell back to 0cqw the instant products/contact hid this chevron, so
     the reveal animated `transform` alongside `opacity` too — a phantom
     slide, not a plain fade. That pin is what makes this opacity easing
     the whole story now. */
  transition: transform var(--ease-move), opacity var(--ease-move);
}

/* The front chevron travels for `about`: entering that state it slides left
   until it sits on the mid chevron. Its label rides the same displacement
   below, which is why both are `translate`, not `left` — a percentage `left`
   resolves against the stage, a percentage `translate` against the element's
   own width, and only a `vw` translate moves an arrow and its label by the
   same distance.

   It does not travel for `services` — only the mid chevron does — but it
   still has to clear out of the stage's open middle, so it fades instead:
   see `.is-services .arrow--front` below. Only this one element carries
   `opacity` in its transition list, not the shared `.hero-nav__link` rule
   nav labels use, so it does not trip the antialiasing gotcha documented
   further down (that was about *four* labels sharing one promoted layer;
   this is a single image with nothing else riding its transition). */
.arrow--front {
  left: var(--arrow-front);
  z-index: 3;
  transform: translate(var(--shift-front, 0), -50%);
  transition: transform var(--ease-move), opacity var(--ease-move);
}

/* Front's resting spot for `services` is exactly the open middle the header
   and grid need, so it fades out in place rather than travelling anywhere.
   Its label (`.hero-nav__link--about`) is unaffected: it keeps reading as
   the link back to `about`, just without its chevron drawn under it.
   `transition: none` matches `.is-products`/`.is-contact`'s identical
   override just below/in products.css: front's own base rule lists
   `opacity` in its transition, so without this it would ease out over
   700ms instead of just gone.

   `--shift-front` is now pinned to `-9cqw` for `services` too (see the
   shared comment above `.hero.is-about`), the same value it has for
   `about` — so front's transform never actually changes entering or
   leaving this state, only its opacity does. Before that fix, `--shift-
   front` reset to `0cqw` the moment `services` hid it; that reset was
   invisible going forward (same instant as the opacity snap this rule
   forces), but going backward — `services` to `about` — nothing here
   forces an instant transform, so it eased `transform` and `opacity`
   together and read as the chevron sliding in from the right while
   fading up. Pinning the shift removed that transform difference at the
   source, so the backward reveal is now a plain fade, no slide. */
.is-services .arrow--front {
  opacity: 0;
  transition: none;
}

/* Every section opens the same way in spirit — one chevron travels onto the
   one behind it, and the fourth arrives — but each section moves its own
   chevron: `about` carries the front chevron onto the mid one,
   `services` carries the mid chevron onto the back one. -9vw is
   --arrow-mid - --arrow-front (9% - 18%) — untouched by every --arrow-back/
   --arrow-quad retune in this file's history (see --arrow-back's own
   comment above), since it depends only on mid/front and neither has moved
   relative to the other since step 1. -10vw is --arrow-back - --arrow-mid
   (-1% - 9%) — was -10.8vw pre-events, -11vw through steps 2-5, -10.5vw
   after step 6, now -10vw after step 8 moved back again without moving mid.

   Each shift is also pinned at this same value in every later state of the
   chain, not just the one where the chevron travels — fixed 2026-09-01,
   see the bug this replaces just below --shift-back's own comment. A
   chevron that has finished travelling is hidden (opacity: 0) for the rest
   of the chain, and until this fix its --shift-* fell back to the base
   0cqw the instant it was no longer named here — invisible while forward,
   since the reset and the hide land in the same instant, but scrolling
   *backward* into the state where it reveals again animated `transform`
   (0 -> its travelled value) at the same time as `opacity` (0 -> 1), which
   read as a second chevron sliding in from the right at partial opacity.
   Pinning the value means transform never differs across a hidden<->
   visible boundary, so only opacity has anything left to animate there. */
.hero.is-about,
.hero.is-services,
.hero.is-products,
.hero.is-events,
.hero.is-contact {
  --shift-front: -9cqw;
}

.hero.is-services,
.hero.is-products,
.hero.is-events,
.hero.is-contact {
  --shift-mid: -10cqw;
}

/* Same mechanic as --shift-front/--shift-mid above: the travelling chevron's
   tip lands exactly on the destination chevron's own tip-offset formula.
   Back's resting tip is var(--arrow-back) + var(--tip-offset-back); quad's
   resting tip is var(--arrow-quad) + var(--tip-offset-quad). Since
   --tip-offset-back == --tip-offset-quad (both 42.515cqh), that term
   cancels in the difference, leaving quad's tip minus back's tip =
   var(--arrow-quad) - var(--arrow-back) = -12% - (-1%) = -11%, so
   -11vw lands back's tip exactly on quad's — a placeholder
   pending the render-and-measure pass every arrow position here needs
   (see --arrow-back's own comment above for the full step-by-step history
   behind this number: -7.3vw pre-events, -14.2vw/-14vw/-13vw/-11vw/-10vw/
   -11vw/-11.5vw through steps 1-8, now -11vw again after step 9 moved
   quad back right). `events`/`contact` both keep this same value pinned
   (see the shared --shift-front/--shift-mid comment above for why: back
   is hidden throughout both, and leaving it to fall back to the base 0vw
   rest value instead — the previous behavior — animated a phantom slide
   the instant it revealed again scrolling backward into `products`). */
.hero.is-products,
.hero.is-events,
.hero.is-contact {
  --shift-back: -11cqw;
}

/* Same mechanic again, one lane further: quad has no further chevron to
   land on, so it travels toward the open ground instead, the same role back
   used to solely own. Target is `.hero-nav__link--contact`'s own plain
   literal (2%); quad's resting tip is 9.5% (see above). 2% - 9.5% = -7.5vw
   would land the tip exactly on `contact`'s label; nudged right of that by
   the same 0.4vw convention the old back-chevron travel used, landing
   slightly past it instead — -7.1vw (was -5.1vw/-6.1vw/-7.1vw/-6.6vw
   through steps 3/5/7/9; steps 6/8 didn't move quad, so no change either
   time). `contact`
   itself sets no `--shift-quad` — arriving there (from `events` or
   anywhere else) simply lets it fall back to the base 0vw rest value
   above, sliding the chevron home to the right — the one reverse step in
   the whole chain.

   Left exactly as original on desktop/wide aspects — on request, 2026-09-13
   (see the laptop-tier override just below for what changed and why; this
   flat -7.1cqw was actually the same approximation that override's own
   comment explains, but the user asked explicitly to leave desktop's look
   alone rather than replace it with the aspect-corrected formula, so it
   stays a flat number here). */
.hero.is-events {
  --shift-quad: -7.1cqw;
}

/* Laptop tier only (same `@media (max-aspect-ratio: 1.85/1)` band every
   other section's own laptop tweak uses — see "Laptop/desktop split" in
   CLAUDE.md): the flat -7.1cqw above was derived by hand assuming the
   resting tip sits at a fixed ~9.5% of stage width, true only near the
   ~1.977 design aspect ratio. At laptop aspects (~1.7-1.85),
   --tip-offset-quad's fixed 42.515cqh reads as a *larger* fraction of the
   stage's (narrower) width, so the true resting tip sits further right
   than that approximation assumed — the flat number undershot there,
   leaving the chevron visibly overlapping `contact`'s label instead of
   clearing it. Reported live on a laptop 2026-09-13; confirmed by headless
   capture at 1650x900 (1.83 aspect, overlapping) vs 1900x950 (2.0 aspect,
   fine). Then, on further request the same day, retargeted from "just past
   contact" to "the tip should reach the viewport's left side" — pushing
   the chevron further left than before, with `contact`'s label now sitting
   in front of the chevron's body rather than in open ground to its left
   (accepted: `.hero-nav` is above every chevron, z-index 21 vs. quad's 0,
   so the label stays clickable/legible either way). Landing exactly on the
   edge left no margin for rendering/rounding differences and reportedly
   clipped the point off-screen on the user's own laptop, so the real
   target is `1.5cqw` in from the edge, not `0`.

   Formula: `target - resting tip`, fully expanded as
   `1.5cqw - var(--arrow-quad) - var(--tip-offset-quad)`. Kept as a live
   calc() (not flattened to one number the way the desktop value above is)
   so it reaches the same true target at any aspect within this tier.

   `--arrow-quad` is deliberately NOT read live via `var(--arrow-quad)`
   here, though — written instead as its own numeric value, `12cqw`
   (negated, since --arrow-quad is -12%), folded into the `13.5cqw` literal
   below (`12cqw` + the `1.5cqw` target = `13.5cqw`). `--arrow-quad` is
   typed as a `%`, correct where it's consumed directly (`left:
   var(--arrow-quad)`, where % resolves against the containing block — the
   stage). But this custom property is only ever consumed inside
   `transform: translate()` (on both .arrow--quad and
   .hero-nav__link--events) — and a bare `%` inside `translate()` resolves
   against the ELEMENT'S OWN box, not the stage, unlike `cqw`/`cqh`, which
   always resolve against the query container regardless of which property
   they appear in (see "Two rules that make the arrow/label pairing work"
   below). Mixing a live `var(--arrow-quad)` (%) into this calc() produced
   exactly that bug on first attempt: a `getBoundingClientRect` probe
   measured the label landing at 8.9% instead of the intended target,
   because the `%` term was resolving against the tiny nav-label element's
   own width instead of the stage's — `12cqw` sidesteps it, the same way
   --shift-back's own flat cqw literal does. Update it by hand if
   --arrow-quad is ever retuned, the same manual-sync cost --shift-back
   already carries. */
@media (max-aspect-ratio: 1.85/1) {
  .hero.is-events {
    --shift-quad: calc(13.5cqw - var(--tip-offset-quad));
  }
}

/* Parked a full viewport to the right until it is called for. It must also be
   `visibility: hidden` while parked: left merely transformed off-screen it
   still promotes a compositing layer, and that quietly drops the hero nav from
   subpixel to greyscale antialiasing. Visibility flips instantly on the way in
   and waits out the slide on the way back.

   Must stay below `.arrow` in source order — both are single-class selectors,
   so the later one wins the `transform` they both set. */
.arrow--far {
  left: var(--arrow-far);
  /* Sits with the other three, below the logo — in the reference the corner
     logo reads on top of this chevron's band. */
  z-index: 3;
  visibility: hidden;
  transform: translate(100cqw, -50%);
  transition: transform var(--ease-move),
    visibility 0s linear var(--ease-move);
}

.is-about .arrow--far,
.is-services .arrow--far,
.is-products .arrow--far,
.is-events .arrow--far,
.is-contact .arrow--far {
  visibility: visible;
  transform: translate(0, -50%);
  transition: transform var(--ease-move), visibility 0s;
}

/* `about` restored to the shared arrival group, on request (it had been
   dropped, on an earlier request, alongside about.css's own `.is-about
   .arrow--back` etc. rule — see about.css for why both are being
   reconsidered together). */

/* Native-size placement, measured off the reference mockups: cropping
   hero_plain.png at its own 673x399 pixel size, flush to the bottom-left
   corner, reproduces the asset exactly. 673/4032 = 16.69% of the stage
   width; height:auto preserves the asset's own aspect ratio instead of
   restating the matching 19.57% height. Persistent across every section,
   not inside any `.section-layer`. */
.corner-triangle {
  position: absolute;
  left: 0;
  bottom: 0;
  width: 16.69%;
  height: auto;
  z-index: 1;
  pointer-events: none;
  user-select: none;
}

/* ---------- Section layers ---------- */

/* One per section, all stacked above the chevron art. Nothing inside one can
   fall behind a chevron — the art is opaque, so a lower z-index hides an
   element rather than tinting it.

   Everything that has to be *read* waits out --delay-settle, so the chevrons
   have landed before any copy fades up and nothing is moving while it is read.
   Each section's stylesheet owns the matching `.is-x .x-layer` rule that turns
   its own layer on, with its own entrance transition (opacity var(--ease-fade)
   var(--delay-settle)) — this base rule carries none, on purpose: leaving a
   section is instant, not faded. Each section's own chevron un-shifts back to
   0 the moment its `.is-x` class drops (`--shift-front`/`--shift-mid` above),
   so its content has to clear out with it rather than linger through a fade
   while its chevron is already sliding home. */
.section-layer {
  position: absolute;
  inset: 0;
  z-index: 20;
  opacity: 0;
  visibility: hidden;
  pointer-events: none;
}

/* ---------- Logos ---------- */

/* Transparent-bg variant: the artwork carries its own canvas padding, so
   this box is wider than the mark reads on screen.

   The logo is also the way home from any section, so it lives in a button.
   The button is stripped back to nothing — it must render pixel-identically
   to the bare image it replaced — and only becomes clickable once the hero
   is no longer the current view. */
.logo-home {
  position: absolute;
  left: 73.5%;
  top: 50%;
  width: 15.7%;
  min-width: 187px;
  max-width: 315px;
  z-index: 4;
  margin: 0;
  padding: 0;
  border: 0;
  background: none;
  -webkit-appearance: none;
  appearance: none;
  pointer-events: none;
  transform-origin: top left;
  /* Vertically centers the mark on the hero stage. The fold rule below
     overrides `transform` outright (not additively), so it has its own
     compensated translateY to land on the same folded position as before
     this top:29% -> 50% move — see its comment. */
  transform: translateY(-50%);
  transition: transform var(--ease-move);
}

.logo-main {
  display: block;
  width: 100%;
  transition: opacity var(--ease-ui);
}

/* The logo shrinks into the top-right corner and becomes the way back. Scale
   and offsets are derived from the rendered hero mark against the about
   reference, about the element's top-left corner.

   This is deliberately ONE rule for every section, not one per section. Both
   states resolve to the identical transform, so the transition above has
   nothing to interpolate when you move from about to services or back — the
   logo simply stays parked in the corner, and the travel only ever plays on
   the hero/section boundary. Two rules that merely happened to agree would
   drift apart on the next edit and reintroduce the jump.

   z-index climbs to 21 here too, same reason as the nav: once a section's
   own layer is the active one, its full-bleed box (z-index 20) would
   otherwise sit above this button's parked z-index 4 and swallow the click
   meant to leave the section. */
/* `contact` joins this group even though it opts out of the fourth chevron
   and gallery (see contact.css) — without it, the logo stays full-size at
   its hero position with no way to click back to the plain hero. Folding it
   is a separate mechanism from the fourth-chevron arrival, so contact can
   take one without the other. */
.is-about .logo-home,
.is-services .logo-home,
.is-products .logo-home,
.is-events .logo-home,
.is-contact .logo-home {
  z-index: 21;
  pointer-events: auto;
  cursor: pointer;
  padding-top: 1.5cqh;
  /* `vw`/`vh`, not `cqw`/`cqh` — deliberately the one exception to this
     file's container-relative-units convention. `setSection` (about.js)
     toggles all five `is-*` classes on `.hero` in one synchronous loop on
     every navigation (four `false`s before the matching `true`); when this
     was `cqw`/`cqh`, that left the fold transform correctly resolved only
     for whichever section happened to be first/second in `SECTIONS` (about,
     services) — every later one (products, events, contact) rendered the
     logo at its full, un-folded hero size, reproducibly, every load, not a
     headless-capture flake. Confirmed by binary-testing: baking `is-contact`
     into the static HTML with no JS at all folds correctly, proving the CSS
     match itself was never the problem, only Chromium's container-query
     recompute after several same-tick `classList.toggle` calls. `vw`/`vh`
     read off the viewport instead, sidestepping container-query
     recalculation entirely. The one accepted cost: within the aspect-lock
     band's letterbox/pillarbox margin (`.hero` narrower/shorter than the
     viewport — see "The stage" in CLAUDE.md), the folded badge can drift by
     that margin instead of tracking `.hero` exactly; not worth trading back
     for a badge that fails to fold at all on 3 of 5 sections.
     -35.22vh was tuned against the box's static top:29%. Its static top is
     now 50% (see the base rule's hero-centering comment), 21% of the hero's
     height further down, so translateY is shifted up by that same 21vh to
     keep the folded mark exactly where it was. */
  transform: translate(13.49vw, -56.22vh) scale(0.718);
}

.is-about .logo-home:hover .logo-main,
.is-services .logo-home:hover .logo-main,
.is-products .logo-home:hover .logo-main,
.is-events .logo-home:hover .logo-main,
.is-contact .logo-home:hover .logo-main {
  opacity: 0.82;
}

.is-about .logo-home:active .logo-main,
.is-services .logo-home:active .logo-main,
.is-products .logo-home:active .logo-main,
.is-events .logo-home:active .logo-main,
.is-contact .logo-home:active .logo-main {
  opacity: 0.7;
}

/* ---------- Social ---------- */

/* Fixed to the viewport, not the stage: unlike `.corner-triangle`/`.logo-
   home`, this icon isn't art-directed against the 4032x2039 chevron canvas,
   so there's nothing to gain from `.hero`'s cqw/cqh coordinate system —
   and fixed positioning means it needs no separate mobile-tier rule the way
   every stage-anchored corner element does (see the mobile block below,
   where `.corner-triangle`/`.logo-home`/`.hero-nav` are all hidden
   outright and replaced by `.mobile-nav`). z-index sits above
   `.mobile-nav` (100) so the icon stays on top of the mobile header too.
   `right: 108px`, not the corner's own `20px` — reordered 2026-09-16, on
   request ("first the linkedin and after the switcher"), so this icon
   reads first (leftmost of the pair) and `.lang-switcher--corner` sits
   after it, at the true corner edge. `108px` = the switcher's own `20px`
   edge inset + its ~76px rendered width + the 12px gap between the two;
   see that rule's own comment below for the paired half of this swap. */
.social-linkedin {
  position: fixed;
  right: 108px;
  bottom: 20px;
  z-index: 200;
  display: block;
  width: 44px;
  height: 44px;
  line-height: 0;
  border-radius: 10px;
  box-shadow: 0 6px 16px -4px rgba(var(--shadow-ink), 0.55);
  transition: transform var(--ease-ui), box-shadow var(--ease-ui);
}

.social-linkedin img {
  display: block;
  width: 100%;
  height: 100%;
}

.social-linkedin:hover {
  transform: translateY(-2px);
  box-shadow: 0 10px 20px -4px rgba(var(--shadow-ink), 0.65);
}

.social-linkedin:active {
  transform: translateY(0);
  box-shadow: 0 4px 10px -4px rgba(var(--shadow-ink), 0.5);
}

/* ---------- Language switcher (corner) ---------- */

/* Fixed to the viewport, not the stage — same reasoning as `.social-
   linkedin`/`.cookie-notice`: not art-directed against the chevron canvas,
   so cqw/cqh buys nothing. Sits at the true bottom-right corner edge
   (`right: 20px`, matching `.social-linkedin`'s own original inset),
   with `.social-linkedin` now sitting to its left instead (moved to
   `right: 108px` — see that rule's own comment above) — reordered
   2026-09-16, on request ("first the linkedin and after the switcher"),
   from this pill's first placement immediately left of a corner-pinned
   LinkedIn icon (before that, top-left, mirroring the logo's own fixed
   top-right fold). `height: 44px` matches `.social-linkedin`'s exactly
   (on request, "same height") — border-box (base.css's global reset), so
   this overrides the base `.lang-switcher` rule's own content-sized
   height without needing to touch that shared rule, which the mobile-nav
   and legal-page instances of this same component still rely on staying
   unset. Replaced by a second `.lang-switcher` instance inside
   `.mobile-nav`'s header row on phone/tablet — see the mobile-flow query
   below, which hides this one there. */
.lang-switcher--corner {
  position: fixed;
  right: 20px;
  bottom: 20px;
  height: 44px;
  z-index: 200;
}

/* Same lightbox-open treatment `.social-linkedin`/`.cookie-notice` get —
   drops below the lightbox (z-index 30/31) instead of floating on top of
   an opened photo/caption. Fixed 2026-09-16 (reference screenshot showed
   a chevron cutting across the flag tile): this used to drop all the way
   to `z-index: 1`, which is *below* the chevrons (`.arrow--*`, z-index
   0-3) too, not just the lightbox — `.hero`'s `container-type: size`
   does not actually isolate its descendants' z-index from this fixed
   sibling's own the way a true stacking context would (confirmed by this
   exact bug: a `z-index: 3` chevron painting over a `z-index: 1` sibling
   only happens if the two are compared directly, flat, not through an
   isolating boundary), so any value below the highest in-stage z-index
   (31, `.lightbox__step`/`.lightbox__close`) gets covered by whichever
   `.hero` content currently has a higher one. `25` sits below the
   lightbox (30/31) — the actual intent — while staying above every other
   in-stage layer (`.hero-nav`/`.section-stepper`/corner-nav at 21,
   `.section-layer` at 20, every chevron at 0-3). */
.hero.is-lightbox ~ .lang-switcher--corner {
  z-index: 25;
}

/* ---------- Cookie notice ---------- */

/* Fixed to the viewport, not the stage — same reasoning as `.social-
   linkedin` above: not art-directed against the chevron canvas, so cqw/cqh
   buys nothing, and fixed positioning needs no separate mobile-tier rule.
   Bottom-left mirrors `.social-linkedin`'s own bottom-right placement and
   20px inset; shares its z-index (200) so it stays above `.mobile-nav`
   (100) — but drops below an open lightbox, see the override a few rules
   down. `.corner-triangle` also sits bottom-left, but only inside `.hero`'s
   own contained coordinate space — this element is a `.hero` sibling with
   independent fixed positioning, so
   there's no clipping interaction, only a z-order one (opaque panel always
   paints on top). Check by screenshot that the 20px inset reads cleanly
   against the triangle's diagonal edge; widen it if not.
   A fixed `height: 44px` (matching `.section-stepper__btn`/`.social-
   linkedin` exactly) was tried on request, then reverted on desktop/
   laptop — content sizes the pill's height here instead, via vertical
   padding. Phone/tablet still uses a 44px floor (see the mobile-flow
   override below), since a narrower pill is more likely to need it. */
.cookie-notice {
  position: fixed;
  left: 20px;
  bottom: 20px;
  z-index: 200;
  display: flex;
  align-items: center;
  gap: 10px;
  max-width: min(420px, calc(100vw - 40px));
  padding: 12px 16px;
  border-radius: 10px;
  background: var(--panel-gallery);
  box-shadow: 0 6px 16px -4px rgba(var(--shadow-ink), 0.55);
  opacity: 0;
  visibility: hidden;
  pointer-events: none;
  transition: opacity var(--ease-fade), visibility 0s linear var(--ease-fade);
}

.cookie-notice.is-visible {
  opacity: 1;
  visibility: visible;
  pointer-events: auto;
  transition: opacity var(--ease-fade);
}

/* Both icons normally sit above everything, `.mobile-nav` included — but
   while a lightbox is open they should read as behind it, not floating on
   top of an opened photo/caption (this is what was overlapping the
   caption text on mobile, see the reference screenshot). `.lightbox`
   itself lives inside `.hero`, so `about.js`'s `openLightbox`/
   `closeLightbox` toggling `is-lightbox` on `.hero` (already used by
   `about.css` for the gallery) doubles as the signal here — both icons
   are `.hero` siblings later in `hero.html`'s DOM, so a general sibling
   combinator reaches them with no extra JS. **`z-index: 25`, not `1`** —
   same fix and same reasoning as `.lang-switcher--corner`'s own identical
   rule just above (see its comment): `1` sits below every chevron
   (0-3) too, not just the lightbox (30/31), since `.hero`'s
   `container-type: size` does not isolate its descendants' stacking from
   a fixed sibling here. `25` clears every in-stage layer up to
   `.hero-nav`/`.section-stepper` (21) while staying below the lightbox. */
.hero.is-lightbox ~ .cookie-notice,
.hero.is-lightbox ~ .social-linkedin {
  z-index: 25;
}

.cookie-notice__icon {
  flex: none;
  display: block;
  width: 22px;
  height: 22px;
  color: var(--paper);
}

.cookie-notice__icon svg,
.cookie-notice__close svg {
  display: block;
  width: 100%;
  height: auto;
}

.cookie-notice__message {
  flex: 1 1 auto;
  min-width: 0;
  margin: 0;
  font-family: var(--font-utility);
  font-size: var(--size-caption);
  line-height: var(--lh-caption);
  color: var(--paper);
}

/* Underlined at rest (unlike `.site-footer__link`) to match the reference
   photo's own resting state; color transition still borrows that
   component's dim-at-rest/bright-on-hover behavior. */
.cookie-notice__link {
  font-family: var(--font-utility);
  color: var(--paper-dim);
  text-decoration: underline;
  text-decoration-color: currentColor;
  text-underline-offset: 2px;
  transition: color var(--ease-ui);
}

.cookie-notice__link:hover {
  color: var(--paper);
}

.cookie-notice__link:active {
  color: var(--paper-dim);
}

/* Same model as `.lightbox__close`: stripped button, icon fills the box,
   opacity dims on hover, scales down on press. */
.cookie-notice__close {
  flex: none;
  width: 14px;
  height: 14px;
  padding: 0;
  border: 0;
  background: none;
  -webkit-appearance: none;
  appearance: none;
  color: var(--paper);
  cursor: pointer;
  transition: opacity var(--ease-ui), transform var(--ease-ui);
}

.cookie-notice__close:hover {
  opacity: 0.75;
}

.cookie-notice__close:active {
  transform: scale(0.9);
}

/* ---------- Nav ---------- */

/* z-index above `.section-layer` (20): otherwise, once a section is open, its
   full-bleed layer paints over this full-bleed nav and swallows clicks meant
   for the links underneath, even across the layer's empty space. `pointer-
   events: none` here keeps that empty space passing clicks through to the
   layer's own content (the rail, the gallery, the services grid) — only the
   links themselves opt back in. */
.hero-nav {
  position: absolute;
  left: 0;
  top: 0;
  width: 100%;
  height: 100%;
  z-index: 21;
  margin: 0;
  padding: 0;
  pointer-events: none;
}

.hero-nav__link {
  position: absolute;
  top: var(--nav-baseline);
  transform: translateY(-50%);
  font-family: var(--font-display);
  font-size: var(--size-nav);
  letter-spacing: var(--track-nav);
  text-transform: uppercase;
  color: var(--paper);
  text-decoration: none;
  white-space: nowrap;
  pointer-events: auto;
  /* Do NOT add `opacity` to this shared transition, however tempting — every
     label then gets a compositable property, Chrome promotes all four, and the
     nav drops from subpixel to greyscale antialiasing across the whole row.
     The label that fades in a given state declares its own transition. */
  transition: color var(--ease-ui);
}

.hero-nav__link:hover {
  color: var(--paper-dim);
}

/* Each label is placed against the chevron point it sits on. `contact`
   is the exception — it stands in open ground, left of any chevron, and
   unlike the other three it never hides itself when it is the current
   section (see contact.css) — there is no destination for it to fade into,
   so it just stays put and readable. */
.hero-nav__link--contact {
  left: 2%;
}

/* Rides the quad chevron. In the events state it travels with it toward the
   screen edge and eases out once landed (see `.is-events .hero-nav__link--
   events` in events.css, which restores the eased opacity transition for
   that direction) — the same self-hiding-as-current-section pattern
   `--products`/`--services`/`--about` below use for their own chevrons.
   Leaving `events` for `contact` (or anywhere else) un-shifts it back to
   rest along with the chevron — `opacity 0s` here is just the plain,
   no-easing default for that reversion; the actual "stay hidden rather
   than reappear" behavior lives in the `.nav-left-events` rule further
   down. */
.hero-nav__link--events {
  left: calc(var(--arrow-quad) + var(--tip-offset-quad));
  transform: translate(var(--shift-quad, 0), -50%);
  transition: transform var(--ease-move-quad), opacity 0s, color var(--ease-ui);
}

/* Rides the back chevron. In the products state it travels with it toward
   quad and eases out once landed (see
   `.is-products .hero-nav__link--products` in products.css, which restores
   the eased opacity transition for that direction) — the same
   self-hiding-as-current-section pattern `--services`/`--about` below use
   for their own chevrons. Leaving `products` for `events` (or anywhere
   else) un-shifts it back to rest along with the chevron — `opacity 0s`
   here is just the plain, no-easing default for that reversion; the actual
   "stay hidden rather than reappear" behavior lives in the
   `.nav-left-products` rule further down, driven by `about.js`, since CSS
   alone has no memory of which section you were on a moment ago. */
.hero-nav__link--products {
  left: calc(var(--arrow-back) + var(--tip-offset-back));
  transform: translate(var(--shift-back, 0), -50%);
  transition: transform var(--ease-move), opacity 0s, color var(--ease-ui);
}

/* Rides the mid chevron. In the services state it arrives underneath
   `products`, the label on the back chevron, and fades out there (see
   `.is-services .hero-nav__link--services` in services.css) — it is the
   current section, so it stops being a link the moment it lands.
   --tip-offset-mid and --tip-offset-back are exactly equal (42.515cqh), so
   it lands precisely on `products`' tip with no offset correction needed. See
   `.hero-nav__link--products` above for why `opacity` is a plain instant
   default here, and `.nav-left-services` further down for the actual
   stay-hidden behavior. */
.hero-nav__link--services {
  left: calc(var(--arrow-mid) + var(--tip-offset-mid));
  transform: translate(var(--shift-mid, 0), -50%);
  transition: transform var(--ease-move), opacity 0s, color var(--ease-ui);
}

/* Rides the front chevron. In the about state it arrives underneath
   `services`, the label on the mid chevron, and fades out there — it is the
   current section, so it stops being a link the moment it lands. See
   `.hero-nav__link--products` above for why `opacity` is a plain instant
   default here, and `.nav-left-about` further down for the actual
   stay-hidden behavior. */
.hero-nav__link--about {
  left: calc(var(--arrow-front) + var(--tip-offset-front));
  transform: translate(var(--shift-front, 0), -50%);
  transition: transform var(--ease-move), opacity 0s, color var(--ease-ui);
}

/* A section's own label must not just avoid reappearing *while its chevron
   slides home* (the plain instant `opacity 0s` above already covers that) —
   it must stay hidden for as long as you're further along the chain than it,
   in either direction, so scrolling backward through it doesn't flash a
   resettling chevron either. CSS has no memory of position in a chain, so
   `about.js`'s `setSection` computes this in JS on every call — a section
   gets `nav-left-<name>` whenever its rank in `SECTIONS` is behind the
   current section's — and toggles these classes on `.hero` itself. `contact`
   has no equivalent rule: its own label already never hides, current or not
   (see `.hero-nav__link--contact` above). `transition: none` is deliberate —
   once truly left, there is nothing left to ease. */
.hero.nav-left-about .hero-nav__link--about,
.hero.nav-left-services .hero-nav__link--services,
.hero.nav-left-products .hero-nav__link--products,
.hero.nav-left-events .hero-nav__link--events {
  opacity: 0;
  pointer-events: none;
  transition: none;
}

/* ---------- Corner "left-behind" nav stack ---------- */

/* A small breadcrumb-style list next to the folded corner logo, showing
   every section you've already passed going forward through the chain —
   `about` joins it once `services` is current, `services` joins it once
   `products` is current, and so on (see `.hero.nav-left-about
   .corner-nav__link--about` etc. further down). This is a second, purely
   visual consumer of the exact `nav-left-<name>` classes `setSection`
   already toggles on `.hero` for the primary `.hero-nav__link`s' own
   hide-when-passed behavior above — no new JS state exists for this,
   only a new markup/CSS pairing (`.corner-nav__link`, hero.html) plus its
   own click wiring (about.js). `contact` has no entry here for the same
   reason it has no `nav-left-contact` class: nothing follows it in
   `SECTIONS`, so it can never be "left behind". */
.corner-nav__link {
  position: absolute;
  right: var(--corner-nav-right);
  /* Centers the line on `top` the same way `.hero-nav__link` centers on
     `--nav-baseline` (`transform: translateY(-50%)`, above) — needed so
     `about`'s own top-of-stack line genuinely lines up with the main
     row's line, not just its box's top edge. Applied to every item, not
     just `--about`, so the constant per-item gap below is unaffected. */
  transform: translateY(-50%);
  font-family: var(--font-display);
  font-size: var(--size-nav);
  letter-spacing: var(--track-nav);
  text-transform: uppercase;
  color: var(--paper);
  text-decoration: none;
  white-space: nowrap;
  opacity: 0;
  pointer-events: none;
  /* Instant by default, same convention as `.hero-nav__link--about` etc.
     above: the entering state (further down) is the one that adds an
     eased transition, for that direction only. Note this `opacity`
     entry governs only the show/hide reveal below, not a hover/press
     dim — color is a flat `--paper` in every state (see the comment on
     `:hover`/`:active` below for why this differs from `.hero-nav__link`). */
  transition: opacity 0s;
}

/* Flat white in every state, unlike `.hero-nav__link` (which dims to
   `--paper-dim` at rest and brightens on hover) — on request, 2026-09-16.
   Hover/press feedback uses `filter`, not `opacity`: the reveal rule
   further down sets `opacity: 1` at higher specificity (three classes vs.
   two here), so an opacity-based dim would be silently overridden the
   moment an item is actually visible/interactive. */
.corner-nav__link:hover,
.corner-nav__link:focus-visible {
  filter: brightness(0.8);
}

.corner-nav__link:active {
  filter: brightness(0.6);
}

/* Stacked top to bottom in chain order, each one slot below the last —
   `about` first (added on reaching `services`), then `services`, then
   `products`, then `events` (added last, on reaching `contact`). */
.corner-nav__link--about {
  top: var(--corner-nav-top);
}

.corner-nav__link--services {
  top: calc(var(--corner-nav-top) + var(--corner-nav-gap));
}

.corner-nav__link--products {
  top: calc(var(--corner-nav-top) + 2 * var(--corner-nav-gap));
}

.corner-nav__link--events {
  top: calc(var(--corner-nav-top) + 3 * var(--corner-nav-gap));
}

/* Reveal, fading in synced with that section's own content settling —
   reuses the exact tokens `.section-layer`'s own entrance transition
   already uses (`opacity var(--ease-fade) var(--delay-settle)`, above).
   Only the item whose `nav-left-*` class just flipped true animates;
   items already shown stay at `opacity: 1` with nothing to transition.
   Retreating past a section drops its `nav-left-*` class, reverting to
   the base rule's instant `transition: opacity 0s` above — matching this
   codebase's "leaving is instant, entering fades" convention (see the
   comment on `.hero.nav-left-about .hero-nav__link--about` etc. above),
   so an item disappears immediately on the way back rather than fading
   out. */
.hero.nav-left-about .corner-nav__link--about,
.hero.nav-left-services .corner-nav__link--services,
.hero.nav-left-products .corner-nav__link--products,
.hero.nav-left-events .corner-nav__link--events {
  opacity: 1;
  pointer-events: auto;
  transition: opacity var(--ease-fade) var(--delay-settle);
}

/* ---------- Section stepper ---------- */

/* Bottom-center prev/next controls — a discrete-click equivalent of the
   wheel handler's own one-section-per-gesture stepping (about.js's
   `stepSection`), same chain, same clamp-at-both-ends behavior, no
   wraparound. Persistent chrome like `.hero-nav`, not part of any one
   section's `.section-layer`, so it needs the same above-`.section-layer`
   (20) z-index to stay clickable no matter which section is current.
   Desktop/laptop only — hidden outright in the mobile-flow query below,
   alongside `.hero-nav`. `bottom: 4cqh` is a first-pass placeholder (no
   reference mockup exists for this control); checked clear of the deepest
   content on the stage (contact's card bottom edge and events' grid
   bottom, both well above it) and of `.corner-triangle`, which owns the
   bottom-left corner rather than the center.

   Tried positioning `left` off the mid/front chevron tips instead of a
   plain stage-centered 50% (on request, "it should be horizontally
   between the arrow front and arrow mid") — landed correctly on a
   validated desktop capture (1600x900) but read off-center on the user's
   actual laptop, so reverted back to plain centering rather than chase a
   formula further without a concrete repro.

   Desktop centering (`left: 50%`) was confirmed good as-is. Laptop tier
   briefly had its own rightward nudge here (`+3.6cqw`, tuned by eye on a
   1440x900 capture across several passes: started at `+3cqw`, briefly
   `+7cqw` on request — "on laptop a bit more to the right" — then
   fine-tuned to `+3.6cqw`), then reverted 2026-09-14 on a follow-up
   request to keep it centered on laptop too, same as desktop — no
   laptop-tier override remains. */
.section-stepper {
  position: absolute;
  left: 50%;
  bottom: 4cqh;
  transform: translateX(-50%);
  display: flex;
  gap: 2cqw;
  margin: 0;
  padding: 0;
  z-index: 21;
}

/* Bare white chevron, no plate — the same language `.about-gallery__nav`/
   `.lightbox__step` use for their own prev/next arrows: a plain glyph
   directly on the ground, dimming on hover rather than sitting on a panel.
   `height: 44px` matches `.social-linkedin`'s own fixed size on request —
   a plain px value, not cqh, for the same reason `.social-linkedin` itself
   isn't sized in cqw/cqh: neither is art-directed against the chevron
   canvas, so there's nothing to gain from the stage's coordinate system. */
.section-stepper__btn {
  display: flex;
  align-items: center;
  justify-content: center;
  height: 44px;
  padding: 0;
  border: 0;
  background: none;
  color: var(--paper);
  cursor: pointer;
  -webkit-appearance: none;
  appearance: none;
  transition: transform var(--ease-ui), opacity var(--ease-ui);
}

.section-stepper__btn svg {
  display: block;
  width: auto;
  height: 100%;
}

.section-stepper__btn:hover {
  opacity: 0.75;
}

/* Nudge direction follows the glyph's own point, not the prev/next label —
   the site's chevrons travel leftward as sections advance, so the "next"
   control sits on the left with a left-pointing glyph and "previous" sits
   on the right with a right-pointing one (see hero.html). */
.section-stepper__btn--next:active {
  transform: translateX(-3px);
}

.section-stepper__btn--prev:active {
  transform: translateX(3px);
}

/* Disabled at either end of the chain — hero has no previous, contact has
   no next. about.js keeps this in sync with `activeSection` on every
   `setSection` call. Native `disabled` already blocks the click and drops
   the button from the tab order; this only needs to cover the look. */
.section-stepper__btn:disabled {
  opacity: 0.3;
  cursor: default;
}

/* ---------- Mobile nav ---------- */

/* Hidden on the stage; the mobile query below turns it on and hides the
   chevron-tip-anchored `.hero-nav` instead. A plain fixed header bar with a
   hamburger dropdown, not an off-canvas panel — lower risk for a first
   pass. Markup lives in hero.html, a sibling of `.hero`, not inside it (it
   is not part of any section's own layer). */
.mobile-nav {
  display: none;
}

@media (max-width: 1024px),
(max-aspect-ratio: 1/1) {

  /* Fixed px, not vh: the header's real height is dominated by the logo
     mark and the 44px toggle button, neither of which scales with
     viewport height, so a vh padding here would only clear `.hero`'s own
     padding-top (below) at tall/artificial test windows and leave it short
     at realistic phone heights — found by testing at a realistic viewport
     instead of an inflated one. Keep this in step with `.hero`'s
     padding-top: header height (~72px: 52px logo/44px toggle, whichever is
     taller, + 20px padding) plus a small buffer. */
  .mobile-nav {
    position: fixed;
    top: 0;
    left: 0;
    width: 100%;
    z-index: 100;
    display: flex;
    align-items: center;
    justify-content: space-between;
    padding: 10px 5vw;
    background: var(--blue-deep);
  }

  /* The fixed top-left corner pill (hero.css's own "Language switcher"
     section, above) has nowhere open to sit once this header takes over —
     replaced by a second `.lang-switcher` instance living right here,
     grouped with the toggle so `.mobile-nav` keeps a plain two-item
     `space-between` (logo / this group) instead of unevenly spacing three
     top-level children. */
  .lang-switcher--corner {
    display: none;
  }

  /* `.social-linkedin`'s desktop/laptop `right: 108px` (see that rule's
     own comment above) only makes sense paired with `.lang-switcher--
     corner` sitting at the true corner edge just to its right — with that
     pill hidden on this tier (just above) and replaced by a mobile-nav
     instance instead, nothing occupies the freed 88px and the icon reads
     as floating short of the corner instead of pinned to it. Moved back
     to the true `right: 20px` corner inset on mobile/tablet only (on
     request, 2026-09-16) — desktop/laptop keep the paired 108px
     placement untouched. */
  .social-linkedin {
    right: 20px;
  }

  .mobile-nav__right {
    display: flex;
    align-items: center;
    gap: 12px;
  }

  .mobile-nav__logo {
    display: block;
    height: 52px;
  }

  .mobile-nav__logo img {
    display: block;
    height: 100%;
    width: auto;
  }

  .mobile-nav__toggle {
    flex: none;
    width: 44px;
    height: 44px;
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    gap: 5px;
    padding: 0;
    border: 0;
    background: none;
    -webkit-appearance: none;
    appearance: none;
    cursor: pointer;
  }

  .mobile-nav__toggle span {
    display: block;
    width: 24px;
    height: 2px;
    background: var(--paper);
  }

  .mobile-nav__menu {
    display: none;
    position: absolute;
    top: 100%;
    left: 0;
    width: 100%;
    flex-direction: column;
    background: var(--blue-deep);
    border-top: 1px solid rgba(244, 247, 249, 0.15);
  }

  .mobile-nav.is-open .mobile-nav__menu {
    display: flex;
  }

  .mobile-nav__link {
    padding: 1.6vh 5vw;
    font-family: var(--font-utility);
    font-size: var(--size-body);
    letter-spacing: 0.02em;
    text-transform: uppercase;
    color: var(--paper);
    text-decoration: none;
    border-bottom: 1px solid rgba(244, 247, 249, 0.1);
  }

  .mobile-nav__link.is-current {
    color: var(--paper-dim);
  }
}

/* Tablet tier: grows the header itself (logo, toggle, padding) since a
   768-1024px screen can afford more chrome than a phone's — layers on top
   of the block above the same way base.css's own tablet-tier block layers
   on the phone type scale. Keep in step with `.hero`'s own tablet-tier
   padding-top below (~96px: 64px logo/52px toggle, whichever is taller,
   + 24px padding + a small buffer — same budget arithmetic as the phone
   header's comment above, scaled up). First-pass values, no tablet
   reference exists yet. */
@media (min-width: 768px) and (max-width: 1024px) {
  .mobile-nav {
    padding: 12px 4vw;
  }

  .mobile-nav__logo {
    height: 64px;
  }

  .mobile-nav__toggle {
    width: 52px;
    height: 52px;
  }

  .mobile-nav__right {
    gap: 16px;
  }

  .mobile-nav__link {
    padding: 1.8vh 4vw;
  }
}

/* ---------- Mobile flow ---------- */

/* Phones and tablets (both orientations, up to 1024px wide) drop the
   chevron "stage" entirely — see base.css's own mobile block for why this
   exact query. .hero stops being a fixed-aspect, letterboxed box (§ above)
   and becomes a plain full-width flow container; the chevron art, corner
   triangle, folding logo and the chevron-tip-anchored nav (all
   art-directed for the wide stage and with no meaningful position at
   portrait/stacked proportions) are hidden outright rather than adapted —
   replaced by `.mobile-nav` above. */
@media (max-width: 1024px),
(max-aspect-ratio: 1/1) {
  .hero {
    position: static;
    width: 100%;
    height: auto;
    aspect-ratio: auto;
    container-type: normal;
    overflow: visible;
    /* The mobile header bar (.mobile-nav) sits fixed above the flow
       content, so the page needs a top offset equal to its own real height
       (~72px, fixed px — see .mobile-nav's own comment for why vh doesn't
       work here) or the first section starts under it. Padding, not
       margin, so the background (--blue-deep, matching .mobile-nav's own)
       shows through the gap rather than leaving it bare. */
    padding-top: 84px;
  }

  .arrow,
  .corner-triangle,
  .logo-home,
  .hero-nav,
  .section-stepper {
    display: none;
  }

  /* Narrower pill on phone/tablet, on request — 75vw instead of the
     desktop rule's `min(420px, calc(100vw - 40px))`. `height: auto` (with
     a `min-height` floor, not a fixed height) replaces the desktop rule's
     fixed 44px: at 75vw the message text is more likely to wrap to two
     lines than at the wider desktop cap, and a fixed height would clip it. */
  .cookie-notice {
    width: 75vw;
    max-width: 75vw;
    height: auto;
    min-height: 44px;
    padding: 10px 16px;
  }

  /* Every section's own `.is-x .x-layer` rule only ever sets opacity/
     visibility/pointer-events to the same "visible" values this sets, so
     nothing here needs to fight those — only `position`/`inset`/`z-index`
     are unique to the base rule, and no section-specific rule contests
     them. Net effect: all six layers render in normal document flow,
     stacked in DOM order, and the page simply scrolls — `about.js`'s
     `is-x` toggling keeps running for hash/nav-highlight bookkeeping but
     stops being what controls visibility. */
  .section-layer {
    position: static;
    z-index: auto;
    opacity: 1;
    visibility: visible;
    pointer-events: auto;
    /* The mobile-nav hamburger menu's own click handler (about.js) calls
       `target.layer.scrollIntoView({ block: 'start' })` — without this,
       `.mobile-nav`'s own fixed 84px (its real height, see its own
       comment above) sits on top of the viewport and covers exactly that
       much of whatever scrolled to the very top, hiding the section's own
       title/first content under the header instead of landing cleanly
       below it. */
    scroll-margin-top: 84px;
  }
}

/* Tablet tier: matches the grown header above (~96px real height) — see
   that block's own comment for the arithmetic. */
@media (min-width: 768px) and (max-width: 1024px) {
  .hero {
    padding-top: 96px;
  }

  .section-layer {
    scroll-margin-top: 96px;
  }
}