/* ============================================================
   CROWN & CORE - PAGE LAYOUT PATTERNS
   ============================================================ */

/* Hero photograph presentation, hoisted to :root because the intro curtain
   (.intro__photo in animations.css) has to reproduce the hero background
   pixel-for-pixel at the end of its shrink - it lives in a fixed overlay
   outside .hero, so it can't inherit these from .hero__bg. Both consume the
   same two values; the 960px override below moves them together. */
:root {
  --hero-photo-pos: 50% 45%;
  --hero-photo-o: 0.78;
}
@media (max-width: 960px) {
  :root { --hero-photo-pos: 55% 45%; --hero-photo-o: 0.4; }
}

/* ---------------- Hero (pinned reveal) ----------------
   .hero-pin-wrap is taller than .hero-pin-inner by the "reveal" scroll
   distance - that's the distance .hero-pin-inner (which holds both .hero
   and the marquee strip as one rigid unit) stays pinned to the viewport top
   before un-pinning into normal flow. main.js drives the clip/zoom visual
   over that same "reveal" distance (capped with min() below), so the reveal
   finishes at exactly the moment the pin releases: the marquee - being part
   of the pinned unit rather than a free sibling - cannot move, scroll, or
   peek-drift on its own before then. */
.hero-pin-wrap {
  position: relative;
  /* Single source of truth for the marquee's height, shared with .hero's
     height calc below. When the short-viewport fallback further down zeroes
     --hero-marquee-h, .hero grows to fill the gap it leaves - but the
     marquee strip itself keeps its fixed 124px (.hero-marquee-peek), so the
     combined hero+marquee content then exceeds 100vh by that same 124px.
     inner/wrap add back exactly that shortfall (124px - --hero-marquee-h)
     below, so the pinned unit is always sized to its actual content and
     the marquee's own box can never overflow past it into the next
     section. */
  --hero-marquee-h: 124px;
  height: calc(100vh + (124px - var(--hero-marquee-h)) + min(500px, 65vh));
}
.hero-pin-inner {
  /* Carries the sticky positioning for the hero + marquee unit together, so
     the marquee stays visually locked to the hero (not just to the
     viewport) for the whole reveal instead of scrolling independently.
     Height matches hero + marquee's true combined height (see
     --hero-marquee-h note on .hero-pin-wrap above), not a flat 100vh - so
     the marquee's fixed-height box is never asked to overflow the pinned
     unit it is meant to fit inside of. */
  position: sticky;
  top: 0;
  height: calc(100vh + (124px - var(--hero-marquee-h)));
}
.hero {
  /* Shorter than 100vh by --hero-marquee-h (the marquee strip's own height,
     124px - fixed padding + fixed font-size, so it doesn't move across
     breakpoints). That gap is what lets the marquee sit directly beneath it,
     filling out .hero-pin-inner's 100vh exactly. On very short viewports
     (below), --hero-marquee-h drops to 0 and this reverts to a plain 100vh
     hero - the peek is a nicety, never worth clipped content. */
  position: relative;
  height: calc(100vh - var(--hero-marquee-h));
  background: var(--charcoal);
  color: var(--paper-text);
  /* Named, not just written into padding directly below: .hero__grid's
     transform reads these same two values back to correct for the fact that
     they aren't equal (see --hero-vshift there). */
  --hero-pad-t: clamp(6rem, 11vw, 8rem);
  --hero-pad-b: var(--space-6);
  padding-top: var(--hero-pad-t);
  padding-bottom: var(--hero-pad-b);
  display: flex;
  /* Centered by default - there is comfortably more box than content at
     ordinary viewport heights. Reverts to flex-start below, on short
     viewports only: this is a fixed-height, overflow:hidden box, and once the
     content gets close to filling it, centering starts pushing the top edge
     upward past the box's own padding-top - which is exactly the clearance
     the padding exists to guarantee, put there so content never sits behind
     the fixed header. flex-start keeps that top edge anchored no matter how
     tall the content gets, so any overflow is pushed to the bottom instead,
     where the max-height query below is already trimming things down. */
  align-items: center;
  /* The horizontal counterpart: keeps .hero__grid centered on the main axis in
     its own right, rather than leaving that solely up to margin-inline:auto on
     the container below - so it stays centered even if that rule changes. */
  justify-content: center;
  /* Flex centers within the padded content box, not the box .hero__bg (the
     video) actually fills - and padding-top/bottom aren't equal (top carries
     header clearance, bottom doesn't need to), so that content box sits off-
     centre within the full box by half the difference. .hero__grid reads this
     back to correct for it, so "centered" means centered on the video, not on
     an incidental padding box. 0px outside the regime this math is valid in
     (auto-height layouts, and the short-viewport fallback below, both reset
     it - see there for why). */
  --hero-vshift: calc((var(--hero-pad-b) - var(--hero-pad-t)) / 2);
  overflow: hidden;
  isolation: isolate;
}
.hero > .container { width: 100%; }
.hero-marquee-peek {
  /* Locks the strip to the exact height .hero's calc() assumes, regardless
     of content - this section must never grow/shrink independently of that
     constant, or the peek math (and the pin-release point) drifts. */
  height: 124px;
  /* Zero the .section--tight block padding: the fixed 124px is the whole
     usable band, and the perspective marquee inside needs all of it (the
     old tiny label strip could afford the padding; the 3D type can't). The
     124px height itself is untouched, so the peek + pin math is unaffected. */
  padding-block: 0;
  display: flex;
  align-items: center;
  overflow: hidden;
}
@media (prefers-reduced-motion: reduce) {
  /* The pin itself is a motion effect (scroll input drives an animation
     instead of the page just scrolling) - reduced-motion visitors get the
     hero as a normal, non-sticky block instead, fully revealed immediately,
     with the marquee following right after in normal document flow. */
  .hero-pin-wrap { height: auto; }
  .hero-pin-inner { position: static; height: auto; }
  /* height:auto means there is no extra box for the padding asymmetry to hide
     in - the box IS the content, top to bottom - so the vshift correction
     above would only push content off the video it's meant to be centered on
     instead of onto it. Zeroed for the same reason everywhere else below. */
  .hero { height: auto; align-items: center; --hero-vshift: 0px; }
}
@media (max-width: 1299px) {
  /* Below 1300px there isn't enough side-by-side room left for the pin's
     payoff to read as intended (the two-column hero grid is already
     cramped well before it stacks at 960px) - so the pin and its clip/zoom
     reveal are dropped in favour of a plain, non-sticky hero with the video
     simply shown full-bleed and settled, same fallback as reduced-motion
     above, just keyed off width instead. main.js mirrors this width check
     so it never fights these values by writing its own clip/zoom on scroll. */
  .hero-pin-wrap { height: auto; }
  .hero-pin-inner { position: static; height: auto; }
  .hero { height: auto; align-items: center; --hero-vshift: 0px; }
  /* --hero-clip/--hero-zoom themselves are reset further down (after the
     unconditional .hero__bg rule that authors them at 13%/1.08, so this
     override actually wins the cascade instead of losing to source order). */
}
@media (max-width: 960px) {
  /* Below 960px .hero__grid stacks (panel goes under the text instead of
     beside it), so the hero's real content height is far taller than any
     fixed-height box could hold even with the pin already off above -
     .hero__panel was measured overflowing a 100vh box by ~590px on a
     375x812 viewport, meaning that entire panel (engagement style,
     delivery model, scale, reach) was permanently clipped and unreachable.
     overflow:visible is the only thing still missing once the grid stacks. */
  .hero { overflow: visible; }
}
.hero__bg {
  position: absolute;
  inset: 0;
  z-index: -1;
  overflow: hidden;
  background: linear-gradient(45deg, black, transparent);
  /* Driven by main.js on scroll: a short reveal-and-settle (clip inset shrinks
     to 0, zoom eases to 1) as the visitor starts scrolling past the hero.
     Reduced-motion / no-JS both resolve to the settled end state below.
     13% (not just enough to match the 1.08 zoom) so the starting frame reads
     visibly smaller/tighter, giving the reveal more distance to travel. */
  --hero-clip: 13%;
  --hero-zoom: 1.08;
}

/* The hero background is the looping valley footage - the same clip the intro
   curtain shrinks out of, so the curtain can hand over without a cut. Paint
   order is explicit: black (0) -> footage (1) -> scrim (2).

   Two nested boxes, on purpose. The inner .hero__photo carries the scroll
   reveal (clip-path + zoom), which is eased with a CSS transition so the
   discrete scroll steps read as continuous motion. The outer .hero__loop
   carries the loop-seam blur, which is driven per frame from the video's own
   currentTime and therefore must NOT be transitioned - a lag there would slide
   the blur off the seam it exists to hide. Separating them also stops the two
   scales from having to share a single `transform` declaration. */
.hero__loop {
  position: absolute;
  inset: 0;
  z-index: 1;
  /* Deliberately bare. A `filter` - even blur(0px) - is not `none`: it forces
     the browser to render this box into an offscreen surface and composite the
     result, for every frame of video, for the whole visit. The seam needs that
     for about a second and a half out of every eleven, so it is switched on
     for exactly that long and off again (below), rather than left declared
     with a neutral value. Same reasoning for transform and opacity. */
}
.hero__loop.is-seam {
  /* Blur hides detail, but not brightness: measured, the clip's last frame is
     ~20% brighter overall than its first, which even at full blur would pop as
     a step at the wrap. Sinking toward the black underneath brings both sides
     to nearly the same luminance at the moment the cut happens. Kept on this
     wrapper (not the photo) so it is not multiplied by the photo's own opacity
     var a second time - and dimming can't leak past a window anyway. */
  opacity: var(--loop-dim, 1);
}
/* The blur and the seam's push-in ride on the photo itself, not this wrapper:
   the photo carries the reveal window (clip-path: inset(--hero-clip)), and a
   filter/transform on the wrapper OUTSIDE that clip let the blur bleed out past
   the window into the letterbox margin on the framed starting frames. On the
   photo the blur is applied before its own clip in paint order, so the window
   crops it cleanly. main.js holds --loop-zoom at 1 while the hero is framed (the
   clip does the containing there); the 1.05 push-in returns only once the hero
   is full-bleed, where it exists to shove the blur's soft rim off-screen. */
.hero__loop.is-seam .hero__photo {
  filter: blur(var(--loop-blur, 0px));
  scale: var(--loop-zoom, 1);
}
.hero__photo {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
  object-position: var(--hero-photo-pos);
  opacity: var(--hero-photo-o);
  /* No colour filter here. It used to carry saturate(0.98) contrast(1.02) -
     a 2% adjustment nobody can see - which on a still costs nothing but on a
     video is a shader pass over every decoded frame, and is enough on its own
     to keep the element off the compositor's fast path for video. */
  clip-path: inset(var(--hero-clip));
  transform: scale(var(--hero-zoom));
  transition: clip-path var(--dur-slow) var(--ease-out), transform var(--dur-slow) var(--ease-out);
}
/* No-JS and reduced-motion never get a src (main.js assigns it), so the
   element renders its poster - a real frame of the clip - and nothing about
   the composition changes. */

/* Scrim: firm on the left where the headline and lede sit, opening up toward the
   right so the footage stays bright and legible - the friendly, daylit read.
   Contrast on the copy is measured against the brightest frame (open sky),
   with headroom to spare.

   Every stop below is 80% of the alpha it was authored at - the scrim as a
   whole lightened by a fifth, its shape and balance untouched. The mirror of
   this in animations.css (.intro__scrim--hero) has to carry the same numbers or
   the curtain's handoff stops being a match. Headline contrast at the darkest
   corner over the brightest frame goes from about 14.8:1 to about 13.5:1, so
   the headroom the paragraph above refers to is still there. */
.hero__bg::before {
  content: '';
  position: absolute;
  inset: 0;
  z-index: 2;
  background:
    linear-gradient(90deg, rgba(20,20,20,0.53) 0%, rgba(20,20,20,0.16) 46%, rgba(20,20,20,0.26) 74%, rgba(20,20,20,0.21) 100%), linear-gradient(180deg, rgba(20,20,20,0.56) 0%, rgba(20,20,20,0.13) 34%, rgba(20,20,20,0.24) 100%);
}
@media (prefers-reduced-motion: reduce) {
  .hero__bg { --hero-clip: 0%; --hero-zoom: 1; }
  .hero__photo { transition: opacity var(--dur-slow) var(--ease-out); }
}
@media (max-width: 1299px) {
  /* Below the pin's own 1299px breakpoint (further up this file): settle
     the video full-bleed and unzoomed instead of leaving it stuck on the
     reveal's starting frame - main.js no longer drives these on scroll at
     this width, so without this they'd stay frozen at the 13%/1.08 authored
     defaults. Placed after the unconditional .hero__bg rule on purpose so
     it wins the cascade tie (equal specificity resolves by source order). */
  .hero__bg { --hero-clip: 0%; --hero-zoom: 1; }
}
.hero__grid {
  display: grid;
  grid-template-columns: 1.1fr 0.9fr;
  gap: var(--space-7);
  align-items: center;
  /* Nudges the flex-centered position back onto the video's true centre - see
     --hero-vshift on .hero. 0px (the fallback, and what every context that
     doesn't define the variable resolves to) leaves this inert. */
  transform: translateY(var(--hero-vshift, 0px));
}
@media (max-width: 960px) {
  .hero__grid { grid-template-columns: 1fr; }
}
.hero__grid > * { min-width: 0; }
.hero__eyebrow { margin-bottom: var(--space-4); }
/* Scoped to the home hero only - sized down from the shared .h-display scale
   (--fs-4xl) so it doesn't compete as hard with the photo behind it. Tuned so
   the longest of the three translated titles (DE, then ES) still wraps to
   4 lines or fewer: max-width is in ch, which scales with font-size, so a
   smaller font alone doesn't change the wrap - both had to move together. */
.hero h1 { max-width: 20ch; font-size: var(--fs-2xl); }
.hero .lede { margin-top: var(--space-4); margin-bottom: var(--space-6); max-width: 42ch; }
.hero__actions {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-3);
}
@media (max-height: 700px) {
  /* Short viewports (small windows, landscape laptops): the hero is a fixed
     100vh box with overflow:hidden, so content taller than that gets clipped
     at the bottom. Claw back space on every axis rather than just padding -
     and stop centering (see .hero above): once content is this close to the
     box's own height, centering it would lift its top edge past padding-top,
     the clearance that keeps it out from behind the fixed header. Anchoring
     to flex-start keeps that edge fixed and pushes any remaining overflow to
     the bottom instead, which is the safer side to lose. Same reasoning kills
     --hero-vshift as it did above: flex-start already anchors to padding-top
     directly, so there's no padded-box-vs-full-box mismatch left to correct
     for, only content it would push further down. */
  .hero { padding-top: clamp(4.5rem, 9vw, 6rem); padding-bottom: var(--space-3); align-items: flex-start; --hero-vshift: 0px; }
  .hero__grid { gap: var(--space-4); }
  .hero h1 { font-size: var(--fs-2xl); }
  .hero .lede { margin-top: var(--space-2); margin-bottom: var(--space-4); }
  .hero__eyebrow { margin-bottom: var(--space-2); }
}
@media (max-height: 700px) and (max-width: 960px) {
  /* Below 960px the grid stacks (panel goes under the text), roughly
     doubling the content height that needs to fit - trim further. */
  .hero__panel { padding: var(--space-3); }
  .hero__panel-row { padding-block: var(--space-1); }
}
@media (max-height: 820px) {
  /* A separate, higher threshold from the 700px tightening above - on
     purpose: between 700 and ~800px, the content-tightening query above is
     already OFF (full-size padding/gap/heading), so untightened content
     needs close to the full 100vh to fit. Measured: full-size hero content
     bottoms out at ~672px regardless of viewport height (it's driven by
     width-based clamp()s, not height), so it only clears
     (100vh - marquee height) once 100vh is comfortably past ~800px. Below
     that, the peek is a nicety - drop it and give hero the full 100vh back;
     the marquee simply returns to appearing only once the visitor scrolls
     (as it did before this change). */
  .hero-pin-wrap { --hero-marquee-h: 0px; }
}

.hero__panel {
  position: relative;
  background: rgba(20,20,20,0.6);
  border: 1px solid var(--line-dark);
  border-radius: var(--radius-md);
  padding: var(--space-5) var(--space-3);
  -webkit-backdrop-filter: blur(6px);
  backdrop-filter: blur(6px);
}
.hero__panel h3 {
  font-family: var(--font-body);
  font-size: var(--fs-xs);
  letter-spacing: var(--tracking-wider);
  line-height: 1.3;
  text-transform: uppercase;
  color: var(--accent-bright);
  margin-bottom: var(--space-4);
}
.hero__panel-row {
  display: flex;
  justify-content: space-between;
  align-items: baseline;
  gap: var(--space-3);
  padding-block: var(--space-2);
  border-top: 1px solid var(--line-dark);
  font-size: var(--fs-sm);
}
.hero__panel-row:first-of-type { border-top: none; }
.hero__panel-row strong {
  font-family: var(--font-display);
  color: var(--paper-text);
  /* Still fluid, but a size up from where it was (was 0.75-0.9375rem): the
     panel read as too small. The longer DE/ES values now wrap to two lines
     at the narrow end of the two-column hero rather than being shrunk to
     fit on one - line-height is pulled in so a wrapped value stays a tight
     pair, not a loose stack. */
  font-size: clamp(0.9375rem, 0.6vw + 0.6rem, 1.125rem);
  line-height: 1.3;
  text-align: right;
  flex: 1 1 auto;
}
.hero__panel-row span {
  color: var(--paper-text-muted);
  flex: 0 1 auto;
  max-width: 44%;
  line-height: 1.3;
  /* Deliberately a step smaller than the value at every width - a
     caption/stat pairing rather than two runs of equally-weighted text,
     which helps the longer DE/ES labels sit next to the value. Bumped
     alongside strong (was 0.6-0.75rem). */
  font-size: clamp(0.75rem, 0.45vw + 0.55rem, 0.875rem);
}

/* ---------------- Page hero (interior pages, shorter) ----------------
   Same photo-under-scrim construction as .hero: photo (0) -> scrim+glow (1) ->
   content (2). The scrim is weighted to the left, where the eyebrow/h1/lede sit. */
.page-hero {
  background: var(--charcoal);
  color: var(--paper-text);
  padding-top: clamp(8rem, 14vw, 10rem);
  padding-bottom: var(--space-7);
  position: relative;
  overflow: hidden;
  isolation: isolate;
}
.page-hero__photo {
  position: absolute;
  inset: 0;
  z-index: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
  object-position: 50% 20%;
  opacity: 0.88;
  filter: saturate(1.05) contrast(1.02);
}
.page-hero::before {
  content: '';
  position: absolute; inset: 0; z-index: 1;
  background:
    radial-gradient(ellipse 55% 70% at 88% 4%, rgba(var(--glow-rgb), 0.18), transparent 62%),
    linear-gradient(90deg, rgba(20,20,20,0.93) 0%, rgba(20,20,20,0.84) 54%, rgba(20,20,20,0.54) 72%, rgba(20,20,20,0.26) 100%),
    linear-gradient(180deg, rgba(20,20,20,0.48) 0%, rgba(20,20,20,0.12) 42%, rgba(20,20,20,0.66) 100%);
}
.page-hero .container { position: relative; z-index: 2; }
.page-hero h1 { max-width: 20ch; }
.page-hero .lede { margin-top: var(--space-4); max-width: 60ch; }
@media (max-width: 640px) {
  .page-hero__photo { opacity: 0.5; }
}

/* ---------------- Two-column split ---------------- */
.split {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: var(--space-8);
  align-items: center;
}
@media (max-width: 900px) {
  .split { grid-template-columns: 1fr; gap: var(--space-6); }
}
.split--reverse .split__media { order: 2; }
@media (max-width: 900px) {
  .split--reverse .split__media { order: 0; }
}
.split > * { min-width: 0; }

/* ---------------- Grid layouts ---------------- */
.grid-2 { display: grid; grid-template-columns: repeat(2, 1fr); gap: var(--space-5); }
.grid-3 { display: grid; grid-template-columns: repeat(3, 1fr); gap: var(--space-5); }
.grid-4 { display: grid; grid-template-columns: repeat(4, 1fr); gap: var(--space-4); }
.grid-2 > *, .grid-3 > *, .grid-4 > * { min-width: 0; }
@media (max-width: 960px) {
  .grid-3 { grid-template-columns: repeat(2, 1fr); }
  .grid-4 { grid-template-columns: repeat(2, 1fr); }
}
@media (max-width: 640px) {
  .grid-2, .grid-3, .grid-4 { grid-template-columns: 1fr; }
}

.section-head {
  max-width: 640px;
  margin-bottom: var(--space-7);
}
.section-head.text-center { margin-inline: auto; }

/* ---------------- Art panels ----------------
   Two variants, used deliberately: a crest/icon panel where the page needs
   restraint, and a photographic panel where it needs presence. Mixing them is
   the point - the site should not read as a photo gallery. */
.art-panel {
  position: relative;
  aspect-ratio: 4 / 5;
  border-radius: var(--radius-md);
  overflow: hidden;
  background: var(--charcoal);
  display: flex;
  align-items: center;
  justify-content: center;
}
.art-panel svg { width: 46%; height: 46%; opacity: 0.9; }
.art-panel--paper {
  background: linear-gradient(160deg, var(--paper-2), var(--paper));
  border-color: var(--line);
}

/* Photographic variant. The image slow-drifts on hover; the accent wash keeps
   the photo tied to the brand rather than floating free of it. */
.art-panel--photo { background: var(--charcoal); }
.art-panel__img {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
  transform: scale(1.02);
  transition: transform var(--dur-slow) var(--ease-out);
  filter: saturate(0.88) contrast(1.04);
}
.art-panel--photo::after {
  content: '';
  position: absolute;
  inset: 0;
  background:
    linear-gradient(180deg, transparent 45%, rgba(20, 20, 20, 0.55) 100%),
    radial-gradient(ellipse 90% 70% at 50% 100%, rgba(var(--glow-rgb), 0.16), transparent 70%);
  pointer-events: none;
}
.art-panel--photo:hover .art-panel__img { transform: scale(1.06); }

@media (prefers-reduced-motion: reduce) {
  .art-panel__img { transition: none; }
  .art-panel--photo:hover .art-panel__img { transform: scale(1.02); }
}

/* ---------------- CTA band ---------------- */
.cta-band {
  background: var(--charcoal);
  color: var(--paper-text);
  border-radius: var(--radius-md);
  padding: var(--space-8) var(--space-6);
  text-align: center;
  position: relative;
  overflow: hidden;
  isolation: isolate;
}
.cta-band::before {
  content: '';
  position: absolute; inset: 0; z-index: -1;
  background: radial-gradient(ellipse 60% 80% at 50% 0%, rgba(var(--glow-rgb), 0.16), transparent 70%);
}
.cta-band h2 { max-width: 20ch; margin-inline: auto; }
.cta-band .lede { margin-inline: auto; margin-top: var(--space-3); margin-bottom: var(--space-5); }
.cta-band__actions { display: flex; justify-content: center; gap: var(--space-3); flex-wrap: wrap; }

/* ---------------- Utility ---------------- */
.flow > * + * { margin-top: var(--space-4); }
.mt-0 { margin-top: 0 !important; }
