/* ==========================================================================
   HERO GALLERY — experimental prototype styling (hero-gallery.html only).
   Fully scoped under .gal — never touches production hero-timeline styles.
   ========================================================================== */

/* Opt-in only (via .gal-standalone on <html>/<body> in hero-gallery.html) —
   NOT a bare html/body selector, since this stylesheet is now also loaded
   by index-v2.html where the gallery is embedded above normal scrolling
   page content and the page must scroll normally.
   On the standalone gallery page, this locks it to exactly one viewport —
   the whole point of the wheel gesture is to rotate the 3D carousel,
   never to scroll the page. Without it, a page height even 1-2px taller
   than the viewport (e.g. a late font swap on the italic industry
   headline) lets the browser scroll — which produced the "loads slightly
   scrolled, first wheel scrolls the PAGE up to the real composition" bug. */
html.gal-standalone, body.gal-standalone { height: 100%; overflow: hidden; }

.gal-back {
  position: fixed;
  left: 24px;
  bottom: 22px;
  z-index: 200;
  font-family: var(--font-sans);
  font-size: 13px;
  color: var(--warm-gray);
  text-decoration: none;
  padding: 8px 14px;
  border: 1px solid rgba(168,170,172,.2);
  border-radius: 999px;
  background: rgba(5,6,6,.6);
  backdrop-filter: blur(10px);
  transition: color 200ms ease, border-color 200ms ease;
}
.gal-back:hover { color: var(--ivory); border-color: rgba(168,170,172,.4); }

.gal-tag {
  position: fixed;
  right: 24px;
  bottom: 22px;
  z-index: 200;
  font-family: var(--font-sans);
  font-size: 11px;
  letter-spacing: .08em;
  text-transform: uppercase;
  color: var(--warm-gray);
  padding: 7px 14px;
  border: 1px solid rgba(230,178,90,.28);
  border-radius: 999px;
  background: rgba(5,6,6,.6);
  backdrop-filter: blur(10px);
}

.gal {
  position: relative;
  min-height: 100svh;
  /* No hero-only background here on purpose — the global site background
     (css/base.css, painted on <body>) shows straight through, so the hero
     reads as one continuous field with the rest of the page instead of a
     separate lit "stage". */
  background: transparent;
  overflow: hidden;
  padding-top: var(--nav-h);
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: flex-start;
}

/* ---------- Atmosphere ----------
   Just a light bottom scrim (for text/card legibility) and a grain layer —
   no stage-photo-specific vignette any more, since there's no stage photo
   left to tame. */
.gal__atmosphere { position: absolute; inset: 0; pointer-events: none; z-index: 0; }
.gal__glow { display: none; }
.gal__vignette {
  position: absolute; inset: 0;
  background: linear-gradient(to bottom, transparent 70%, rgba(5,6,6,.4) 100%);
}
.gal__noise {
  position: absolute; inset: 0;
  opacity: .035;
  mix-blend-mode: overlay;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='120' height='120'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.9' numOctaves='2' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23n)'/%3E%3C/svg%3E");
}

/* ---------- Dust-mote particle field ----------
   Canvas driven by js/hero-particles.js. Sits ABOVE the atmosphere/
   vignette/noise (z-index 0) but BELOW the headline and card stage
   (z-index 2), so it drifts over the dark background without ever
   competing with readable content. pointer-events:none so it never
   intercepts the wheel/click/touch input the carousel depends on. */
.gal__particles {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  z-index: 1;
  pointer-events: none;
}

/* ---------- Intro text ----------
   Deliberately compact — the gallery, not the headline, is the main
   visual event. Its own max-width keeps it a normal content column even
   though the gallery stage below is nearly full viewport width. */
.gal__intro {
  position: relative;
  z-index: 2;
  text-align: center;
  /* Nav bottom → main heading gap. This is now the full gap (no eyebrow
     above the heading any more), so it's sized on its own merits rather
     than as a small top-up to an eyebrow's margin. */
  padding: clamp(14px, 1.4vw, 22px) 24px 0;
  max-width: 900px;
  flex: 0 0 auto;
}
.gal__title {
  font-family: var(--font-serif);
  /* Sized to fit the full sentence on one line at desktop widths rather
     than wrapping — see white-space:nowrap below. Narrower viewports
     scale the font down via the vw term before nowrap would ever force
     horizontal overflow (and the vw component keeps working normally at
     the smaller breakpoints where wrapping is acceptable again). */
  font-size: clamp(18px, 2.15vw, 32px);
  line-height: 1.05;
  color: var(--ivory);
  font-weight: 500;
  margin: 0 0 8px;
  white-space: nowrap;
}
.gal__title-accent { color: rgba(232, 236, 240, .92); }
.gal__sub {
  font-family: var(--font-sans);
  font-size: 12px;
  color: var(--warm-gray);
  letter-spacing: .02em;
  /* Gap down to the dynamic industry headline — kept tight, this is a
     continuation of the same typography block, not a separated section. */
  margin: 0 0 clamp(6px, .6vw, 10px);
}

/* ---------- Dynamic industry title ----------
   Page typography, not card content: one shared, editorial title between
   the main headline block and the gallery stage, updated by JS whenever
   the active center card changes. Fixed min-height so a short vs. long
   title never shifts the gallery below it — the crossfade animates
   opacity/translateY/blur only, never height. */
.gal__industry {
  position: relative;
  display: flex;
  align-items: center;
  justify-content: center;
  min-height: clamp(40px, 4.4vw, 58px);
  /* The full headline → card-stage gap is owned by .gal__stage's
     margin-top alone now (see below) — this box no longer adds its own
     separate margin on top of that, which is what was compounding into
     an oversized empty area above the cards. */
  margin: 0;
}
.gal__industry-headline {
  position: relative;
  display: inline-block;
  white-space: nowrap;
  width: max-content;
  max-width: none;
  opacity: 1;
  transform: translateY(0);
  filter: blur(0);
  transition: opacity 340ms cubic-bezier(0.22, 1, 0.36, 1),
              transform 340ms cubic-bezier(0.22, 1, 0.36, 1),
              filter 340ms cubic-bezier(0.22, 1, 0.36, 1);
}
.gal__industry.is-switching .gal__industry-headline {
  opacity: 0;
  transform: translateY(-5px);
  filter: blur(2px);
}
.gal__industry.is-entering .gal__industry-headline {
  opacity: 0;
  transform: translateY(6px);
  filter: blur(2px);
}

/* Elegant editorial signature typography — premium display italic, not a
   casual/wedding script. One shared system for all five industries (same
   font, size, weight, animation); only the two-tone accent color and the
   words themselves change per industry (set via JS + --gal-accent-rgb on
   #gal-industry). No underline/line accent — the type itself carries it. */
.gal__ind-a,
.gal__ind-b {
  font-family: "Instrument Serif", var(--font-serif), serif;
  font-style: italic;
  font-weight: 400;
  font-size: clamp(34px, 2.5vw, 52px);
  line-height: 1;
  letter-spacing: 0;
}
/* headlineA (the "expressive" half) stays a fixed titanium white across
   every industry — deliberately NOT tied to any per-industry variable —
   so only headlineB carries the identity color change. */
.gal__ind-a { color: rgba(222, 229, 236, .92); margin-right: .28em; }
.gal__ind-b {
  color: rgba(var(--gal-title-b-rgb, 255, 255, 255), .88);
  transition: color 400ms cubic-bezier(0.22, 1, 0.36, 1);
}

/* ---------- Scene / Stage ----------
   .gal__stage is the perspective camera (100vw-safe, no hidden overflow
   width). .gal__track is the preserve-3d group cards live in — its
   overflow is intentionally visible so card glow/shadow is never clipped,
   while each card's own INTERNAL hero content still clips to its frame
   (see .gal__card-frame). Card left/width/height are computed once by JS
   from the real viewport (layoutCards()) — never touched during hover —
   so interaction only ever animates `transform` on `.gal__card-3d`. */
.gal__stage {
  position: relative;
  z-index: 2;
  /* ROOT CAUSE of the old "huge empty gap + cards stuck near the bottom":
     this was `flex: 1 1 auto`, which made the stage grow to consume ALL
     leftover vertical space in the `.gal` column (min-height:100svh) up
     to the viewport bottom, then `align-items:center` vertically centered
     the (shorter) track inside that oversized box — pushing the visual
     card position down near the middle of the remaining viewport instead
     of anchoring it right under the typography. Sizing to content
     (flex: 0 0 auto) makes the stage exactly as tall as the track/cards
     it holds, so it simply follows the headline with ONE explicit gap. */
  flex: 0 0 auto;
  display: flex;
  align-items: center;
  justify-content: center;
  /* Full viewport width (minus a small edge margin), matching the PSD
     reference composition where the far cards sit almost flush with the
     canvas edges (44px margin on a 2560px canvas = ~1.7%) — not capped
     at a fixed max-width, which used to leave the V noticeably narrower
     than the page on wide screens. */
  width: calc(100vw - 40px);
  max-width: 100%;
  /* The one explicit, controlled gap between the dynamic industry
     headline and the top of the center card — kept small and deliberate,
     not a large empty separation. */
  margin: clamp(12px, 1.6vh, 24px) auto 0;
  padding: 4px 0 0;
  /* Camera pulled well back (was 1500px, then 1900px) so the center card
     no longer reads as a close-up telephoto shot — see .gal__track's
     translateZ "world" pull-back below for the other half of that
     effect. */
  perspective: 2100px;
  perspective-origin: 50% 48%;
  overflow: visible;
}
/* No CSS-simulated key light here any more — the real stage photo on
   .gal already has this exact spotlight baked in at the right position,
   so a second glow drawn behind the stage would just double it up. */

.gal__loading {
  position: absolute;
  inset: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 10px;
  font-family: var(--font-sans);
  font-size: 12px;
  letter-spacing: .1em;
  text-transform: uppercase;
  color: var(--warm-gray);
  opacity: 1;
  transition: opacity 400ms ease;
  pointer-events: none;
}
.gal.is-ready .gal__loading { opacity: 0; }
.gal__loading-dot {
  width: 6px; height: 6px; border-radius: 50%;
  background: var(--warm-gray);
  animation: galLoadingPulse 1.1s ease-in-out infinite;
}
@keyframes galLoadingPulse { 0%,100% { opacity: .25; } 50% { opacity: 1; } }

.gal__track {
  position: relative;
  width: 100%;
  /* Independent of card size on purpose — a generous, dedicated stage
     volume (not just "as tall as the card") is what reads as a spacious
     exhibition room instead of a tight crop around one card. */
  height: clamp(500px, 58vh, 650px);
  transform-style: preserve-3d;
  /* the "world" pulled back from the camera (translateZ, unchanged) PLUS
     the whole group moved as one unit (translateY) to balance the gap
     under the headline/dynamic industry title. Relative card-to-card
     geometry (rot/tz/tx/ty per card, computed in JS) is completely
     untouched by this — every card just inherits the same shift. Eased
     back from -70px to -30px so the new .gal__industry title row has
     visible breathing room above the stage instead of colliding with it. */
  transform: translateZ(-140px) translateY(-30px);
  opacity: 0;
  transition: opacity 500ms cubic-bezier(0.22, 1, 0.36, 1);
}
.gal.is-ready .gal__track { opacity: 1; }

/* Floor reflection glow + its animated light-sweep removed entirely per
   request — trimmed as dead render weight on the hero (a blurred,
   animated mix-blend-mode layer under 5 cards) once the card mirrors
   themselves (-webkit-box-reflect) were already gone. The markup node
   (.gal__floor, aria-hidden) is left in the HTML but renders nothing. */
.gal__floor { display: none; }

/* ---------- Card ----------
   All five cards sit at the same stationary anchor point (dead center of
   the stage) but the `transform` below moves EACH card to wherever it
   currently reads visually (left-far/near/center/right-near/far), and
   pointer events are hit-tested against that same transformed box — so a
   side card's hover/click target always matches where it's actually
   drawn, with no separate untransformed hitbox to drift out of sync.
   Only `transform` (and filter/opacity on descendants) ever changes on
   interaction — never width/height/margin/gap — so switching the active
   card causes zero layout reflow.
   --gal-rot/--gal-tz/--gal-tx/--gal-scale (this card's V position for its
   current offset from the active hero) and --gal-hover-tz (a small
   forward nudge on side-card hover preview only) are written by JS. */
.gal__card {
  position: absolute;
  left: 50%;
  top: 50%;
  transform-style: preserve-3d;
  backface-visibility: hidden;
  will-change: transform;
  cursor: pointer;
  z-index: 1;
  transform:
    translate(-50%, -50%)
    translate3d(var(--gal-tx, 0px), var(--gal-ty, 0px), calc(var(--gal-tz, 0px) + var(--gal-hover-tz, 0px)))
    rotateY(var(--gal-rot, 0deg))
    scale(var(--gal-scale, 1));
  /* No CSS transition on transform any more — hero-gallery.js now writes
     --gal-rot/--gal-tz/--gal-ty/--gal-tx/--gal-scale every rAF frame as
     part of its own continuous easing (position → targetPosition). A
     fixed-duration CSS transition on top of per-frame JS writes would
     fight that: each frame's write would itself try to animate over 980ms,
     compounding into stutter instead of one smooth motion. */
}
/* Stacking order is NOT driven by these tier classes any more — see
   renderFrame()'s inline `card.style.zIndex` write in hero-gallery.js.
   A discrete near/side/active z-index swap here used to flip the moment
   nearestIndex changed (the halfway point of a card's move), while the
   card's own translateZ was still continuously interpolating — so the
   incoming card would visually pop in front of the outgoing one before
   it had actually finished moving forward, reading as a jump. Setting
   z-index every frame from the same continuous depth value the
   transform already uses keeps stacking order and visual depth in sync
   at every point of the motion, not just at rest. */

.gal__card-frame {
  position: absolute;
  inset: 0;
  overflow: hidden;
  border-radius: 12px;
  /* Layered titanium surface, not a flat fill: a cool radial catch-light
     sitting in the top-left (where the frame's own metallic highlight
     lands) over a graphite base that darkens diagonally toward the
     bottom-right, reading as a panel with real physical depth rather
     than a solid gray card. */
  border: 1px solid rgba(200,206,214,.16);
  background:
    radial-gradient(135% 150% at 14% 8%, rgba(215,222,230,.07), transparent 55%),
    linear-gradient(152deg, #121316 0%, #0b0c0d 46%, #060707 100%);
  box-shadow: 0 30px 70px -20px rgba(0,0,0,.65),
              inset 0 1px 0 rgba(255,255,255,.05),
              inset 0 -1px 0 rgba(0,0,0,.5);
  transition: border-color 980ms cubic-bezier(0.65, 0, 0.35, 1),
              box-shadow 980ms cubic-bezier(0.65, 0, 0.35, 1),
              filter 300ms cubic-bezier(0.22, 1, 0.36, 1);
}
/* Cool metallic edge highlight, top/upper-left only — present on every
   card (active included) at low opacity; near/side tiers additionally
   layer in the inward rim light below via --gal-rim-gradient. */
.gal__card-frame::before {
  content: "";
  position: absolute;
  inset: 0;
  pointer-events: none;
  z-index: 3;
  border-radius: inherit;
  background: linear-gradient(135deg, rgba(255,255,255,.16) 0%, transparent 24%), var(--gal-rim-gradient, none);
  opacity: .45;
}
/* Very subtle static sheen — a single fixed diagonal streak, not an
   animated sweep. Painted once, never repositioned, so it costs nothing
   during motion (unlike the old infinitely-animated glass-sheen sweep). */
.gal__card-frame::after {
  content: "";
  position: absolute;
  inset: 0;
  pointer-events: none;
  z-index: 3;
  border-radius: inherit;
  background: linear-gradient(115deg, transparent 46%, rgba(255,255,255,.06) 52%, transparent 60%);
  opacity: .6;
}
.gal__card.is-active .gal__card-frame::after { opacity: .85; }
.gal__card.is-active .gal__card-frame {
  border-color: rgba(242, 246, 250, .88);
  box-shadow: 0 50px 110px -18px rgba(0,0,0,.75),
              0 0 46px -12px rgba(210, 220, 230, .22),
              inset 0 1px 0 rgba(255,255,255,.14),
              inset 0 -1px 0 rgba(0,0,0,.55),
              inset 0 0 0 1px rgba(255,255,255,.06);
}
.gal__card.is-active .gal__card-frame::before { opacity: .7; }
/* Rear-card frames: a refined, neutral brushed-silver/metallic edge —
   NOT the industry accent color, and not a glow. This is what makes the
   rear cards read as physical display panels standing out from the black
   background, while the ACTIVE card alone keeps its own accent-colored
   border/glow above. Near cards get a marginally stronger silver than
   far cards, matching their closer position in the depth stack. */
.gal__card.is-near .gal__card-frame {
  border-color: rgba(210,217,225,.60);
  box-shadow: 0 30px 70px -20px rgba(0,0,0,.65),
              inset 0 1px 0 rgba(255,255,255,.08),
              inset 0 -1px 0 rgba(0,0,0,.5),
              0 0 16px rgba(190,200,215,.05);
}
.gal__card.is-side .gal__card-frame {
  border-color: rgba(210,217,225,.40);
  box-shadow: 0 26px 60px -18px rgba(0,0,0,.6),
              inset 0 1px 0 rgba(255,255,255,.05),
              inset 0 -1px 0 rgba(0,0,0,.45);
}
.gal__card.is-near .gal__card-frame::before,
.gal__card.is-side .gal__card-frame::before {
  opacity: .55;
}
/* Very subtle neutral rim light on the edge facing the center stage —
   points each rear card visually toward the middle without any neon
   outline. Left-side cards (offset < 0) get a brighter right edge;
   right-side cards get a brighter left edge. Layered into ::before's
   background as a second image (see --gal-rim-gradient above) alongside
   the top-left metallic highlight every card already has. */
.gal__card.is-left .gal__card-frame {
  --gal-rim-gradient: linear-gradient(to right, transparent 82%, rgba(255,255,255,.10) 100%);
}
.gal__card.is-right .gal__card-frame {
  --gal-rim-gradient: linear-gradient(to left, transparent 82%, rgba(255,255,255,.10) 100%);
}
/* subtle hover preview cue on a SIDE card only — never full activation,
   just a brightness/border nudge plus the small translateZ set via
   --gal-hover-tz on .gal__card-3d */
.gal__card.is-hover-preview:not(.is-active) .gal__card-frame {
  border-color: rgba(226, 231, 236, .58);
  box-shadow: 0 30px 70px -20px rgba(0,0,0,.65), 0 0 26px -10px rgba(210, 220, 230, .14);
}
.gal__card.is-hover-preview:not(.is-active) .gal__card-frame {
  /* applied to the frame (parent), not live/preview directly, so it
     composites on top of their own image layers instead of
     overwriting them */
  filter: brightness(1.15);
}

/* ---------- Static full-color screenshot ----------
   .gal__card-preview is a real <img>, permanently visible — see
   images/hero-gallery-frame-exports/. There is no live Hero DOM, no
   crossfade, nothing mounted or torn down per card; the center card is
   emphasized purely through the brightness/opacity/frame rules below. */
.gal__card-frame .gal__card-preview {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  display: block;
  object-fit: cover;
  object-position: 50% 50%;
  /* The frame-export PNGs are rendered on a canvas with a safe margin
     baked in around the hero composition (measured: at the same
     object-fit:cover size, the exported PNG's own content only fills
     ~80-85% of its canvas, leaving a visible letterboxed border). Scaling
     the image up crops that baked-in margin away so the screenshot reads
     as a full-bleed shot filling the card, not a smaller image floating
     inside a black mat.
     Reported: the composition still sat too high after that scale alone
     — object-position has no further effect here since the transform's
     own scale re-centers on the element's box regardless of it, so the
     vertical nudge has to be a translateY on the already-scaled image
     instead, pushing the visible composition down toward true center. */
  transform: scale(1.2) translateY(1.5%);
  transform-origin: center;
  transition: opacity 300ms cubic-bezier(0.22, 1, 0.36, 1), filter 300ms cubic-bezier(0.22, 1, 0.36, 1);
}
/* Center card: full brightness/opacity — the frame/border/shadow rules
   above are what set it apart, not the image itself. */
.gal__card.is-active .gal__card-frame .gal__card-preview {
  opacity: 1;
  filter: brightness(1.04);
}
/* Near cards: clearly visible, only slightly dimmer than center. */
.gal__card.is-near .gal__card-frame .gal__card-preview {
  opacity: .9;
  filter: brightness(.92);
}
/* Far cards: still fully readable, never fading into the background. */
.gal__card.is-side .gal__card-frame .gal__card-preview {
  opacity: .78;
  filter: brightness(.82);
}

/* Keep expensive paint effects dormant while the cards are transforming. */
.gal.is-moving .gal__card-frame,
.gal.is-moving .gal__card-frame .gal__card-preview { transition: none; }

/* Soft reflection beneath each card — removed per feedback, it read as an
   artificial pasted-on light pool rather than a natural surface effect. */

@media (min-width: 1181px) {
  .gal__card-frame {
    box-shadow:
      0 30px 70px -20px rgba(0,0,0,.65),
      inset 0 1px 0 rgba(255,255,255,.05),
      inset 0 -1px 0 rgba(0,0,0,.5),
      0 34px 26px -22px rgba(0,0,0,.5);
  }
  .gal__card.is-near .gal__card-frame {
    box-shadow:
      0 30px 70px -20px rgba(0,0,0,.65),
      inset 0 1px 0 rgba(255,255,255,.08),
      inset 0 -1px 0 rgba(0,0,0,.5),
      0 0 16px rgba(190,200,215,.05),
      0 34px 26px -22px rgba(0,0,0,.5);
  }
  .gal__card.is-active .gal__card-frame {
    box-shadow:
      0 50px 110px -18px rgba(0,0,0,.75),
      0 0 46px -12px rgba(210, 220, 230, .22),
      inset 0 1px 0 rgba(255,255,255,.14),
      inset 0 -1px 0 rgba(0,0,0,.55),
      inset 0 0 0 1px rgba(255,255,255,.06),
      0 40px 30px -20px rgba(0,0,0,.55);
  }
}

/* ---------- Bottom status nav ---------- */
.gal__status {
  position: absolute;
  left: 50%;
  bottom: 22px;
  transform: translateX(-50%);
  z-index: 4;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 10px;
  pointer-events: none;
}
.gal__status-dots { display: flex; gap: 7px; }
.gal__status-dot {
  width: 5px; height: 5px; border-radius: 50%;
  background: rgba(168,170,172,.35);
  transition: background 400ms cubic-bezier(0.22, 1, 0.36, 1), transform 400ms cubic-bezier(0.22, 1, 0.36, 1);
}
.gal__status-dot.is-active {
  background: rgba(238, 242, 246, .95);
  box-shadow: 0 0 8px rgba(220,230,240,.55);
  transform: scale(1.4);
}
.gal__status-hint {
  font-family: var(--font-sans);
  font-size: 10px;
  letter-spacing: .16em;
  text-transform: uppercase;
  color: var(--warm-gray);
  opacity: .8;
  /* The touch copy ("Zum Wechseln der Branchen wischen") wraps to two
     lines on narrow phones — without this, the shrink-to-fit box (sized
     to its widest line) still centers as a whole via .gal__status's
     flex, but the wrapped line inside it reads left-flush instead of
     centered under the line above it. */
  text-align: center;
  /* The real stage photo's floor reflection can be bright right where
     this hint sits — a solid shadow keeps it legible regardless of what
     part of the photo is behind it, instead of relying on the vignette. */
  text-shadow: 0 1px 3px rgba(0,0,0,.9), 0 0 14px rgba(0,0,0,.7);
}
/* Wheel copy by default; swapped for the swipe copy on touch/coarse-
   pointer devices (phones/tablets) — same rotation gesture either way,
   just worded for the input that's actually available. */
.gal__status-hint-touch { display: none; }
@media (pointer: coarse) {
  .gal__status-hint-wheel { display: none; }
  .gal__status-hint-touch { display: inline; }
}

/* The live industry artwork keeps its own imagery, but its atmospheric
   color wash belongs to the gallery system. Keep that wash neutral so the
   five cards share the same black, silver and titanium visual language. */
.gal .ghero .fc-glow {
  background: radial-gradient(
    ellipse 48% 44% at calc(50% + var(--glow-x, 0px)) calc(44% + var(--glow-y, 0px)),
    rgba(214, 224, 234, .18) 0%,
    rgba(144, 158, 172, .08) 44%,
    transparent 72%
  ) !important;
}

/* ---------- Responsive: horizontal scroll gallery ----------
   Below 1180px, JS switches layoutMode to "scroll" and stops writing 3D
   custom properties — this CSS handles the resulting static/scrollable
   presentation. */
/* Below 1180px this used to switch to a flat horizontal scroll-snap
   strip (perspective:none, transform:none, fixed vw-based width) instead
   of the 3D carousel. The gallery is now the same V-shaped 3D carousel
   at every width — computeGeometry()/layout() in hero-gallery.js size
   and position every card via JS at all viewports, touch-swipe drives
   rotation the same way wheel does on desktop — so this block is just
   spacing, never a different presentation. */
@media (max-width: 1180px) {
  /* Desktop forces a full-viewport-tall hero (min-height:100svh) for the
     cinematic composition, but the mobile/tablet card fan is much
     shorter than a full screen — with the 100svh floor still in effect
     that left a huge dead gap below the cards, and pushed the
     absolutely-positioned "Klassische Ansicht" toggle (anchored to
     .gal's own bottom edge) far down past it. Letting the section
     shrink to its actual content height fixes both at once. */
  .gal { min-height: auto; }
  .gal__stage { padding: 8px 0 76px; }
  /* Raised well above the toggle button below — both .gal__status
     (bottom-anchored inside .gal__stage) and .hero-toggle-btn (bottom-
     anchored inside .gal, which now ends at the same edge as the stage
     once the section shrinks to content height) were converging on the
     same bottom line and overlapping. */
  .gal__status { bottom: 64px; }
  /* Reflection system fully removed on mobile/tablet — cards only, no
     -webkit-box-reflect, no layered titanium gradient, no ::before
     highlight, no animated ::after glass sheen. -webkit-box-reflect
     itself is never DECLARED below 1181px in the first place any more
     (see the desktop-only `@media (min-width: 1181px)` wrapper around
     the "Cinematic card reflections" rules above) rather than declared
     here and "removed" — that override reliably failed in testing (see
     the note up there for why). Background/pseudo-elements don't have
     that problem, so they're still safely reset with a plain override
     here. */
  .gal__card-frame {
    background: #0a0b0c !important;
  }
  .gal__card-frame::before { display: none !important; }
  .gal__card-frame::after { display: none !important; animation: none !important; }
}

/* No stage photo on phones — matches the JS phone-geometry branch's own
   700px cutoff (computePhoneGeometry). The image is a large, desktop-
   composed shot (the horizon light band and floor are framed for a wide
   stage); on a narrow screen it either crops away the parts that make
   it read as a stage at all or just adds a heavy download for a
   background that barely shows behind the cards anyway. Falls back to
   the plain dark ground color — .gal__vignette/.gal__noise/.gal__floor
   (all cheap CSS, not photos) still carry the atmosphere. */
@media (max-width: 700px) {
  .gal { background-image: none; }
  /* #hero-toggle-btn ("Klassische Ansicht") is styled in index.html's own
     inline <style> block with a fixed `left: 4vw` (matches the nav
     logo's left edge on desktop) — on a narrow phone that reads as
     off-center against the now-centered status dots/hint above it, so
     center it here to match. Only exists on index.html/index-v2.html
     (the gallery embedded in the real homepage); hero-gallery.html has
     no such button, so this rule simply never matches there. */
  .hero-toggle-btn {
    left: 50% !important;
    transform: translateX(-50%);
  }
}

@media (max-width: 640px) {
  .gal { padding-top: calc(var(--nav-h) + 6px); }
  .gal__intro { padding: 16px 20px 0; }
}

@media (prefers-reduced-motion: reduce) {
  .gal__card, .gal__card-frame, .gal__card-preview {
    transition-duration: 1ms !important;
  }
  .gal__card-frame::after,
  .gal__floor::after {
    animation: none !important;
  }
}
