/* Cascabel — motion layer
 *
 * Loads after each page's inline <style>, so these rules win on ties.
 * Everything here animates transform/opacity only, and everything here
 * switches itself off under prefers-reduced-motion.
 *
 * Tokens come from the page (--venom, --black, --ease-title...). Fallbacks are
 * spelled out so this file also works on a page that hasn't declared them.
 */

/* ── 1. The cut ───────────────────────────────────────────────────────────
 * A film cut between pages: black wipes in, the browser navigates behind it,
 * the next page wipes back out. Inserted by motion.js, so a page without JS
 * simply never gets it.
 */
.cb-cut {
  position: fixed;
  inset: 0;
  z-index: 9998;
  background: var(--black, #0a0a0a);
  pointer-events: none;
  opacity: 0;
}

/* arriving — the shutter lifts */
.cb-cut.cb-in {
  opacity: 1;
  animation: cb-lift .42s cubic-bezier(.16, 1, .3, 1) forwards;
}

/* leaving — the shutter drops, fast, like a strike */
.cb-cut.cb-out {
  opacity: 1;
  pointer-events: all;           /* swallow clicks once we're on our way out */
  transition: opacity .18s cubic-bezier(.7, 0, .2, 1);
}

@keyframes cb-lift {
  from { opacity: 1 }
  to   { opacity: 0 }
}

/* A hairline of venom crosses the black at the moment of the cut — the rattle,
 * borrowed from the brand's signature sweep. One frame of colour, then gone. */
.cb-cut::after {
  content: "";
  position: absolute;
  left: 0; right: 0; top: 50%;
  height: 1px;
  background: linear-gradient(90deg, transparent, var(--venom, #c2261d), transparent);
  transform: scaleX(0);
  transform-origin: 50% 50%;
}
.cb-cut.cb-out::after {
  animation: cb-sweep .34s cubic-bezier(.7, 0, .2, 1) forwards;
}
@keyframes cb-sweep {
  from { transform: scaleX(0);   opacity: 0 }
  40%  { transform: scaleX(1);   opacity: 1 }
  to   { transform: scaleX(1);   opacity: 0 }
}

/* ── 2. Ambient venom light in the hero ───────────────────────────────────
 * The hero breathes: a slow red glow drifts behind the title. Sits under the
 * content and never intercepts a click.
 */
.cb-glow {
  position: absolute;
  width: 62vw;
  height: 62vw;
  max-width: 900px;
  max-height: 900px;
  top: -18%;
  right: -12%;
  border-radius: 50%;
  pointer-events: none;
  z-index: 0;
  background: radial-gradient(circle, rgba(194, 38, 29, .20), transparent 62%);
  filter: blur(40px);
  animation: cb-drift 16s cubic-bezier(.2, .7, .2, 1) infinite alternate;
}
@keyframes cb-drift {
  from { transform: translate3d(0, 0, 0) scale(1) }
  to   { transform: translate3d(-6%, 5%, 0) scale(1.12) }
}

/* ── 2b. The light field ──────────────────────────────────────────────────
 * A red light that follows the pointer across a handful of otherwise flat
 * bands. At rest it drifts slowly on its own; the moment a pointer enters, it
 * goes with them.
 *
 * WHY IT IS BUILT THIS WAY. The first version moved the light by changing the
 * CENTRE OF A GRADIENT inside a box carrying filter: blur(44px). Every pointer
 * move re-rasterised that gradient and re-applied a 44px blur across the whole
 * section — a full repaint per mouse move, which is exactly why hovering these
 * sections crawled.
 *
 * Now the light is a FIXED blob that never redraws. Only its transform changes,
 * and a transform is handled by the compositor: the blurred texture is
 * rasterised once and simply moved. Same look, none of the cost.
 *
 * THE CLIPPING BOX MUST NOT MOVE. The drift used to live on this wrapper, the
 * same element that carries `overflow: hidden` — and a clip travels with the
 * element it belongs to. So the wrapper's own edge walked 2% into the band and
 * sliced the light against a straight vertical line, on the LEFT while the
 * drift was going right and on the RIGHT while it was coming back. That is the
 * seam the client photographed twice, from two different sides, and it moved
 * with the mouse only because the mouse decides where the light is when the
 * edge sweeps past it.
 *
 * So the wrapper is now perfectly still and does nothing but clip, exactly on
 * the section's edges. The drift moved onto the blob, written with the
 * `translate` property rather than `transform`: the two are separate
 * properties that compose on their own, so the idle wander and the pointer can
 * share one element without a second box to carry them.
 */
.cb-field {
  position: absolute;
  inset: 0;
  z-index: 0;
  pointer-events: none;
  overflow: hidden;
}
.cb-field::before {
  content: "";
  position: absolute;
  /* The middle of the band is the RESTING PLACE, and it is written here rather
     than measured in JS: the negative margins pull the blob back by half its
     own size, so with no --cb-x/--cb-y at all the light is already centred.
     motion.js then writes an offset FROM here. */
  top: 50%;
  left: 50%;
  /* Wider than the last pass: at 24vw the light lit a patch and left the rest
     of the band dead, especially with the pointer near an edge. The tone comes
     down a notch at the same time, so covering more ground does not also mean
     shouting. */
  width: var(--cb-size, clamp(380px, 34vw, 640px));
  aspect-ratio: 1;
  margin-left: calc(var(--cb-size, clamp(380px, 34vw, 640px)) / -2);
  margin-top: calc(var(--cb-size, clamp(380px, 34vw, 640px)) / -2);
  border-radius: 50%;
  background: radial-gradient(circle,
              rgba(194, 38, 29, .46), rgba(194, 38, 29, .17) 48%, transparent 74%);
  filter: blur(30px);
  opacity: .52;
  /* px, written by motion.js — a percentage here would resolve against the
     blob's own width instead of the section */
  transform: translate3d(var(--cb-x, 0px), var(--cb-y, 0px), 0);
  transition: transform .5s cubic-bezier(.2, .7, .2, 1), opacity .5s ease;
  will-change: transform;
  animation: cb-field-a 23s ease-in-out infinite alternate var(--cb-phase, 0s);
}
/* Under the pointer the light is brighter, follows faster, and the idle drift
   stands down — it is being led, not wandering. */
.cb-tracking > .cb-field::before {
  opacity: .82;
  transition: transform .18s cubic-bezier(.2, .7, .2, 1), opacity .3s ease;
  animation-play-state: paused;
}
/* Percentages here are of the BLOB, not of the band — it is the blob that
   moves now. Roughly the same travel as before at the sizes this runs at. */
@keyframes cb-field-a {
  from { translate: -6% -4% }
  to   { translate: 7% 4% }
}
/* On the saturated bands the light is a highlight moving over the colour, not
   a red glow on top of red. */
/* .tally was a venom band when this list was written and is black now, so it
   was painting a WHITE light on a black section — the grey smear the client
   photographed. It belongs with the dark sections and takes the red. */
.venom-band > .cb-field::before,
.urgent > .cb-field::before,
.onsite > .cb-field::before,
.promise > .cb-field::before {
  background: radial-gradient(circle,
              rgba(255, 255, 255, .34), rgba(255, 255, 255, .1) 48%, transparent 72%);
  opacity: .5;
}
.venom-band.cb-tracking > .cb-field::before,
.urgent.cb-tracking > .cb-field::before,
.onsite.cb-tracking > .cb-field::before,
.promise.cb-tracking > .cb-field::before { opacity: .85 }

/* The field carries the z-index of the section's own backdrop layers (set in
   motion.js), so nothing else needs reordering. An earlier version raised every
   child to z-index 1 and buried a photograph doing it. */

/* ── 3. Photographs fade up instead of popping in ─────────────────────────
 * The photos are real files now, so they arrive after the markup. Without
 * this they snap in one by one.
 */
img.cb-img {
  opacity: 0;
  transition: opacity .7s cubic-bezier(.16, 1, .3, 1);
}
img.cb-img.cb-ready { opacity: 1 }

/* ── The floor ────────────────────────────────────────────────────────────
 * Reduced motion: no cut, no drift, no push, no fade. Content stays visible.
 * (Each page already zeroes animations globally; this makes the new pieces
 * safe on their own terms too.)
 */
@media (prefers-reduced-motion: reduce) {
  .cb-cut { display: none !important }
  .cb-glow { animation: none !important }
  .cb-field { display: none !important }
  img.cb-img { opacity: 1 !important; transition: none !important }
}
