/*3D Tunnel Experience*/

.tunnel-experience *,
.tunnel-experience *::before,
.tunnel-experience *::after {
  margin: 0;
  padding: 0;
  box-sizing: border-box;
}

/* .tunnel-experience IS the <body> element — body_class() puts the class there
   in WordPress, and index.html does the same locally. These rules used to be
   written as ".tunnel-experience html, .tunnel-experience body", which can
   never match (there is no html/body INSIDE body), so the reset silently did
   nothing and the theme's own body margin was left in place. */
.tunnel-experience {
  margin: 0;
  padding: 0;
  width: 100%;
  min-height: 100vh;
  /* dvh tracks the viewport as a mobile browser's URL bar collapses and
     returns; vh above is the large-viewport value every browser understands
     and is what non-dvh browsers keep. */
  min-height: 100dvh;
  overflow: hidden;          /* body overflow propagates to the viewport */
  /* Stops the pull-to-refresh and the rubber-band bounce that a vertical drag
     on the canvas would otherwise trigger on Android and iOS. touch-action
     below cancels the gesture; this cancels the CHAINING of it to the browser
     itself, which touch-action does not cover. */
  overscroll-behavior: none;
  background: #0d0e11;
  -webkit-tap-highlight-color: transparent;
  touch-action: none;
  /* A phone rotated mid-visit must not have its type rescaled by the OS: the
     loading screen is sized from the viewport and would jump. */
  -webkit-text-size-adjust: 100%;
  text-size-adjust: 100%;

  --accent: #1E5AA8;   /* Adrosonic deep blue */
  --ink: rgba(255, 255, 255, 0.92);
  --ink-dim: rgba(255, 255, 255, 0.45);
  --font: 'Space Grotesk', 'Arial', sans-serif;

  /* Design-system tokens, named as the Figma file names them. */
  --adro-electric: #00CCFF;   /* ADROSONIC Electric blue — Secondary */
  --adro-white:    #FFFFFF;   /* ADROSONIC White-Base */
  /* The scroll prompt is specified in Montserrat, not the Space Grotesk the
     rest of the page uses. Space Grotesk is the fallback so the hint still
     renders in a sane face if the Montserrat request is ever dropped. */
  --font-hint: 'Montserrat', 'Space Grotesk', 'Arial', sans-serif;
  /* The loading screen is set in Zalando Sans Expanded, the display face the
     design file specifies. Space Grotesk falls back into its place if the
     Google Fonts request is ever dropped. */
  --font-display: 'Zalando Sans Expanded', 'Space Grotesk', 'Arial', sans-serif;
}

.tunnel-experience .webgl {
  display: block;
  width: 100%;
  height: 100%;
  touch-action: none;
}


/* ------------------------------------------------------------
   SITE LOGO — the page's only navigation.

   One asset: the site's own header logo (white wordmark on transparency,
   272 x 54 with its own built-in padding), so this page and the Astra header
   are never two different logos that drift apart.

   Sized by width alone, height auto, so the lockup can never be distorted and
   a replacement asset of different proportions still renders correctly.

   Figma (Main Page, "image 13" header): 272 x 55 on desktop, the same size as
   the site header. It was 320px here once; every breakpoint below was scaled
   by 272/320 (0.85) together so the responsive curve keeps its shape.
   ------------------------------------------------------------ */
.tunnel-experience #site-logo {
  /* 272px from ~1500px up, easing to 140px on a phone. This is the one number
     to change if the logo wants to be bigger or smaller. */
  --logo-w: clamp(140px, calc(11.9vw + 93.5px), 272px);

  position: fixed;
  /* ABOVE the loader (9999), not below it: main.js also reveals this link when
     the scene FAILS to load, and an escape hatch behind the failed loading
     screen would be no escape at all. */
  z-index: 10000;
  top:  calc(clamp(14px, 1.4vw, 20px) + env(safe-area-inset-top, 0px));
  left: calc(clamp(18px, 2.4vw, 36px) + env(safe-area-inset-left, 0px));

  display: block;
  text-decoration: none;
  color: #fff;                  /* only reached by the alt-text fallback below */
  cursor: pointer;
  -webkit-tap-highlight-color: transparent;
  /* Negative margin cancels the padding, so the padding only grows the tap
     target without shifting the logo off its designed position. */
  padding: 10px;
  margin: -10px;

  /* Prefer NOT to put a `filter` on this element.
     ------------------------------------------------------------
     It sits over a full-screen WebGL canvas that repaints every frame, and a
     filter makes the compositor build a render surface for the overlay and
     re-run the filter pass on each of those repaints.

     Measured, because the first attempt at measuring this was wrong and it is
     worth recording how: unpaired A/B runs suggested drop-shadow was costing
     ~15fps, but that was monitorPerf()'s quality ladder stepping down DURING
     the experiment, not the filter. Re-measured with PAIRED samples after
     letting the ladder bottom out, the honest cost is ~1fps under a 6x CPU
     throttle (32.4 vs 31.3fps, the no-filter arm winning 9 of 14 pairs), and
     nothing at all unthrottled at 1920x1080.

     So: a drop shadow here is affordable, but it buys little — this corner of
     the scene is dark and the vignette darkens it further — and it is the one
     property most likely to hurt on a weak GPU. It is left off. If the logo
     ever needs separating from a bright background, prefer a static
     `background: radial-gradient(...)`: a plain paint on an already-promoted
     layer, with no per-frame filter pass. */

  /* Its own compositor layer, so the canvas repainting underneath never drags
     the logo into a re-raster — it stays a cached quad. Measured at zero cost
     either way at 1920x1080; this is insurance for weaker GPUs. */
  will-change: transform;

  opacity: 0;
  transform: translateY(-6px);
  pointer-events: none;
  /* Delayed so the fade-in starts after the loading screen is most of the way
     out, instead of the two brand marks overlapping at the same corner. */
  transition: opacity 0.8s ease 0.35s,
              transform 0.8s cubic-bezier(0.22, 1, 0.36, 1) 0.35s;
}

.tunnel-experience #site-logo.show {
  opacity: 1;
  transform: translateY(0);
  pointer-events: auto;
}

.tunnel-experience #site-logo-img {
  display: block;
  width: var(--logo-w);
  height: auto;
  /* Hover/press live on the IMAGE, not the anchor: the anchor's own transform
     carries the reveal (translateY + its 0.35s delay), and scaling it would
     fight that transition and inherit the delay.
     Scaled from the left edge, so the mark grows into the scene instead of
     past the gutter it is pinned to. transform is compositor-only, so the
     effect never triggers layout or paint over the WebGL canvas. */
  transform-origin: left center;
  transition: transform 0.35s cubic-bezier(0.22, 1, 0.36, 1);
}

/* (hover: hover) keeps this to real pointers. On a touchscreen a tap fires a
   sticky :hover that would leave the logo enlarged after the finger lifts. */
@media (hover: hover) {
  .tunnel-experience #site-logo:hover #site-logo-img { transform: scale(1.05); }
}
.tunnel-experience #site-logo:focus-visible #site-logo-img { transform: scale(1.05); }
/* A slight give on press: it confirms the click on desktop and is the only
   feedback a touch visitor gets. */
.tunnel-experience #site-logo:active #site-logo-img { transform: scale(1.02); }

.tunnel-experience #site-logo:focus-visible {
  outline: 2px solid rgba(255, 255, 255, 0.9);
  outline-offset: 2px;
  border-radius: 6px;
}

/* Phones and portrait tablets. These are held at a fraction of desktop viewing
   distance, so scaling the logo down by viewport width alone made it read
   SMALLER than on desktop, not the same: the previous curve (22.3vw, 100px
   floor) put a 100px mark on a 390px phone, its lettering shorter than the
   "Scroll to Explore" caption. Sized here by eye on the test viewports
   instead — 390x800 gets 150px, 834x1072 gets 230px.

   This rule meets the desktop curve with a step at 900px (230 here against
   200.6 just above). Only a desktop window dragged through 900px wide can see
   it; no real device sits on that line. */
@media (max-width: 900px) {
  .tunnel-experience #site-logo { --logo-w: clamp(140px, calc(18vw + 80px), 230px); }
}

/* A phone in landscape is wide but very short, so width alone would put an
   oversized logo into the little vertical room there is. Height drives it
   instead, scaled up in step with the portrait rule above (148px on a 390px
   tall screen). Declared after the rule above so it wins where both apply. */
@media (max-height: 520px) {
  .tunnel-experience #site-logo { --logo-w: clamp(120px, 38vh, 190px); }
}

/* If the image 404s, main.js swaps it for a <span> carrying its alt text. The
   link has to keep working — it is the way off this page — so the text is
   dressed as a wordmark rather than left as a broken-image caption. */
.tunnel-experience #site-logo-img.failed {
  width: auto;
  font: 700 clamp(15px, 1.4vw, 20px)/1 var(--font);
  letter-spacing: 5px;
  color: #fff;
  white-space: nowrap;
}

@media (prefers-reduced-motion: reduce) {
  .tunnel-experience #site-logo {
    transform: none;
    transition: opacity 0.2s linear;
  }
  .tunnel-experience #site-logo.show { transform: none; }
  /* The hover scale still shows the state change; it just snaps into it. */
  .tunnel-experience #site-logo-img { transition: none; }
}


/* ------------------------------------------------------------
   LOADING SCREEN

   Design reference, frame 1922 x 843:
     heading   Zalando Sans Expanded 600 / 64px on a 78px line, #FFF
     gap       71px
     status    Zalando Sans Expanded 400 / 32px on a 39px line, #00CCFF
     gap       30px
     bar       390 x 2, white track

   FONT SIZES ARE NOT SET HERE. They come from the site's typography classes
   in the theme's global-settings.css, applied in the markup:
     .slogan       larger-size    (64px at 1920; below 1195px it follows
                                   largest-size instead, see HEADING SIZE
                                   BELOW DESKTOP)
     #progress     small-size     (32px at 1920)
     .loader-inner small-size     (the bar below is in em of this)
   Those classes carry !important, so a font-size written here would be dead.
   The gaps and the bar are still written in em, so they follow whatever size
   the classes resolve to at each breakpoint.
   ------------------------------------------------------------ */
.tunnel-experience #loader {
  position: fixed;
  inset: 0;
  z-index: 9999;
  /* Fallback backdrop, shown until the artwork loads and where WebGL is
     unavailable. */
  background:
    radial-gradient(ellipse 120% 62% at 52% 30%,
                    rgba(46, 68, 102, 0.55) 0%, transparent 70%),
    linear-gradient(180deg, #070c15 0%, #0b1220 38%, #05080e 100%);
  display: flex;
  align-items: center;
  justify-content: center;
  overflow: hidden;
  transition: opacity 0.9s ease;
}

.tunnel-experience #loader.hide {
  opacity: 0;
  pointer-events: none;
}

/* ------------------------------------------------------------
   BACKDROP

   Two layers. `.loader-bg-still` is the design artwork exported with its
   lenticular effect already baked in; it is always present and does not
   move. `.loader-bg-live` is a canvas above it running the same effect in
   a shader, animated over a 2s loop. The canvas is only used when the
   pre-effect source layer is supplied; otherwise the still remains.
   ------------------------------------------------------------ */
.tunnel-experience #loader-bg {
  position: absolute;
  inset: 0;
  overflow: hidden;
  z-index: 0;
}

/* background-image is applied by main.js: in WordPress the file lives in the
   Media Library, so a relative URL here would resolve against the theme. */
.tunnel-experience .loader-bg-still {
  position: absolute;
  inset: 0;
  background-position: center center;
  background-repeat: no-repeat;
  /* cover: the artwork is 2.28:1 and centred, so a taller viewport crops
     the margins rather than letterboxing. */
  background-size: cover;
}

.tunnel-experience .loader-bg-live {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  display: block;
  opacity: 0;
  /* Revealed only once the first rendered frame is on the canvas. */
  transition: opacity 0.45s ease;
}

.tunnel-experience .loader-bg-live.show { opacity: 1; }

/* The still is hidden once the canvas is live. The canvas is transparent
   where the shader has not painted, so leaving the still underneath would
   blend a baked frame into a rendered one. */
.tunnel-experience #loader-bg.live .loader-bg-still {
  opacity: 0;
  transition: opacity 0.45s ease;
}

.tunnel-experience .loader-inner {
  position: relative;
  z-index: 1;
  display: flex;
  flex-direction: column;
  align-items: center;
  text-align: center;
  max-width: 92vw;
  /* The design sits the block slightly below centre. Doubled because a top
     margin on a centred flex item shifts it by half the value. */
  margin-top: 2vh;
}

.tunnel-experience .slogan {
  font-family: var(--font-display);
  font-weight: 600;
  line-height: 1.219;                /* Figma: 78 / 64 */
  text-transform: capitalize;        /* Figma: capitalize */
  color: var(--adro-white);
  margin-bottom: 1.109em;            /* Figma: 71 / 64 */
  max-width: 100%;
  /* One line at desktop sizes, where this changes nothing. Where it wraps,
     the lines come out even instead of one word left on the last. */
  text-wrap: balance;
}

/* ------------------------------------------------------------
   HEADING SIZE BELOW DESKTOP — a page-only exception, by agreement.

   `larger-size` is the one class in global-settings.css with NO breakpoint
   rules: a single clamp(2rem, -1.289rem + 4.408vw, 4rem) for every width. It
   is the class that gives Figma's 64px at 1920, which is why the heading uses
   it, but it bottoms out at 32px from ~1195px down. So a landscape tablet, a
   portrait tablet and a 320px phone all got the same 32px:
     - on tablets the heading was only 1.33x the status line (Figma: 2x),
       and smaller than the site's own top-level heading there (44px);
     - on small phones it was larger than the site's largest heading and ran
       to four lines at 320px.

   global-settings.css is site-wide and must not change for this page, so
   the fix lives here, scoped to this one heading. Below 1195px the heading
   takes `largest-size`'s values, breakpoint for breakpoint, copied from
   global-settings.css. It keeps the site's own type scale, and the heading
   stays ~1.8-2x the status line at every width, as in the design. From
   1195px up, `larger-size` applies untouched, so desktop is unchanged.

   The selector out-specifies `:root .larger-size`, and both are !important,
   so this wins wherever it applies, whatever order the stylesheets load in.
   The breakpoints are written to be gapless: the theme's own `max-width:
   390px` / `min-width: 391px` pair leaves widths between 390 and 391 with no
   rule at all, and some browsers report such widths.

   If `largest-size` changes in global-settings.css, copy the change here.
   ------------------------------------------------------------ */
@media screen and (max-width: 1194.98px) {
  .tunnel-experience .slogan.larger-size {
    font-size: clamp(2.23rem, 1.509rem + 1.502vw, 2.63rem) !important;
  }
}
@media screen and (max-width: 767.98px) {
  .tunnel-experience .slogan.larger-size {
    font-size: clamp(2rem, 1.484rem + 2.116vw, 2.5rem) !important;
  }
}
@media screen and (max-width: 390.98px) {
  .tunnel-experience .slogan.larger-size {
    font-size: clamp(1.405rem, -1.354rem + 13.797vw, 2rem) !important;
  }
}
/* The theme's fixed sizes for the exact iPad widths (1194 landscape, 834
   portrait), matching its own `largest-size` rule for them. */
@media screen and (min-width: 1193.98px) and (max-width: 1194.98px),
       screen and (min-width: 833.98px) and (max-width: 834.5px) and (orientation: portrait) {
  .tunnel-experience .slogan.larger-size { font-size: 44px !important; }
}

/* Typed reveal, at every viewport. main.js splits the heading into one
   .slogan-ch span per character, numbered --i, and adds .is-typing; each
   character then appears 32ms after the one before, starting at 0.25s.
   A per-character reveal types the same on one line or three, which the old
   right-edge clip could not.

   Until main.js runs the heading is still plain text, so it is held hidden
   to stop it flashing up whole and then re-typing. If main.js never runs at
   all, the hold lifts on its own after 2s and the heading simply appears. */
.tunnel-experience .slogan {
  animation: tunnelSloganHold 0s linear 2s both;
}
.tunnel-experience .slogan.is-typing { animation: none; }

@keyframes tunnelSloganHold {
  from { opacity: 0; }
  to   { opacity: 1; }
}

.tunnel-experience .slogan-word { white-space: nowrap; }

/* Each character's own state is VISIBLE; the animation only HOLDS it hidden
   until its turn, then ends with no fill and the character simply shows.

   Do not turn this into a short "fade to visible" with a fill. That was the
   first version — a 1ms step-end animation with fill: both — and when the
   main thread stalled (it does, parsing the tunnel GLB right as the heading
   types), a frame could jump straight over the 1ms. Chrome/Edge then reported
   those animations finished yet left the characters at opacity 0 for good:
   letters missing mid-sentence, reproduced under a 6x CPU throttle. With no
   fill, a finished animation has nothing left to get stuck in.

   The step to 1 at the very end is not decoration. A hold of 0 -> 0 is
   refused by Chrome's compositor as "no visible change" (trace failure code
   131072), which leaves the typing on the main thread — the thread that is
   parsing the GLB at that moment, so the letters arrived in three or four
   lumps. With a real change in the keyframes the compositor runs them, and
   the typing keeps its 32ms rhythm however busy the page is. */
.tunnel-experience .slogan-ch {
  animation: tunnelTypeHold linear;
  animation-duration: calc(0.25s + var(--i, 0) * 32ms);
}

@keyframes tunnelTypeHold {
  from, 99.9% { opacity: 0; }
  to          { opacity: 1; }
}

.tunnel-experience #progress {
  font-family: var(--font-display);
  font-weight: 400;
  line-height: 1.219;                /* Figma: 39 / 32 */
  color: var(--adro-electric);       /* Figma: #00CCFF */
  margin-bottom: 0.9375em;           /* Figma: 30 / 32 */
  white-space: nowrap;
  opacity: 0;
  animation: tunnelFadeUp 1s ease 0.15s forwards;
}

.tunnel-experience .loader-track {
  /* Figma: 390 x 2, white. The clamp stops it collapsing on small
     viewports. */
  width: clamp(220px, 12.1875em, 390px);
  max-width: 78vw;
  height: 2px;
  background: var(--adro-white);
  overflow: hidden;
  opacity: 0;
  animation: tunnelFadeUp 1s ease 0.15s forwards;
}

/* Progress fill: electric blue over the white track. */
.tunnel-experience #loader-bar {
  height: 100%;
  width: 0%;
  background: var(--adro-electric);
  transition: width 0.25s ease;
}

@keyframes tunnelFadeUp {
  from { opacity: 0; transform: translateY(18px); }
  to   { opacity: 1; transform: translateY(0); }
}

@media (prefers-reduced-motion: reduce) {
  .tunnel-experience .slogan,
  .tunnel-experience .slogan-ch,
  .tunnel-experience #progress,
  .tunnel-experience .loader-track {
    animation: none;
    opacity: 1;
    clip-path: none;
    transform: none;
  }
}


.tunnel-experience #vignette {
  position: fixed;
  inset: 0;
  z-index: 5;
  pointer-events: none;
  background: radial-gradient(ellipse 130% 100% at 50% 45%,
              transparent 58%, rgba(4, 5, 8, 0.5) 100%);
}


/* ------------------------------------------------------------
   SCROLL PROMPT — label above, mouse capsule below.

   Figma: text Montserrat 300 / 18px, 18px gap, capsule 25 x 42 with rx 12.5
   (rx = half the width, so it is a full pill), fill #144074 at 21%, 1px stroke
   #00CCFF at 10%, and a 10 x 6 chevron in Electric blue.

   Every size is derived from ONE number, --cap-w, so the whole prompt scales
   as a unit and keeps the design's proportions at any viewport.
   ------------------------------------------------------------ */
.tunnel-experience #hint {
  --cap-w: clamp(21px, 1.7vw, 25px);

  position: fixed;
  bottom: calc(clamp(24px, 3.2vh, 38px) + env(safe-area-inset-bottom, 0px));
  left: 50%;
  transform: translateX(-50%) translateY(14px);
  z-index: 10;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: clamp(12px, 1.2vw, 18px);          /* Figma: 18 */
  opacity: 0;
  transition: opacity 0.9s ease, transform 0.9s ease;
  pointer-events: none;
}

.tunnel-experience #hint.show {
  opacity: 1;
  transform: translateX(-50%) translateY(0);
}

.tunnel-experience #hint-text {
  font-family: var(--font-hint);
  /* Size: the site's smallest-size class, in the markup (18px at 1920). */
  font-weight: 300;
  line-height: normal;
  color: var(--adro-white);
  text-align: center;
  white-space: nowrap;
}

/* The capsule. Drawn, not uploaded: a rounded box with a fill and a 1px
   stroke is precisely border-radius + background + border. 999px gives the
   same full pill as Figma's rx 12.5 on a 25-wide box, at any size. */
.tunnel-experience .hint-mouse {
  width: var(--cap-w);
  height: calc(var(--cap-w) * 42 / 25);
  flex: none;
  border-radius: 999px;
  background: rgba(20, 64, 116, 0.21);          /* #144074 @ 21% */
  border: 1px solid rgba(0, 204, 255, 0.10);    /* #00CCFF @ 10% */
  display: flex;
  align-items: center;
  justify-content: center;
  color: var(--adro-electric);                  /* the chevron inherits this */
}

/* The chevron's artboard is 12 x 8 and its ink spans 10 x 6 inside that, so
   sizing the SVG to 12/25 of the capsule width lands the visible chevron on
   Figma's 10 x 6 exactly when the capsule is at its full 25px. */
.tunnel-experience .hint-arrow {
  display: block;
  width: calc(var(--cap-w) * 12 / 25);
  height: auto;
}

/* The chevron rises, bottom to top — the direction the camera travels when you
   scroll forward, so the prompt shows the gesture rather than just naming it.

   Two deliberate performance choices, because this sits over a WebGL canvas
   that repaints every frame:
     1. ONLY transform and opacity are animated. Both are compositor-only, so
        the loop never triggers layout or paint over the scene.
     2. The animation is declared on `.show` alone. noteInteraction() drops
        that class on the visitor's first scroll, which STOPS the animation
        outright rather than merely hiding it — the old .hint-line drip kept
        ticking behind opacity: 0 for the rest of the visit. */
.tunnel-experience #hint.show .hint-arrow {
  --arrow-travel: calc(var(--cap-w) * 0.32);    /* ~8px at full size */
  will-change: transform, opacity;
  animation: tunnelScrollArrow 1.9s cubic-bezier(0.33, 0, 0.2, 1) infinite;
}

@keyframes tunnelScrollArrow {
  0%   { transform: translateY(var(--arrow-travel));               opacity: 0; }
  20%  { transform: translateY(calc(var(--arrow-travel) *  0.45)); opacity: 1; }
  70%  { transform: translateY(calc(var(--arrow-travel) * -0.8));  opacity: 1; }
  100% { transform: translateY(calc(var(--arrow-travel) * -1));    opacity: 0; }
}

/* No animation wanted: the chevron simply rests centred, which is exactly the
   static Figma composition. Nothing else needs overriding. */
@media (prefers-reduced-motion: reduce) {
  .tunnel-experience #hint.show .hint-arrow { animation: none; }
}
