/* Motion layer styles. Hand-authored, like site.css. Loaded only by the two pages
   that opt in via page['motion'] in build-spike.py.

   THE RULE: nothing in this file may hide content except under `.motion`, which is
   set by an inline gate in <head> that refuses to run under prefers-reduced-motion
   and strips itself after 2.5s if the scripts never arrive. Anything hidden outside
   a `.motion` selector is a bug that will fail the JavaScript-disabled pass in
   audit-spike.mjs, and worse, will ship a blank page to anyone whose script blocks.

   Structural rules that must apply with or without script live outside `.motion`
   and are marked as such. They are no-ops visually. */

/* ---------------------------------------------------------- structural ---- */
/* Emitted by the generator on every hero headline so the two lines can be masked.
   display:block on a span inside a heading that already broke into two lines is a
   visual no-op, so this is safe with script off. */
.m-mask { display: block; }
.m-line { display: block; }

/* The detection field. Sits at inset 0 inside .hero, which is already
   position:relative with overflow:hidden. .hero-inner is z-index 2, so the field
   is under the copy and over the dot pattern. */
.field-canvas {
  position: absolute; inset: 0; z-index: 1;
  pointer-events: none; opacity: 1;
  /* Masked off the copy column entirely. Unmasked, the range rings run behind the
     headline, which is untidy and puts a second surface under type whose contrast
     is contractual. This way the field belongs to the artwork and the left half of
     the hero is exactly as it was. */
  /* Widened when the hero went full bleed: the copy column now runs to about 46%
     of the viewport, so the field starts clearing it at 40% and is fully present
     by 66%. Same intent as before, new geometry. */
  -webkit-mask-image: linear-gradient(90deg, transparent 40%, #000 66%);
  mask-image: linear-gradient(90deg, transparent 40%, #000 66%);
}
/* The hero stacks on a phone and the artwork moves above the copy, so there is no
   right-hand side to attach the field to. It is also the device least able to
   afford a background canvas. home.js checks the same query and never starts it. */
@media (max-width: 900px) {
  .field-canvas { display: none; }
}

/* ------------------------------------------------------- hidden states ----- */
/* Everything below is gated on .motion. */

/* Masked headline. The padding/margin pair gives descenders somewhere to live;
   without it, overflow:hidden clips the tail of a "y" or a "g". */
.motion .hero-slide[data-active] .m-mask {
  overflow: hidden;
  padding-bottom: .14em;
  margin-bottom: -.14em;
}
.motion .hero-slide[data-active] .m-line { transform: translateY(108%); }

.motion .site-header { opacity: 0; }
.motion .hero-slide[data-active] .kicker,
.motion .hero-slide[data-active] .hero-sub,
.motion .hero-slide[data-active] .hero-tags,
.motion .hero-slide[data-active] .hero-cta { opacity: 0; }
.motion .hero-slide[data-active] .hero-vis { opacity: 0; }
.motion .field-canvas { opacity: 0; }

/* ------------------------------------------------------- product hero ----- */
/* The headline reveals with clip-path rather than a transform, driven by a custom
   property so GSAP writes a plain number each frame and the browser recomputes the
   clip. No transform is involved, so there is nothing for a stylesheet and an
   inline style to fight over: the failure mode that produced three separate bugs
   in this layer. Structural default is fully revealed. */
.sc-hero .sc-h1 > span {
  --rv: 1;
  clip-path: inset(0 0 calc((1 - var(--rv)) * 112%) 0);
}
.motion .sc-hero .sc-h1 > span { --rv: 0; }
.motion .sc-hero .sc-say,
.motion .sc-hero .sc-acts,
.motion .sc-hero .sc-figs { opacity: 0; }

/* The field sits over the photograph and under the copy. `.sc-hero .wrap` already
   establishes the copy above it; this only has to clear the left column so it
   never sits behind the headline, whose contrast over a photograph is measured. */
.sc-hero .field-canvas {
  z-index: 1;
  -webkit-mask-image: linear-gradient(90deg, transparent 44%, #000 70%);
  mask-image: linear-gradient(90deg, transparent 44%, #000 70%);
}
.sc-hero .wrap { position: relative; z-index: 2; }
/* The photograph is transformed on scroll, so it needs a box to be transformed
   inside without dragging the section's own layout around. */
.sc-hero > picture, .sc-hero > img { will-change: transform; }

/* Hero figures that lead somewhere. On a product whose figures are the same
   numbers the coverage plan draws, each one is a link into that ring. */
.sc-figs div { position: relative; }
.sc-figs a {
  text-decoration: none; color: inherit;
  transition: color .25s ease;
}
/* The whole cell is the target, not just the number. */
.sc-figs a::after { content: ""; position: absolute; inset: 0; }
.sc-figs div:has(a):hover dd { color: #E8C37A; }
.sc-figs a:focus-visible { outline: 2px solid #E8C37A; outline-offset: 4px; }

/* ------------------------------------------- cinematic hero variation ----- */
/* One orchestrated moment: the page opens as the system's own picture, tight on
   the target, and resolves into the true photograph while pulling back to the full
   panorama. Then it settles into a quiet ambient state with the detection field.
   Everything is driven by a single number, --sensor, so the whole sequence is one
   value for GSAP to animate and there is nothing for two owners to fight over.

   Structural default is --sensor: 0, the resolved photograph. Script off and the
   page is simply the picture, which is the correct fallback. */
.sc-hero-cinematic { --sensor: 0; }
.motion .sc-hero-cinematic { --sensor: 1; }

.sc-sensor {
  position: absolute; inset: 0; z-index: 1; pointer-events: none;
  opacity: var(--sensor);
}
/* Blended to colour rather than filtered. A `filter` on a 2600px image is a GPU
   pass every frame of the transition; a composited overlay is not, and .sc-hero
   already sets `isolation: isolate` so the blend cannot escape the section. */
/* .62, not 1. At full strength `mix-blend-mode: color` flattens the frame into a
   blue rectangle and the vehicle disappears entirely, which is the opposite of the
   point: this is meant to read as the scene seen through a sensor, not as a
   coloured panel. The photograph has to stay legible underneath. */
.sc-sensor-tone {
  position: absolute; inset: 0;
  background: linear-gradient(180deg, #1B4E86 0%, #12708F 55%, #1B4478 100%);
  mix-blend-mode: color;
  opacity: .62;
}
.sc-sensor-lines {
  position: absolute; inset: 0;
  background: repeating-linear-gradient(
    180deg, rgba(0, 0, 0, .34) 0 1px, transparent 1px 3px);
  opacity: .42;
  /* Drifts slowly, so the overlay reads as a live feed rather than a texture. */
  animation: sensor-drift 2.4s linear infinite;
}
@keyframes sensor-drift { to { transform: translateY(3px); } }

/* Ambient bed, once the hero has resolved. A warm breath at the horizon and a fine
   grain: a still photograph that is never quite still, without anything literally
   moving in it. Both fade in as --sensor falls to 0. */
.sc-hero-cinematic::before {
  content: ""; position: absolute; inset: 0; z-index: 1; pointer-events: none;
  background: radial-gradient(58% 40% at 52% 62%,
    rgba(255, 176, 92, .20), transparent 70%);
  opacity: calc(1 - var(--sensor));
  animation: horizon-breathe 13s ease-in-out infinite;
}
@keyframes horizon-breathe { 50% { opacity: calc((1 - var(--sensor)) * .45); } }

@media (prefers-reduced-motion: reduce) {
  .sc-sensor { display: none; }
  .sc-hero-cinematic::before { animation: none; }
}

/* ------------------------------------------------------------- product ---- */
/* The orbit. Structural, so it holds with script off: the window shows exactly
   one frame of the strip, and with a single-frame strip that is the whole image. */
.sc-orbit-sec { background: #0D1219; color: #fff; }
.sc-orbit-sec h2 { color: #fff; }
.sc-orbit-grid {
  display: grid; grid-template-columns: 1.1fr 1fr; gap: 40px;
  align-items: center; margin-top: 26px;
}
.sc-orbit { position: relative; display: grid; place-items: center; }
/* A lit stage behind the object. It reads as deliberate lighting, and it also
   covers the soft rectangular edge of this render's alpha mask, which is visible
   against a flat dark ground. A cleaner cut-out would not need it. */
.sc-orbit::before {
  content: ""; position: absolute; z-index: 0;
  width: 118%; aspect-ratio: 1; border-radius: 50%;
  background: radial-gradient(circle, rgba(63,115,209,.30), rgba(63,115,209,.07) 46%, transparent 68%);
  pointer-events: none;
}
.sc-orbit-window {
  position: relative; z-index: 1;
  width: 100%; max-width: 440px; overflow: hidden;
  display: grid; place-items: center;
}
.sc-orbit-strip {
  /* N frames wide inside a one-frame window. width is set from the frame count so
     a multi-frame strip lines up without JavaScript having to measure it. */
  width: calc(var(--frames, 1) * 100%);
  height: auto; max-height: 380px; object-fit: contain;
  filter: drop-shadow(0 30px 42px rgba(0, 0, 0, .6));
  will-change: transform;
}
.sc-orbit.is-dragging { cursor: grabbing; }
/* Emphasis is a rule and a colour lift, never a dim. Both states clear 4.5:1 on
   this ground: the resting colour composites to roughly 10:1 and the live state is
   pure white. Dimming the inactive paragraph was the first attempt and axe failed
   it as a serious contrast violation. */
.sc-orbit-copy p {
  color: rgba(255, 255, 255, .74);
  margin-bottom: 14px; max-width: 46ch;
  border-left: 2px solid transparent; padding-left: 18px;
  transition: color .45s ease, border-color .45s ease, transform .45s cubic-bezier(.2,.7,.3,1);
}
.sc-orbit-copy p.is-live {
  color: #fff; border-left-color: #E8C37A; transform: translateX(4px);
}
/* Tabular, so the bearing does not jitter as it counts. */
.sc-orbit-read {
  margin-top: 16px;
  font-family: ui-monospace, "SF Mono", Menlo, monospace;
  font-size: 11px; letter-spacing: .16em; text-transform: uppercase;
  color: #E8C37A; font-variant-numeric: tabular-nums;
}
@media (max-width: 860px) {
  .sc-orbit-grid { grid-template-columns: 1fr; gap: 24px; }
}

/* ---- coverage plan view -------------------------------------------------
   Structural, so it holds with script off: the rings are server-rendered SVG at
   full opacity and the figures are a real <dl> beside them. The motion layer only
   adds the draw and the hover isolation. */
.sc-cov { background: #0D1219; color: #fff; }
.sc-cov h2 { color: #fff; }
.sc-cov-grid {
  display: grid; grid-template-columns: 1fr 1fr; gap: 44px;
  align-items: center; margin-top: 28px;
}
@media (max-width: 860px) { .sc-cov-grid { grid-template-columns: 1fr; gap: 26px; } }
.sc-cov-plan { position: relative; }
.cov-svg { width: 100%; max-width: 420px; margin-inline: auto; display: block; overflow: visible; }
.cov-grid { fill: none; stroke: rgba(255, 255, 255, .12); stroke-width: 1; }
.cov-tick {
  fill: rgba(255, 255, 255, .55); font-size: 7.5px; text-anchor: middle;
  font-family: ui-monospace, "SF Mono", Menlo, monospace;
  /* A dark halo, painted under the glyph, so a label crossing a band edge stays
     readable without needing a solid plate behind it. */
  stroke: #0D1219; stroke-width: 2.5; paint-order: stroke fill;
}
/* Light, because three translucent annuli stacked at .20 read as mud. */
.cov-band { fill: none; stroke: var(--cov, #3F73D1); opacity: .11; }
.cov-edge { fill: none; stroke: var(--cov, #3F73D1); stroke-width: 2; opacity: .95; }
.cov-origin { fill: #E8C37A; }
.cov-layer[data-layer="0"] { --cov: #5B8DE8; }
.cov-layer[data-layer="1"] { --cov: #3FB8DC; }
.cov-layer[data-layer="2"] { --cov: #E8C37A; }
.cov-scale {
  text-align: center; margin-top: 10px; color: rgba(255, 255, 255, .5);
  font-family: ui-monospace, "SF Mono", Menlo, monospace;
  font-size: 10px; letter-spacing: .14em; text-transform: uppercase;
}
.sc-cov-legend { margin: 0; display: grid; gap: 2px; }
.cov-item {
  padding: 15px 18px; border-left: 2px solid var(--cov, #3F73D1);
  background: rgba(255, 255, 255, .03);
  transition: background .3s ease, transform .3s cubic-bezier(.2, .7, .3, 1);
}
.cov-item[data-layer="0"] { --cov: #5B8DE8; }
.cov-item[data-layer="1"] { --cov: #3FB8DC; }
.cov-item[data-layer="2"] { --cov: #E8C37A; }
.cov-item dt { font-size: 15px; font-weight: 650; color: #fff; }
.cov-item dd { margin: 4px 0 0; }
.cov-item dd b {
  display: block; font-size: 25px; font-weight: 730; letter-spacing: -.02em;
  color: var(--cov); font-variant-numeric: tabular-nums;
}
.cov-item dd span {
  display: block; margin-top: 5px; font-size: 13px; font-weight: 400;
  color: rgba(255, 255, 255, .62);
}
.cov-item.is-hot { background: rgba(255, 255, 255, .08); transform: translateX(4px); }
.cov-note { color: rgba(255, 255, 255, .55); margin-top: 22px; max-width: 62ch; }
/* Ring isolation on hover lives in product.js, not here. The reveal leaves an
   inline opacity on every layer, so a stylesheet rule would lose to it silently.
   Whatever dims the rings, it never dims the figures beside them: those are text
   and they stay at full contrast. */

/* ---- component cards ----------------------------------------------------
   site.css already owns the card's own lift and shadow on hover. This adds the one
   thing it does not: the object rises off its panel, a little further than the card
   itself, so the render reads as sitting on the surface rather than printed on it.
   Deliberately no scale: the ADS renders are 195px to 441px, and magnifying one
   shows exactly the softness ask A15 exists to fix. */
.sc-card-pic img {
  transition: transform .4s cubic-bezier(.2, .7, .3, 1);
}
.sc-card:hover .sc-card-pic.is-render img { transform: translateY(-6px); }

/* The engagement chain step that is currently level with the reader. */
.sc-steps > * { transition: border-color .4s, background .4s, opacity .4s; }
.sc-steps > .is-live { border-color: #E8C37A; background: rgba(232, 195, 122, .07); }

/* Leader lines on the annotated view draw themselves in. Structural: the line is
   an element with a width, so with script off it simply exists at full width. */
.motion .sc-cal { opacity: 0; }

/* Specification cells arrive rather than being there. */
.motion .sc-spec-grid > * { opacity: 0; }

/* --------------------------------------------------------- last resort ---- */
/* If a reader has asked for less motion, nothing above ever applies, because the
   gate never adds .motion. This block is belt and braces for anything that sets
   the class some other way, and for CSS-only transitions declared in site.css. */
@media (prefers-reduced-motion: reduce) {
  .motion .hero-slide[data-active] .m-mask { overflow: visible; }
  .motion .hero-slide[data-active] .m-line { transform: none; }
  .motion .site-header,
  .motion .hero-slide[data-active] .kicker,
  .motion .hero-slide[data-active] .hero-sub,
  .motion .hero-slide[data-active] .hero-tags,
  .motion .hero-slide[data-active] .hero-cta,
  .motion .hero-slide[data-active] .hero-vis,
  .motion .field-canvas,
  .motion .sc-cal,
  .motion .sc-spec-grid > * { opacity: 1; }
  .field-canvas { display: none; }
}
