/* BUDDY tags the headline.
 *
 * This retroactively explains the hero: the headline is already sprayed
 * paint (css/hero-stencil.css's #nb-spray filter), and this sequence is the
 * one frame of story that says who painted it and that he is not supposed to
 * be here.
 *
 * Everything that moves the character is `transform` and `opacity` on named
 * groups inside brand/buddy-tagger.svg, inlined by js/hero-tag.js so each
 * part can be driven independently — a parent transform on #tag-rig carries
 * the whole character across the scene, and child transforms on the antenna,
 * feet and arm squash, drag and overlap inside that space. Nothing here
 * triggers layout: the mount is `position: absolute` inside `.act--arrival`
 * (already `position: relative`, see css/stage.css), so it can never change
 * that section's box and can never move the CLS gate off zero.
 *
 * The one exception is the headline reveal, and the brief calls that out
 * explicitly: a clip-path swept left-to-right on the h1 ITSELF (real DOM,
 * still selectable, still readable) is how the paint appears to land in sync
 * with the tag beat. clip-path does not affect layout — it is a paint-time
 * clip, not a box change — so the zero-CLS gate is untouched.
 */

.nb-hero-tag {
  position: absolute;
  inset: 0;
  overflow: hidden;      /* the frame edge. exits are free because of this. */
  contain: layout paint; /* a translated/scaled child can never grow this box
                             or the document's scrollHeight. */
  pointer-events: none;
  z-index: 2;
}

.nb-hero-tag__rig {
  position: absolute;
  left: 0;
  /* 26%, not the section's true floor: `.act--arrival` is often taller than
     one viewport (padding + copy length), and a mascot planted at its literal
     bottom edge stands below the fold on first paint. This keeps him level
     with the CTA row, which is where the visible "ground" actually reads. */
  /* His ground line is the foot of the headline, not the foot of the section.
     The arrival section is taller than a viewport, so a percentage of IT put
     him down on the body copy, spraying the paragraph. He belongs at the wall
     he is painting. */
  bottom: 42%;
  /* Much bigger than the vector stand-in needed, and this is not a taste
     call. A flat silhouette reads at 80px; a rendered character with panel
     lines, a visor and a gold trim does not — at the old clamp he was a
     79px speck you would have to be told about. The art is detailed, so it
     has to be large enough for the detail to be the point. */
  width: clamp(190px, 23vw, 430px);
  transform-origin: 50% 100%;
  opacity: 0;
  transform: translateX(-32vw) scale(0.5);
  will-change: transform, opacity;
}

/* --- the two raster layers ----------------------------------------------
 * The character is the designed robot, delivered as a render lit on a dark
 * teal vignette. Both layers carry a real alpha channel, cut like this:
 *
 *   The ROBOT by a flood fill from the border with a tight per-step
 *   tolerance — "is this pixel the same as the background pixel beside it"
 *   rather than "is this pixel dark", which is the only test that survives a
 *   smooth gradient. Two other approaches were tried and both failed in ways
 *   worth recording: a loose tolerance (14) walked straight through his dark
 *   navy panel seams and hollowed him out, and a difference key against a
 *   fitted polynomial background swallowed the teal glow around him as
 *   foreground. A large morphological close to recover the darkest leg
 *   panels welded his arm to his chest, so it is a small one and those few
 *   slivers stay trimmed — invisible against a near-black hero, which is the
 *   only place this asset is used.
 *
 *   The PAINT by chroma: it is strongly cyan against a desaturated backing,
 *   so (G+B)/2 - R separates them cleanly and keeps the overspray speckle
 *   and the two drips. Those are the whole reason to use the artist's jet
 *   instead of drawing particles.
 *
 * An earlier pass composited both with mix-blend-mode: screen and no matte
 * at all. It looked right in isolation and wrong on the page, for a reason
 * that is easy to lose an afternoon to: .nb-hero-tag__rig carries
 * will-change, which creates a stacking context, and a blend mode only ever
 * blends within its own stacking context. The layers were screening against
 * the rig's empty backdrop instead of against the hero, so each one drew its
 * backing as a flat opaque box. Real alpha does not care.
 */
.nb-hero-tag__bot,
.nb-hero-tag__spray {
  display: block;
  width: 100%;
  height: auto;
  transform-box: fill-box;
  pointer-events: none;
  user-select: none;
}

.nb-hero-tag__bot {
  transform-origin: 50% 100%;
}

/* The jet, pinned to the can's nozzle.
 *
 * These are not eyeballed. The robot layer is cropped from (110, 44) of the
 * 2000x1116 source and is 1390 wide; the jet layer is cropped from (1500, 60)
 * and is 500 wide. That puts its origin 100% across the robot and 1.5% down,
 * at 36.0% of his width. Change either crop and these three change with it.
 *
 * Unlike the robot this one carries a real alpha channel, so it composites
 * normally. It could be keyed because the paint is strongly cyan against a
 * desaturated backing — (G+B)/2 - R separates them cleanly, and it keeps the
 * overspray speckle and the two drips, which are the whole reason to use the
 * artist's jet rather than draw particles. The robot could not be keyed the
 * same way and does not need to be: see the note above. */
.nb-hero-tag__spray {
  position: absolute;
  left: 100%;
  top: -0.86%;
  width: 36.6%;
  transform-origin: 0% 50%;
  opacity: 0;
}

/* =========================================================================
   BEAT 1 — SNEAK IN (~1100ms)
   Enters low and small at the left edge, leaning forward into the walk.
   Furtive: he knows he shouldn't be here. Feet cycle out of phase with each
   other — overlap, not one rigid unit. ========================================================================= */

/* The sneak was the weakest beat and it was a legibility problem, not a timing
   one: entering at half scale against a dark aurora, he read as a dim smudge
   rather than as a character, and you could miss him entirely until he was
   already at the wall. Bigger throughout, and he fades up faster so the eye
   catches him arriving instead of finding him already there. The furtiveness
   comes from the low posture and the lean, which cost nothing to read. */
@keyframes tag-sneak-in {
  0%   { transform: translateX(-30vw) scale(0.68) rotate(-2deg); opacity: 0; }
  8%   { opacity: 1; }
  60%  { transform: translateX(-6vw) scale(0.78) rotate(-9deg); }
  100% { transform: translateX(5vw)  scale(0.82) rotate(-6deg); }
}
[data-stage="sneak"] .nb-hero-tag__rig {
  animation: tag-sneak-in 1100ms cubic-bezier(.22,.61,.36,1) forwards;
}

/* The walk. A rigged character would cycle two feet out of phase; a single
   raster cannot, so the trudge is carried by the whole body instead — a
   short rise and fall with a lean that rocks against it. Two oscillators at
   the same period but different phase is what stops it reading as a sprite
   being slid across the screen. */
@keyframes tag-trudge {
  0%, 100% { transform: translateY(0) rotate(-1.2deg) scale(1, 1); }
  25%      { transform: translateY(-3.5%) rotate(0.4deg) scale(0.985, 1.015); }
  50%      { transform: translateY(0) rotate(1.2deg) scale(1, 1); }
  75%      { transform: translateY(-3.5%) rotate(-0.4deg) scale(0.985, 1.015); }
}
[data-stage="sneak"] #tag-body { animation: tag-trudge 560ms ease-in-out infinite; }

/* Drag: the antenna is on a slower, bouncier clock than the legs, so it is
   always a beat behind the body's lean. */


/* =========================================================================
   BEAT 2 — THE TAG (~1400ms)
   Plants (squash on landing, volume-preserved rebound), winds up, sprays.
   This is the moment the whole hero exists for, so the headline paints on
   in sync — see .nb-tag-paint below, timed off the same two custom
   properties js/hero-tag.js sets on the h1 itself.
   ========================================================================= */

/* The rig itself has no keyframe of its own during this beat — only the
   body/arm/spray sub-parts animate — so without an explicit resting
   transform here it would snap back to its pre-sequence base style
   (opacity 0, off-screen left) the instant [data-stage] stops matching
   "sneak". This pins him exactly where the sneak's last frame left him. */
[data-stage="tag"] .nb-hero-tag__rig {
  opacity: 1;
  transform: translateX(5vw) scale(0.64) rotate(-6deg);
}

@keyframes tag-plant-squash {
  0%   { transform: scale(1, 1); }
  18%  { transform: scale(1.20, 0.80); }
  42%  { transform: scale(0.92, 1.08); }
  100% { transform: scale(1, 1); }
}
[data-stage="tag"] #tag-body { animation: tag-plant-squash 380ms cubic-bezier(.28,1.5,.5,1) forwards; }



/* The jet. transform-origin is the nozzle, so scaleX is the paint leaving
   the can rather than the whole image growing — it lances out, holds while
   the headline paints, and dies back. It does not linger: the jet image
   carries its own paint blob and drips, and leaving that on screen next to
   the finished headline would read as a second, unrelated tag. The headline
   IS the tag; this is only the act of making it. */
@keyframes tag-spray-puff {
  0%       { opacity: 0; transform: scaleX(0.05) scaleY(0.7); }
  9%       { opacity: 1; transform: scaleX(0.55) scaleY(0.92); }
  16%      { opacity: 1; transform: scaleX(1) scaleY(1); }
  62%      { opacity: 1; transform: scaleX(1.04) scaleY(1.02); }
  78%      { opacity: 0.72; transform: scaleX(1.0) scaleY(0.96); }
  100%     { opacity: 0; transform: scaleX(0.9) scaleY(0.88); }
}
[data-stage="tag"] #tag-spray { animation: tag-spray-puff 1400ms ease-out forwards; }

/* The headline. clip-path sweeps its right edge from fully closed to fully
   open, revealing the already-painted (filter: url(#nb-spray)) letterforms
   left to right — the drips that filter bakes in appear to "run" as the
   sweep passes over them because they sit at fixed points along the word.
   The -6%/-20%/-60% sides give the spray filter's own bleed (which runs
   past the box on every edge, see hero-stencil.css) room so this clip never
   guillotines the paint texture itself. */
.nb-tag-paint {
  clip-path: inset(-20% 100% -60% -6%);
  /* A clip alone gives a perfectly vertical cut travelling across the letters,
     which reads as a wipe transition rather than as paint arriving. The mask
     feathers that leading edge over a couple of percent and breaks it up, so
     what crosses the type is a spray front rather than a guillotine. The clip
     still does the actual revealing; this only softens where it lands. */
  -webkit-mask-image: linear-gradient(
    95deg, #000 0%, #000 calc(var(--tag-front, 100%) - 5%),
    rgba(0,0,0,.55) calc(var(--tag-front, 100%) - 2%),
    transparent var(--tag-front, 100%));
  mask-image: linear-gradient(
    95deg, #000 0%, #000 calc(var(--tag-front, 100%) - 5%),
    rgba(0,0,0,.55) calc(var(--tag-front, 100%) - 2%),
    transparent var(--tag-front, 100%));
  animation: nb-tag-headline-paint var(--tag-duration, 1400ms) linear forwards;
  animation-delay: var(--tag-delay, 1100ms);
}
@keyframes nb-tag-headline-paint {
  0%   { clip-path: inset(-20% 100% -60% -6%); }
  100% { clip-path: inset(-20% -6% -60% -6%); }
}
.nb-tag-paint--done {
  clip-path: none;
  animation: none;
  -webkit-mask-image: none;
  mask-image: none;
}

/* =========================================================================
   BEAT 3 — TURN AND APPROACH (~1100ms)
   Turns out to camera and scales up toward the middle of the screen — the
   cheat for "closer to us" is scale plus a slightly lower position, never
   perspective. A beat at the end: pleased with himself, maybe caught.
   ========================================================================= */

@keyframes tag-approach {
  0%   { transform: translateX(5vw)  scale(0.64) rotate(-6deg); }
  28%  { transform: translateX(18vw) scale(0.80, 0.70) rotate(6deg); } /* the turn: a horizontal squash sells the pivot */
  62%  { transform: translateX(32vw) scale(1.02) rotate(0deg) translateY(1.5%); }
  100% { transform: translateX(37vw) scale(1.12) rotate(0deg) translateY(3%); } /* held beat, bigger and lower = closer */
}
[data-stage="approach"] .nb-hero-tag__rig,
[data-stage="anticipate"] .nb-hero-tag__rig {
  /* Neither keyframe below touches opacity — only transform — so without this
     the rig falls back to its pre-sequence base opacity: 0 the moment
     [data-stage] stops matching "tag". Same failure mode as the "tag" pin
     above, one selector earlier in the chain. */
  opacity: 1;
}
[data-stage="approach"] .nb-hero-tag__rig {
  animation: tag-approach 1100ms cubic-bezier(.28,1.5,.5,1) forwards;
}




/* A small held-breath bounce at the end of the approach — "pleased with
   himself" — riding on top of the approach's own transform via the body
   only, so the rig's position keeps its settled value. */
@keyframes tag-pleased-bob { 0%, 100% { transform: translateY(0) scale(1,1); } 50% { transform: translateY(-3%) scale(1.03, 0.97); } }
[data-stage="approach"] #tag-body { animation: tag-pleased-bob 420ms cubic-bezier(.22,.61,.36,1) 620ms; }

/* =========================================================================
   BEAT 4 — BOLT (~900ms total: anticipation crouch + fast smeared exit)
   ========================================================================= */

@keyframes tag-anticipate-crouch {
  0%   { transform: translateX(37vw) scale(1.12) rotate(0deg) translateY(3%); }
  100% { transform: translateX(34vw) scale(0.98, 1.22) rotate(-7deg) translateY(6%); }
}
[data-stage="anticipate"] .nb-hero-tag__rig {
  animation: tag-anticipate-crouch 200ms cubic-bezier(.34,.9,.4,1) forwards;
}


/* The exit is smeared, not inbetweened: it stretches long and thin along the
   direction of travel and drops the character's detail almost immediately,
   which is two poses (crouch, smear) doing the work eight correct frames
   would have. */
@keyframes tag-bolt-exit {
  0%   { transform: translateX(34vw)  scaleX(0.98) scaleY(1.22) rotate(-7deg) translateY(6%); opacity: 1; }
  14%  { transform: translateX(44vw)  scaleX(1.95) scaleY(0.58) rotate(9deg)  translateY(4%); opacity: 1; }
  55%  { transform: translateX(88vw)  scaleX(2.4)  scaleY(0.5)  rotate(11deg) translateY(1%); opacity: 0.85; }
  100% { transform: translateX(132vw) scaleX(2.6)  scaleY(0.45) rotate(11deg) translateY(-2%); opacity: 0; }
}
[data-stage="bolt"] .nb-hero-tag__rig {
  animation: tag-bolt-exit 700ms cubic-bezier(.5,0,.75,0) forwards;
}
@keyframes tag-bolt-fade { 0% { opacity: 1; } 22% { opacity: 0; } 100% { opacity: 0; } }
/* The jet is already gone by the time he bolts — re-animating it here from
   opacity 1 flashed the paint back on for a frame at the worst possible
   moment. It stays where the tag beat left it. */
[data-stage="bolt"] #tag-spray { opacity: 0; }

[data-stage="gone"] { display: none; }

/* =========================================================================
   Reduced motion — the default on the machine this was built on, and the one
   frame most visitors here will ever see. No sequence: BUDDY simply stands at
   the left of the already-finished tag, as though he has just stepped back
   from it. The headline is never clipped in this path at all.
   ========================================================================= */

[data-stage="still"] .nb-hero-tag__rig {
  opacity: 1;
  transform: translateX(3vw) scale(0.62) rotate(0deg);
}
[data-stage="still"] #tag-body { animation: none; transform: none; }
/* He keeps the jet in the still. In reduced motion this frame is the entire
   sequence, and a character holding a can that is visibly doing nothing is a
   worse read than one caught mid-spray — a still photograph of a tag in
   progress is a photograph of a tag in progress. */
[data-stage="still"] #tag-spray { animation: none; transform: none; opacity: 0.9; }

@media (prefers-reduced-motion: reduce) {
  .nb-hero-tag *,
  .nb-hero-tag { animation: none !important; transition: none !important; }
}

/* Phones: no Buddy. js/hero-tag.js never mounts him under 640px; this is the
   guard for a window that narrows after he has already started, so he can
   never end up standing on the pitch text and the headline is never left
   half-painted behind a clip. */
@media (max-width: 639.98px) {
  .nb-hero-tag { display: none !important; }
  .nb-tag-paint {
    clip-path: none;
    animation: none;
    -webkit-mask-image: none;
    mask-image: none;
  }
}

/* =========================================================================
   After the tag, he comes back: the idle post.
   =========================================================================
 * A few seconds after the bolt he wanders back in and settles in the hero
 * corner — breathing, watching, saluting when the visitor returns to the
 * hero. Absolute inside .act--arrival (already position:relative), so like
 * the tag rig it can never move layout and the CLS gate stays at zero.
 * pointer-events:none and aria-hidden: he is decoration, and nothing may
 * depend on him. Sizing mirrors the offerings mascot's rule — height is the
 * invariant, width follows the pose — via aspect-ratio on the tallest pose
 * and object-fit:contain on both.
 */
.nb-hero-buddy {
  position: absolute;
  right: clamp(14px, 3.5vw, 54px);
  bottom: clamp(14px, 5vh, 64px);
  width: clamp(92px, 9vw, 148px);
  aspect-ratio: 181 / 228;
  z-index: 4;
  pointer-events: none;
  contain: layout paint;
  opacity: 0;
  transform: translateY(12px);
  transition: opacity .6s ease, transform .6s ease;
}
.nb-hero-buddy.is-in {
  opacity: 1;
  transform: none;
  animation: hero-buddy-breathe 5.2s ease-in-out infinite;
}
.nb-hero-buddy .hb-pose {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: contain;
  opacity: 0;
  transition: opacity 280ms ease-out;
  filter: drop-shadow(0 10px 14px rgba(0, 0, 0, 0.45));
}
.nb-hero-buddy[data-pose="thinking"] .hb-think { opacity: 1; }
.nb-hero-buddy[data-pose="salute"] .hb-salute { opacity: 1; }
@keyframes hero-buddy-breathe {
  0%, 100% { translate: 0 0; }
  50%      { translate: 0 -6px; }
}

@media (prefers-reduced-motion: reduce) {
  .nb-hero-buddy { animation: none !important; transition: none !important; }
}

/* Phones: no Buddy — same guard as the tag sequence. js/hero-tag.js never
   mounts him under 640px; this covers a window narrowed after he started. */
@media (max-width: 639.98px) {
  .nb-hero-buddy { display: none !important; }
}
