/* ==========================================================================
   BB2G · motion.css — THE LURE STANDARD, as a stylesheet.

   Kristen, twice (2026-08-05 and again 2026-08-14): "make the graphics moving
   like lure.bornbetween2generals.com — I don't like lame graphics."

   Both times the answer was built by hand, per app, because the JS half
   (kit/motion.js) and the fetcher (kit/hero.mjs) existed but THE CSS HALF
   NEVER DID. 48 of 147 live apps carry a hero loop; every one was bespoke.
   This file is the missing half, so the next one is two lines instead of a day.

   Pair it with kit/motion.js. Markup contract:

     <section class="bbm-hero">
       <img   class="bbm-hero__still" src="poster.jpg" alt="">   <- ALWAYS present
       <video class="heroloop" muted loop playsinline preload="metadata"
              poster="poster.jpg" src="hero.mp4"></video>
       <svg   class="bbm-motif" aria-hidden="true">...</svg>
       <div   class="bbm-hero__veil"></div>
       <div   class="bbm-hero__in"> ...type... </div>
     </section>

   FOUR TRAPS CLOSED HERE, each one paid for in a previous session:

   1. REDUCED MOTION RESOLVES TO THE FINISHED STATE — never a frozen mid-frame
      and never nothing. A blank page is a worse failure than movement.
   2. opacity:0 IS NEVER THE DEFAULT. The hidden-before-reveal state applies
      only under html.bbm-js, which an inline <head> script sets before paint.
      With scripting off nothing ever hides. (html.no-js the other way round is
      wrong: if the script never runs the class is never removed.)
   3. A GLOW MUST BE BIGGER THAN THE BOX IT SITS BEHIND. Written as an inset
      pseudo-element it is 100% occluded and does literally nothing — the blur
      spill IS the glow. And it needs pointer-events:none or it eats clicks.
   4. KEN BURNS RUNS ONLY WHILE ON SCREEN (.is-live, set by the observer), not
      permanently — otherwise every offscreen hero burns battery forever.
   ========================================================================== */

:root {
  --bbm-drift: 44s;    /* hero push — slow enough to read as depth, not motion */
  --bbm-kb: 26s;       /* ken burns                                           */
  --bbm-motif: 34s;    /* line-motif drift                                    */
  --bbm-rise: .62s;    /* scroll reveal                                       */
  --bbm-ease: cubic-bezier(.22, .61, .36, 1);
  /* The grade. Lure runs the footage DOWN so white type holds. But the first
     Full Circle attempt (brightness .55 under a .90 veil) erased the footage
     completely — and "lame graphics" is the whole complaint. OPEN THE VEIL
     UNTIL THE FOOTAGE READS, then hold the type with a shadow. */
  --bbm-grade: saturate(.68) contrast(1.10) brightness(.78);
  --bbm-veil-top: .72;
  --bbm-veil-mid: .34;
  --bbm-veil-bot: .80;
}

/* ---------- the hero ---------- */
.bbm-hero {
  position: relative;
  isolation: isolate;
  overflow: hidden;
  min-height: clamp(420px, 76vh, 760px);
  padding-block: clamp(3rem, 9vw, 7rem);
  /* NOT display:flex. Making the hero a flex container turns .bb-wrap into a
     flex item, which shrink-wraps it off the page grid — and "fixing" that
     with width:100% overrides the min() that IS the page gutter. Centre on
     min-height + padding, and constrain the prose on an INNER element. */
}

/* The still is the floor. It is a real <img>, not a poster attribute, because
   the watchdog removes a stalled <video> and would take its poster with it. */
.bbm-hero__still,
.bbm-hero .heroloop {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
  z-index: 0;
  filter: var(--bbm-grade);
  pointer-events: none;
}

.bbm-hero .heroloop {
  opacity: 0;
  transition: opacity .9s ease-in;
  z-index: 1;
}
/* Only once the frames are actually decoding does the loop cross over the
   still, so a hero is never a black box mid-load. */
.bbm-hero .heroloop.is-live {
  opacity: 1;
  animation: bbmHeroDrift var(--bbm-drift) ease-in-out infinite alternate;
}

.bbm-hero__veil {
  position: absolute;
  inset: 0;
  z-index: 2;
  pointer-events: none;
  background: linear-gradient(
    180deg,
    rgba(8, 10, 16, var(--bbm-veil-top)) 0%,
    rgba(8, 10, 16, var(--bbm-veil-mid)) 46%,
    rgba(8, 10, 16, var(--bbm-veil-bot)) 100%
  );
}

.bbm-hero__in {
  position: relative;
  z-index: 3;
  /* type holds against moving footage without closing the veil down */
  text-shadow: 0 2px 18px rgba(0, 0, 0, .55);
}

/* ---------- the line motif over the footage ---------- */
.bbm-motif {
  position: absolute;
  inset: 0;
  z-index: 2;
  width: 100%;
  height: 100%;
  opacity: .16;
  pointer-events: none;
  animation: bbmMotifDrift var(--bbm-motif) ease-in-out infinite alternate;
}

/* ---------- ambient glow ----------
   FULL BOX OR LARGER. An inset glow is entirely hidden behind the opaque
   element in front of it; the blur spilling past the edge is the whole effect. */
.bbm-glow { position: relative; }
.bbm-glow::before {
  content: "";
  position: absolute;
  top: 2%;
  left: 0;
  width: 100%;
  height: 100%;
  z-index: -1;
  pointer-events: none;
  filter: blur(42px);
  opacity: .5;
  background: radial-gradient(60% 55% at 50% 50%, var(--gold, #c5a046), transparent 72%);
}

/* ---------- scroll reveal ----------
   Visible by default. Hidden ONLY when JS has proven it is present. */
html.bbm-js .bbm-rise {
  opacity: 0;
  transform: translateY(18px);
  transition: opacity var(--bbm-rise) var(--bbm-ease),
              transform var(--bbm-rise) var(--bbm-ease);
  will-change: opacity, transform;
}
html.bbm-js .bbm-rise.is-in { opacity: 1; transform: none; }
html.bbm-js .bbm-rise:nth-child(2) { transition-delay: .07s; }
html.bbm-js .bbm-rise:nth-child(3) { transition-delay: .14s; }
html.bbm-js .bbm-rise:nth-child(4) { transition-delay: .21s; }

/* ---------- ken burns, on screen only ---------- */
.bbm-kb { overflow: hidden; }
.bbm-kb > img, .bbm-kb > video { width: 100%; height: 100%; object-fit: cover; }
.bbm-kb.is-live > img,
.bbm-kb.is-live > video { animation: bbmKenburns var(--bbm-kb) ease-out both; }

/* ---------- small ambient pieces ---------- */
.bbm-bob   { animation: bbmBob 2.8s ease-in-out infinite; }
.bbm-pulse { animation: bbmPulse 2.2s ease-in-out infinite; }
.bbm-tide  { animation: bbmTideline 14s linear infinite; }

@keyframes bbmHeroDrift  { from { transform: scale(1.05); } to { transform: scale(1.15); } }
@keyframes bbmKenburns   { from { transform: scale(1.02) translate3d(0,0,0); }
                           to   { transform: scale(1.12) translate3d(0,-1.5%,0); } }
@keyframes bbmMotifDrift { from { transform: translateX(-2.5%); } to { transform: translateX(2.5%); } }
@keyframes bbmBob        { 0%,100% { transform: translateY(0); } 50% { transform: translateY(6px); } }
@keyframes bbmPulse      { 0%,100% { opacity: 1; } 50% { opacity: .55; } }
@keyframes bbmTideline   { from { background-position: 0 0; } to { background-position: 200% 0; } }

/* ==========================================================================
   REDUCED MOTION — resolve to the FINISHED state.
   Not a frozen mid-frame, and above all not an empty page. The reveal must
   land at fully visible; kit/motion.js pauses the video, which CSS cannot do.
   ========================================================================== */
@media (prefers-reduced-motion: reduce) {
  .bbm-hero .heroloop,
  .bbm-motif,
  .bbm-kb.is-live > img,
  .bbm-kb.is-live > video,
  .bbm-bob, .bbm-pulse, .bbm-tide {
    animation: none !important;
  }
  html.bbm-js .bbm-rise,
  html.bbm-js .bbm-rise.is-in {
    opacity: 1 !important;
    transform: none !important;
    transition: none !important;
  }
  .bbm-hero .heroloop { opacity: 1; transition: none; }
}
