/* ════════════════════════════════════════════════════════════════
   BeatPush — Landing signature motion (Punchy, clean)
   PR #131. Card-focused choreography: the HERO and mini-dashboard
   are always visible from first paint, only the SAMPLE CARD
   choreographs its own assembly. The contact-path SVG layer was
   removed (#131 clean-up) — the lime L-line read as decorative
   and crossed the card stats / CTA. Card assembly + CTA hit
   carry the signature on their own.

   Driven by `app/web/static/js/landing-signature.js`. Class flips
   the engine adds/removes on `.sig-stage`:
     .armed       hides every .reveal element pre-play
     .sweep       fires the scan-line keyframe (over the card)
     .finished    resolved post-state marker
     .skip        instant resolved state, no animation
   Plus per-element flips:
     .reveal.lit            fragment landed (per-beat)
     .sc-card.found         lime border flash on the sample card
     .sc-cta.hit            decisive one-time CTA pulse

   No-animation baseline: without `.armed`, every `.reveal`
   element renders at opacity:1, transform:none (resolved). The
   scan-line stays opacity:0 until `.sweep` adds the keyframe.
   JS-off / failed / slow all show the resolved landing.
   ════════════════════════════════════════════════════════════════ */

/* ── stage container is just a positioning ancestor ─────────────
   The class is layered onto .lp-wrap. We don't reset the wrap's
   width / padding / overflow — the signature lives on TOP of the
   existing landing, not inside a fake frame. position: relative
   so the scan-line overlay anchors here. The scan-line lives
   inside .lp-sample, so we promote that section to a positioning
   ancestor too. */
.sig-stage { position: relative; }
.sig-stage .lp-sample { position: relative; }

/* ── armed = pre-play hidden state ─────────────────────────────
   Only fires when JS sets .armed. No-JS, slow-JS, failed-JS keep
   the resolved baseline. Variant transforms decide the per-element
   entrance direction; `.reveal` carries the shared transition.

   The `:not(.lit)` qualifier is load-bearing. Without it, the
   armed-hidden rule had specificity (0,3,0) — `.sig-stage` +
   `.armed` + `.reveal` — which beat `.reveal.lit` (0,2,0). JS
   would add `.lit` per the schedule but the armed-hidden rule
   would keep matching, so fragments stayed invisible until
   `.armed` was removed at finish(). The visual was a single
   late reveal instead of a staggered assembly.

   `:not(.lit)` also adds specificity (the inner class counts),
   but the load-bearing piece is the MATCHING behavior: once JS
   adds .lit, the `:not(.lit)` no longer matches that element,
   the armed-hidden rule stops applying, and the fragment falls
   back to .reveal.lit (opacity:1 baseline, transform:none) with
   the .reveal transition fading it in over 420ms. */
/* `transition: none` on the armed-not-lit baseline is also load-
   bearing: without it the elements would interpolate from baseline
   opacity 1 down to 0 over the 420ms .reveal transition (because
   armed was added DYNAMICALLY by the engine, not before first
   paint). The producer would see a slow fade-out from 0–420ms,
   then a fade-back-in starting whenever the beat lit them — a
   U-shape that reads as a flicker. With `transition: none`, the
   hide is instant on arm, and the per-beat fade-in is the only
   visible motion. .lit removes the :not match → the base
   .reveal transition takes over → smooth 420ms fade-in. */
.sig-stage.armed .reveal:not(.lit)   { opacity: 0; transition: none; }
.sig-stage.armed .r-rise:not(.lit)   { transform: translateY(16px); }
.sig-stage.armed .r-rise-s:not(.lit) { transform: translateY(8px); }
.sig-stage.armed .r-pop:not(.lit)    { transform: scale(0.6); }
.sig-stage.armed .r-snap:not(.lit)   { transform: translate(6px, 6px); }

.reveal { transition: opacity 0.42s ease, transform 0.42s cubic-bezier(0.2, 0.8, 0.2, 1); }
.reveal.lit { opacity: 1; transform: none; }
.r-pop.lit { transform: scale(1); }
/* brutalist hard-shadow snap on r-snap fragment landing */
.r-snap.lit { animation: snap-shadow 0.4s ease forwards; }
@keyframes snap-shadow {
    0%   { box-shadow: 6px 6px 0 var(--text-faint); }
    100% { box-shadow: 0 0 0 transparent; }
}

/* ── scan-line overlay ─────────────────────────────────────────
   The scan-line is positioned absolute inside `.lp-sample` so it
   sweeps the card region specifically, not the whole page. (In
   the first cut it was inside .lp-wrap which made it sweep across
   ~3000px of content; the new card-focused choreography only
   wants the lime band to pass over the sample card itself.) */
.lp-sample .sig-scanline {
    position: absolute;
    left: 0; right: 0; top: 0;
    height: 2px;
    z-index: 50;
    pointer-events: none;
    background: linear-gradient(90deg, transparent, var(--hit) 20%, var(--hit) 80%, transparent);
    box-shadow: 0 0 22px 2px oklch(0.88 0.22 128 / 0.55);
    opacity: 0;
    transform: translateY(-4px);
}
.lp-sample .sig-scanline::before {
    content: "";
    position: absolute;
    left: 0; right: 0; top: 2px;
    height: 64px;
    background: linear-gradient(180deg, oklch(0.88 0.22 128 / 0.12), transparent);
}
.sig-stage.sweep .sig-scanline {
    animation: scan-sweep 740ms cubic-bezier(0.4, 0, 0.2, 1) forwards;
}
@keyframes scan-sweep {
    0%   { opacity: 0; transform: translateY(-4px); }
    8%   { opacity: 1; }
    92%  { opacity: 1; }
    100% { opacity: 0; transform: translateY(var(--sig-scan-end, 460px)); }
}

/* ── CTA hit — one decisive pulse + brutalist shadow snap ─────
   Adds box-shadow on top of whatever the existing IG button has,
   then resolves clean. No loop. The hit fires once and that's it. */
.sc-cta.hit { animation: cta-hit 380ms cubic-bezier(0.2, 0.8, 0.2, 1) 1; }
@keyframes cta-hit {
    0%   { transform: scale(1); box-shadow: 0 0 0 transparent; }
    45%  { transform: scale(1.05); box-shadow: 3px 3px 0 rgba(178, 29, 82, 0.85); }
    100% { transform: scale(1); box-shadow: 0 0 0 transparent; }
}

/* ── sample card "found" flash on lime ────────────────────────
   Layers a brief lime inset-glow on the card when the scan-line
   first crosses it. Reads as "match found". Tail fades back to
   the existing card border. */
.sc-card.found { animation: card-found 600ms ease forwards; }
@keyframes card-found {
    0%   { box-shadow: inset 0 0 0 1px transparent; }
    30%  { box-shadow: inset 0 0 0 1px var(--hit), 0 0 30px oklch(0.88 0.22 128 / 0.25); }
    100% { box-shadow: inset 0 0 0 1px transparent, 0 0 0 transparent; }
}

/* ── .skip = instant resolved state, no signature motion ──────
   The patched contract verified before implementation:
   sig.js adds .skip on the stage in three paths — resolve(true)
   after natural play finishes, the reduced-motion early-return,
   and skip-on-interaction (scroll > 8px, pointerdown,
   touchstart, keydown). This block kills every signature-driven
   transition + animation when .skip is on. */
.sig-stage.skip .reveal {
    transition: none !important;
}
.sig-stage.skip .sig-scanline,
.sig-stage.skip .sc-card,
.sig-stage.skip .sc-cta,
.sig-stage.skip .r-snap {
    animation: none !important;
}

/* ── mobile re-key ─────────────────────────────────────────────
   On mobile the sample card stacks 1fr (cover on top, body
   below), so the scan-line sweep distance is bigger. Bump
   --sig-scan-end accordingly. */
@media (max-width: 600px) {
    .sig-stage.sweep .sig-scanline { animation-duration: 900ms; }
    .sig-scanline { --sig-scan-end: 700px; }
}

/* ── reduced-motion: resolve everything, no animation ──────────
   The JS engine sets .skip on the stage immediately when
   prefers-reduced-motion: reduce. This CSS block backs that up
   in case the engine fails to load: scan-line hidden, reveal
   elements stay at their resolved baseline. */
@media (prefers-reduced-motion: reduce) {
    .sig-scanline { display: none !important; }
    .sig-stage .reveal { opacity: 1 !important; transform: none !important; transition: none !important; }
}
