/* ============================================================
   SCENE3D.CSS — the immersive layer.

   Loads after site.css. Everything here is scoped to .has-3d, which
   scene3d.js only adds once WebGL has actually compiled and linked.
   If anything fails, none of this applies and the flat site is what
   renders — the 3D is strictly additive.
   ============================================================ */

#scene3d {
  position: fixed;
  inset: 0;
  width: 100%;
  height: 100%;
  z-index: 0;
  display: block;
  pointer-events: none;           /* never eats clicks or scroll */
  background: transparent;
}
.no-3d #scene3d { display: none; }

/* ---------- STATIC FALLBACK ----------
   The same lens, pre-rendered from the same shader. It occupies exactly
   the position the canvas would, so the flat and animated versions of the
   page look like the same design rather than two different ones. */
#lensStill {
  position: fixed;
  inset: 0;
  z-index: 0;
  display: block;
  pointer-events: none;
}
#lensStill[hidden] { display: none !important; }
#lensStill img {
  position: absolute;
  top: 50%; left: 68%;
  transform: translate(-50%, -50%);
  width: min(92vh, 62vw); height: auto;
  max-width: none;
  opacity: .92;
}
@media (max-width: 900px) {
  #lensStill img { left: 50%; top: 74%; width: min(118vw, 92vh); }
}

/* Content must sit above the canvas.
   Only `main` and `footer` are static and need position:relative to take a
   z-index. header is already sticky, and .wa-float/.vmodal/.lightbox are
   already fixed — setting position on those would rip them out of their
   layout, so they get z-index alone. */
.has-3d main, .has-lens main,
.has-3d footer, .has-lens footer { position: relative; z-index: 2; }

.has-3d header, .has-lens header { z-index: 60; }
.has-3d .wa-float, .has-lens .wa-float { z-index: 80; }
.has-3d .vmodal, .has-3d .lightbox,
.has-lens .vmodal, .has-lens .lightbox { z-index: 100; }

/* The page background has to become transparent or it hides the scene,
   but the body keeps its colour so there is never a white flash. */
.has-3d body { background: var(--bg); }

/* ---------- READABILITY ----------
   The single biggest risk with a full-page 3D background is text that
   cannot be read. Every content section gets a scrim; the hero is left
   open on the right so the lens is visible. */

.has-3d .hero::before { opacity: .55; }

.has-3d main > section,
.has-lens main > section {
  position: relative;
  isolation: isolate;
}
.has-3d main > section::after,
.has-lens main > section::after {
  content: '';
  position: absolute;
  inset: 0;
  z-index: -1;
  background: linear-gradient(
    to right,
    rgba(22,20,15,.94) 0%,
    rgba(22,20,15,.90) 46%,
    rgba(22,20,15,.62) 72%,
    rgba(22,20,15,.42) 100%);
  pointer-events: none;
}

/* The hero is the one place the scene is fully exposed, so the scrim
   clears completely well before the lens rather than sitting on top of it. */
.has-3d .hero::after,
.has-lens .hero::after {
  background: linear-gradient(
    to right,
    rgba(22,20,15,.90) 0%,
    rgba(22,20,15,.66) 30%,
    rgba(22,20,15,.06) 50%,
    rgba(22,20,15,0) 64%);
}

/* Panelled sections already have their own fill — keep them solid. */
.has-3d .proof::after, .has-lens .proof::after,
.has-3d .notes::after, .has-lens .notes::after,
.has-3d .band::after, .has-lens .band::after { background: rgba(22,20,15,.96); }

/* Cards are translucent but NOT blurred.
   The first version used backdrop-filter here, which put 24 live blurs on
   the page — 3 services + 9 work cards + 3 tiers + 3 proof items + 3
   contact channels + the form + the header. Each one re-blurs its region
   of the page on every frame the canvas behind it changes, and the canvas
   changes 60 times a second. That, not the shader, is what made a GTX 1060
   stutter: the raymarch itself measures ~3ms at 3.2 megapixels.
   Plain translucency costs essentially nothing. */
.has-3d .svc, .has-lens .svc,
.has-3d .case, .has-lens .case,
.has-3d .tier, .has-lens .tier,
.has-3d .form-card, .has-lens .form-card,
.has-3d .channel, .has-lens .channel,
.has-3d .rail-card, .has-lens .rail-card,
.has-3d .proof-item, .has-lens .proof-item {
  background: rgba(31,28,22,.90);
}

.has-3d header, .has-lens header {
  background: rgba(22,20,15,.88);
  backdrop-filter: none;
  -webkit-backdrop-filter: none;
}
.has-3d #toggle3d {
  backdrop-filter: none;
  -webkit-backdrop-filter: none;
  background: rgba(31,28,22,.92);
}

.has-3d footer, .has-lens footer {
  background: rgba(22,20,15,.96);
}

/* In 3D the lens is the hero visual, so the cycling viewfinder would
   compete with it. It is hidden rather than removed, so turning 3D off
   at runtime brings it straight back. */
.has-3d .hero .viewfinder,
.has-lens .hero .viewfinder { display: none; }
.has-3d .hero-grid, .has-lens .hero-grid { grid-template-columns: 1fr; }
.has-3d .hero, .has-lens .hero { min-height: 82vh; display: grid; align-items: center; }
.has-3d .hero .lede, .has-lens .hero .lede,
.has-3d .hero h1, .has-lens .hero h1 { max-width: 30ch; }

/* ---------- TOGGLE ----------
   A visitor who finds the scene distracting can switch it off, and the
   choice is remembered. Hidden until the scene is actually running. */
/* `display:inline-flex` below beats the UA's `[hidden]{display:none}`, so
   without this rule the chip showed even when the scene had refused to
   start — the page then said "3D ON" while rendering no 3D at all. */
#toggle3d[hidden] { display: none !important; }

#toggle3d {
  position: fixed;
  left: 20px; bottom: 20px; z-index: 81;
  display: inline-flex; align-items: center; gap: 8px;
  background: rgba(31,28,22,.86);
  backdrop-filter: blur(10px);
  -webkit-backdrop-filter: blur(10px);
  border: 1px solid var(--line);
  color: var(--muted);
  font-family: 'JetBrains Mono', ui-monospace, monospace;
  font-size: .62rem; letter-spacing: .14em; text-transform: uppercase;
  padding: 10px 14px; border-radius: 999px;
  transition: color .2s ease, border-color .2s ease;
}
#toggle3d:hover { color: var(--text); border-color: var(--amber); }
#toggle3d .dot {
  width: 6px; height: 6px; border-radius: 50%;
  background: var(--amber);
  box-shadow: 0 0 8px var(--amber);
}

/* off state: no glow, so the chip reads as an invitation rather than a
   status light claiming something is running */
#toggle3d.is-off .dot {
  background: var(--muted);
  box-shadow: none;
}
#toggle3d.is-off { color: var(--muted); }
#toggle3d.is-off:hover { color: var(--amber); border-color: var(--amber); }
#toggle3d.is-off:hover .dot { background: var(--amber); }

@media (max-width: 680px) {
  #toggle3d { left: 14px; bottom: 14px; font-size: .58rem; padding: 9px 12px; }

  /* On a phone there is no room beside the copy, so the lens is pushed
     BELOW it (uOffsetY in scene3d.js) and the hero scrim becomes a
     vertical fade: solid where the text is, clear where the lens is.
     Previously this was a flat 74% wash over everything, which is why
     the scene was technically running but effectively invisible. */
  .has-3d main > section::after,
.has-lens main > section::after { background: rgba(22,20,15,.92); }
  .has-3d .hero::after,
.has-lens .hero::after {
    background: linear-gradient(
      to bottom,
      rgba(22,20,15,.94) 0%,
      rgba(22,20,15,.88) 42%,
      rgba(22,20,15,.30) 66%,
      rgba(22,20,15,.05) 100%);
  }
  .has-3d .hero, .has-lens .hero { min-height: 0; padding-bottom: 52vh; }

  /* The outline button has only a faint border, which disappears once the
     lens is behind it. Give it a body on the 3D layer. */
  .has-3d .hero .btn-ghost,
  .has-lens .hero .btn-ghost {
    background: rgba(22,20,15,.86);
    border-color: var(--muted);
  }
  .has-3d .hero-note, .has-lens .hero-note { text-shadow: 0 1px 6px rgba(0,0,0,.8); }
}

/* Reduced motion is decided in scene3d.js, not here. A CSS rule keyed on
   the system preference cannot tell "reduced motion" apart from "reduced
   motion, but I pressed Enable 3D" — the old one here hid the canvas even
   after the visitor opted in, which is why 3D ON showed nothing. */

/* ---------- DRIFT ----------
   The pre-rendered lens with a slow sway, for browsers where live WebGL
   would run on the CPU. Rotation stays small so the horizontal lens
   flare in the image never visibly tilts. One composited transform on one
   image: about as cheap as motion gets. */
.lens-drift #lensStill img { animation: lensDrift 14s ease-in-out infinite; }
@keyframes lensDrift {
  0%   { transform: translate(-50%, -50%) rotate(-4deg) scale(1);    }
  50%  { transform: translate(-50%, -51%) rotate( 4deg) scale(1.035); }
  100% { transform: translate(-50%, -50%) rotate(-4deg) scale(1);    }
}
