/* ============================================================================
   Purpose: The WebGL stage - the bay the car stands in. Owns the canvas, the
   floor light, the loading state and the fallback swap. Tokens only.
   ========================================================================= */

.czf-stage {
  position: absolute;
  inset: 0;
  transition: opacity var(--dur-slow) var(--ease-out);
}

/* The page-level layer. Fixed, behind everything, full viewport - the car can
   then be placed anywhere in frame by shifting the camera's projection rather
   than by moving DOM around.

   The clip is what keeps the car INSIDE the diagnostic's left column. The
   canvas is viewport-fixed while the column scrolls, so the inset is written
   live from JS every frame; at rest (hero) all four values are zero and the
   car has the whole frame. */
.czf-stage--fixed {
  position: fixed;
  inset: 0;
  z-index: var(--z-base);
  /* Feathered, not cropped. A hard clip edge across a lit car reads as a
     rendering fault; a mask lets the bodywork dissolve at the column boundary
     instead. Two gradients intersected - one for the right edge, one for the
     bottom. */
  --czf-clip-fade: var(--space-96);

  /* Three edges, one idiom.
   *
   * The vertical gradient is a BAND, open at both ends: a top boundary as well
   * as a bottom one. The top is what stops the car drawing over the gauges as
   * it travels up the column - the curve alone could only make that unlikely,
   * and "unlikely" is a bug you see once a session.
   *
   * Each boundary fades on the far side of itself, so a value of 0 puts the
   * whole fade off-canvas and the mask is a no-op. That is what lets the hero,
   * which wants the full frame, share these rules with the diagnostic. */
  -webkit-mask-image:
    linear-gradient(to right,
      #000 calc(100% - var(--czf-clip-r, 0px) - var(--czf-clip-fade)),
      transparent calc(100% - var(--czf-clip-r, 0px))),
    linear-gradient(to bottom,
      transparent calc(var(--czf-clip-t, 0px) - var(--czf-clip-fade)),
      #000 var(--czf-clip-t, 0px),
      #000 calc(100% - var(--czf-clip-b, 0px)),
      transparent calc(100% - var(--czf-clip-b, 0px) + var(--czf-clip-fade)));
  -webkit-mask-composite: source-in;

  mask-image:
    linear-gradient(to right,
      #000 calc(100% - var(--czf-clip-r, 0px) - var(--czf-clip-fade)),
      transparent calc(100% - var(--czf-clip-r, 0px))),
    linear-gradient(to bottom,
      transparent calc(var(--czf-clip-t, 0px) - var(--czf-clip-fade)),
      #000 var(--czf-clip-t, 0px),
      #000 calc(100% - var(--czf-clip-b, 0px)),
      transparent calc(100% - var(--czf-clip-b, 0px) + var(--czf-clip-fade)));
  mask-composite: intersect;
  z-index: var(--z-base);
  overflow: hidden;
  pointer-events: none;
  /* The bay is lit by the car, not by a coloured wash. A tinted glow behind
     the vehicle reads as a gradient artefact, so the only light here is a
     neutral pool under the car and a faint warm bounce off the floor. */
  background:
    radial-gradient(46% 30% at 62% 62%, rgba(255, 255, 255, 0.045) 0%, transparent 70%),
    radial-gradient(60% 26% at 60% 88%, rgba(245, 168, 0, 0.045)  0%, transparent 74%);
}

/* The verdict's stage. A block in the flow rather than a layer behind the page:
   on /result/ there is nothing to sit behind, and the car is a figure in a
   document. Sized so it reads at the same scale as the hero. */
.czf-stage--inline {
  position: relative;
  inset: auto;
  width: 100%;
  /* Sized to the car, not to the page. The camera compensates for the box
     height (see `fit` in stage.js), so a shorter box renders the SAME size car
     with less empty canvas around it - the gap goes, the car does not. */
  height: 44svh;
  min-height: 260px;
  /* Desktop gets a little more room under the car than the old 8px, so the
     verdict below it reads as a separate beat rather than as a caption. */
  margin-bottom: var(--space-24);
  pointer-events: none;
  background: radial-gradient(52% 34% at 50% 62%, rgba(255, 255, 255, 0.045) 0%, transparent 72%);
}

.czf-stage--inline[hidden] { display: none; }

/* The car is the first thing on this page, so it does not need the section's
   full opening rhythm above it as well as its own. */
.czf-page-result .czf-result.czf-section { padding-top: var(--space-32); }

@media (max-width: 1023px) {
  .czf-page-result .czf-result.czf-section { padding-top: var(--space-16); }
  .czf-stage--inline { height: 40svh; min-height: 220px; }
}

/* Phones. The car is drawn to fit its box, so the box carries a lot of empty
   canvas above and below it on a narrow screen - and on a short screen that
   dead space is the difference between the verdict being visible on arrival
   and needing a scroll. Tighter box, tighter margins, and the section's own
   opening padding comes off too. */
@media (max-width: 767px) {
  .czf-page-result .czf-result.czf-section { padding-top: var(--space-8); }
  .czf-stage--inline {
    height: 34svh;
    min-height: 180px;
    margin-top: calc(var(--space-8) * -1);
    margin-bottom: 0;
  }
}

.czf-stage--inline canvas { pointer-events: auto; }

.czf-stage canvas {
  display: block;
  width: 100% !important;
  height: 100% !important;
  opacity: 0;
  transition: opacity var(--dur-cine) var(--ease-out);
}

.czf-stage.is-ready canvas { opacity: 1; }

/* On the result the page becomes something to read, so the car steps out.
   It has already made its argument during the diagnostic. */
.czf-has-result .czf-stage { opacity: 0; pointer-events: none; }

/* On narrow screens the car no longer sits behind the copy - it has its own
   reserved band, first under the header and then in the gap between the gauges
   and the question panel. So it is the subject again and runs at full strength.

   The band is clipped here rather than trusted to arithmetic: the canvas is
   viewport-fixed while its slot scrolls, and a car that bled over the headline
   or the question panel would undo the whole point of giving it a slot. The two
   offsets are written live by animations.js; feathered, because a hard cut
   across a lit car reads as a rendering fault. */
@media (max-width: 1023px) {
  .czf-stage.is-ready canvas { opacity: 1; }
  /* The band is the same mask, driven by the same two variables. Narrow just
     never sets a right-hand clip, because there is no column to clip to. */
}

/* --- Hotspots ---------------------------------------------------------------
   Markers pinned to bodywork, positioned from 3D by stage.js. The element is
   translated to the projected point, so every visual detail hangs off a
   centred origin and nothing needs to know its own size.
   ------------------------------------------------------------------------ */

.czf-stage__hotspots {
  position: absolute;
  inset: 0;
  pointer-events: none;
  z-index: var(--z-raised);
}

.czf-hotspot {
  position: absolute;
  top: 0;
  left: 0;
  display: flex;
  align-items: center;
  gap: var(--space-8);
  opacity: 0;
  visibility: hidden;
  transition: opacity var(--dur-base) var(--ease-out),
              visibility var(--dur-base) var(--ease-out);
  will-change: transform;
}

.czf-hotspot[data-show="1"] { opacity: 1; visibility: visible; }

/* The dot sits ON the panel, so it is pulled back by half its own size. */
.czf-hotspot__dot {
  position: relative;
  flex: 0 0 auto;
  width: var(--space-8);
  height: var(--space-8);
  margin-left: calc(var(--space-8) / -2);
  border-radius: var(--radius-full);
  background: var(--c-shield);
  box-shadow: var(--glow-shield);
}

/* One slow ring. A pulsing marker on every panel would be a fairground. */
.czf-hotspot__dot::after {
  content: "";
  position: absolute;
  inset: calc(var(--space-4) * -1);
  border: 1px solid currentColor;
  border-radius: var(--radius-full);
  color: var(--c-shield);
  opacity: 0.5;
  animation: czf-hotspot-ring 2.8s var(--ease-out) infinite;
}

@keyframes czf-hotspot-ring {
  0%        { transform: scale(0.6); opacity: 0.6; }
  60%, 100% { transform: scale(2.2); opacity: 0; }
}

.czf-hotspot__label {
  font-family: var(--font-mono);
  font-size: var(--fs-micro);
  letter-spacing: var(--tracking-wide);
  text-transform: uppercase;
  color: var(--c-ink);
  white-space: nowrap;
  padding: var(--space-4) var(--space-8);
  background: rgba(6, 7, 8, 0.72);
  border: 1px solid var(--c-line-2);
  backdrop-filter: blur(6px);
}

.czf-hotspot[data-channel="weather"] .czf-hotspot__dot {
  background: var(--c-weather);
  box-shadow: var(--glow-weather);
}
.czf-hotspot[data-channel="weather"] .czf-hotspot__dot::after { color: var(--c-weather); }
.czf-hotspot[data-channel="weather"] .czf-hotspot__label { border-color: var(--c-weather-ghost); }

@media (max-width: 599px) {
  .czf-hotspot__label { display: none; }   /* dots only - labels would tile */
}

@media (prefers-reduced-motion: reduce) {
  .czf-hotspot__dot::after { animation: none; opacity: 0.35; }
}


/* Drag to spin. The canvas is the only thing in the stage that takes input. */
.czf-stage.is-ready canvas { cursor: grab; }
.czf-stage.is-dragging canvas { cursor: grabbing; }

/* --- Loading ---------------------------------------------------------------
   A quiet scanning line rather than a spinner. It belongs to the diagnostic
   metaphor, and it stops the hero looking broken during the model fetch.
   ------------------------------------------------------------------------ */

.czf-stage__loading {
  position: absolute;
  left: 50%;
  top: 50%;
  width: min(46%, 420px);
  height: 1px;
  transform: translate(-50%, -50%);
  background: linear-gradient(90deg,
    transparent, var(--c-weather), transparent);
  opacity: 0.5;
  animation: czf-scan 2.1s var(--ease-in-out) infinite;
}

@keyframes czf-scan {
  0%, 100% { transform: translate(-50%, -50%) scaleX(0.28); opacity: 0.16; }
  50%      { transform: translate(-50%, -50%) scaleX(1);    opacity: 0.55; }
}

.czf-stage.is-ready .czf-stage__loading,
.czf-stage.is-unsupported .czf-stage__loading,
.czf-stage.is-failed .czf-stage__loading { display: none; }

/* --- Fallback --------------------------------------------------------------
   No WebGL, weak device, reduced motion, or a failed model: the SVG car takes
   over. It is not a placeholder - it is the same information, drawn flat.
   ------------------------------------------------------------------------ */

.czf-stage__fallback {
  position: absolute;
  inset: 0;
  display: none;
  place-items: center;
  padding: var(--space-32);
}

.czf-stage.is-unsupported .czf-stage__fallback,
.czf-stage.is-failed .czf-stage__fallback { display: grid; }

.czf-stage.is-unsupported .czf-stage__loading { display: none; }

.czf-stage__fallback .czf-car__svg { max-width: min(46vh, 300px); }

/* Without JS at all, show the fallback rather than an empty black box. */
html:not(.czf-js) .czf-stage__fallback { display: grid; }

/* --- Print -----------------------------------------------------------------
   The canvas will not print. Swap in the SVG so the customer's PDF still
   carries their lit diagram into the studio.
   ------------------------------------------------------------------------ */

@media print {
  .czf-stage { position: relative; background: none; }
  .czf-stage canvas,
  .czf-stage__loading { display: none !important; }
  .czf-stage__fallback { display: grid !important; position: relative; }
}
