/* ============================================================
   BOLDFLO DESIGN SYSTEM — 02 · MOTION LIBRARY
   Baseline vocabulary, measured off the NovaPay reference.

   THE GRAMMAR: accelerate to leave, decelerate to arrive.
   Anything exiting the frame uses --bf-e-exit and speeds up as
   it goes. Anything entering uses --bf-e-arrive and slows into
   place. Two eases doing opposite jobs is what makes the
   reference feel physical rather than tweened. Do not use a
   single ease for both halves of a transition.
   ============================================================ */

/* ============================================================
   A · DECK — the signature move
   Three cards in fixed slots; the front one flings out right,
   the rest step forward, the old front re-enters at the back.
   ============================================================ */
.bf-deck{position:relative;isolation:isolate}

.bf-deck .bf-deck__card{position:absolute}
.bf-deck__card{
  position:absolute;
  border-radius:var(--bf-r-card);
  overflow:hidden;
  background:var(--bf-glass-soft);
  backdrop-filter:blur(calc(var(--bf-glass-blur) * 1.5)) saturate(120%);
  will-change:transform,opacity;
  transition:
    left   var(--bf-d-settle) var(--bf-e-arrive),
    right  var(--bf-d-settle) var(--bf-e-arrive),
    top    var(--bf-d-settle) var(--bf-e-arrive),
    bottom var(--bf-d-settle) var(--bf-e-arrive),
    transform var(--bf-d-settle) var(--bf-e-arrive),
    opacity   var(--bf-d-mid)  linear;
}

/* Slots. Offsets are proportional so the deck scales with its container. */
.bf-deck__card[data-slot="0"]{left:0;right:16%;top:0;bottom:0;z-index:3;background:var(--bf-glass)}
.bf-deck__card[data-slot="1"]{left:8%;right:8%;top:11%;bottom:11%;z-index:2;background:var(--bf-glass-soft)}
.bf-deck__card[data-slot="2"]{left:16%;right:0;top:23%;bottom:23%;z-index:1;background:var(--bf-glass-soft)}

/* Only the front card's text is legible. */
.bf-deck__text{transition:opacity var(--bf-d-mid) ease}
.bf-deck__card[data-slot="0"] .bf-deck__text{opacity:1;transition-delay:.16s}
.bf-deck__card[data-slot="1"] .bf-deck__text,
.bf-deck__card[data-slot="2"] .bf-deck__text{opacity:0;transition-delay:0s}

/* FLING OUT. Accelerating ease: solid for the first third, then gone. */
.bf-deck__card[data-state="leaving"]{
  left:0;right:16%;top:0;bottom:0;z-index:4;
  transform:translateX(130%) rotate(10deg);
  opacity:0;
  transition:
    transform var(--bf-d-exit) var(--bf-e-exit),
    opacity .34s ease .12s;
}
/* FIX vs the reference: the leaving card never dropped its text,
   so for ~0.34s the outgoing and incoming paragraphs rendered on
   top of each other, unreadable. Kill it on the way out. */
.bf-deck__card[data-state="leaving"] .bf-deck__text{
  opacity:0;
  transition:opacity .14s ease;
  transition-delay:0s;
}

/* RE-ENTRY at the back — placed with no transition, then released. */
.bf-deck__card[data-state="entering-back"]{
  left:16%;right:0;top:23%;bottom:23%;z-index:1;
  transform:scale(.9);opacity:0;transition:none;
}
/* Back-swipe mirrors the fling. */
.bf-deck__card[data-state="entering-front"]{
  left:0;right:16%;top:0;bottom:0;z-index:4;
  transform:translateX(-130%) rotate(-10deg);
  opacity:0;transition:none;
}

/* ============================================================
   B · ENTRANCE — plays once, then detaches
   ============================================================ */
@keyframes bf-lineRise{from{transform:translateY(100%)}to{transform:translateY(0)}}
@keyframes bf-liftIn{from{opacity:0;transform:translateY(9px)}to{opacity:1;transform:none}}
@keyframes bf-pillIn{from{opacity:0;transform:translateY(8px) scale(.972)}to{opacity:1;transform:none}}
@keyframes bf-focusIn{from{opacity:0;transform:translateY(14px);filter:blur(4px)}
                      to{opacity:1;transform:none;filter:blur(0)}}
@keyframes bf-scaleIn{from{opacity:0;transform:scale(.96)}to{opacity:1;transform:none}}

/* Masked line reveal. The mask is the parent; padding/margin pair
   keeps descenders from being clipped. */
/* The mask needs headroom on BOTH edges: display line-height is 0.9, tighter
   than the font's glyph extent, so overflow:hidden crops cap tops and
   descenders. Top padding is larger because that is the edge the line rises
   past; the small bottom value reveals it a sliver early, which is invisible
   at these durations and far better than clipped type. */
.bf-rise{display:block;overflow:hidden;padding:.3em 0 .18em;margin:-.3em 0 -.18em}
.bf-rise > *{display:block;transform:translateY(100%)}
.bf-go .bf-rise > *{animation:bf-lineRise var(--bf-d-long) var(--bf-e-arrive) both}

.bf-lift{opacity:0}  .bf-go .bf-lift{animation:bf-liftIn   var(--bf-d-settle) var(--bf-e-soft)   both}
.bf-pill{opacity:0}  .bf-go .bf-pill{animation:bf-pillIn   var(--bf-d-settle) var(--bf-e-soft)   both}
.bf-focus{opacity:0} .bf-go .bf-focus{animation:bf-focusIn .85s              var(--bf-e-arrive) both}
.bf-scale{opacity:0} .bf-go .bf-scale{animation:bf-scaleIn var(--bf-d-reveal) var(--bf-e-arrive) both}

/* Stagger: set --i on each child. */
.bf-stagger > *{animation-delay:calc(var(--i, 0) * 60ms)}

/* ============================================================
   C · AMBIENT — never competes with content
   ============================================================ */
@keyframes bf-glowPulse{0%,100%{opacity:.5;transform:scale(1)}50%{opacity:.85;transform:scale(1.06)}}
@keyframes bf-dotPulse{0%,100%{box-shadow:0 0 0 0 currentColor}50%{box-shadow:0 0 0 5px transparent}}
@keyframes bf-ghostDrift{from{transform:translate3d(0,0,0)}to{transform:translate3d(-2%,1.5%,0)}}
@keyframes bf-nebula{0%{transform:translate3d(0,0,0) scale(1)}
                     50%{transform:translate3d(-1.5%,1%,0) scale(1.05)}
                     100%{transform:translate3d(0,0,0) scale(1)}}

.bf-glow{animation:bf-glowPulse 6s var(--bf-e-soft) infinite}
.bf-dot{animation:bf-dotPulse 2.4s var(--bf-e-soft) infinite}
.bf-ghost{animation:bf-ghostDrift 18s var(--bf-e-soft) infinite alternate}
.bf-nebula{animation:bf-nebula 24s linear infinite}

/* ============================================================
   D · PRESS — tactile feedback, 160ms
   ============================================================ */
.bf-press{transition:transform var(--bf-d-press) var(--bf-e-soft),
                     background var(--bf-d-fast) var(--bf-e-soft)}
.bf-press:active{transform:scale(.86)}
.bf-press--subtle:active{transform:translateY(1px) scale(.98)}

/* ============================================================
   E · SCROLL REVEAL
   Add .bf-watch; an IntersectionObserver adds .bf-go once.
   Resting state is the finished design, so no-JS still renders.
   ============================================================ */
.bf-watch:not(.bf-go) .bf-rise > *{transform:translateY(100%)}
.bf-watch:not(.bf-go) :is(.bf-lift,.bf-pill,.bf-focus,.bf-scale){opacity:0}

/* ============================================================
   F · REDUCED MOTION — resting state, immediately
   ============================================================ */
@media (prefers-reduced-motion:reduce){
  .bf-rise{overflow:visible;padding:0;margin:0}
  .bf-rise > *{transform:none !important}
  :is(.bf-lift,.bf-pill,.bf-focus,.bf-scale){opacity:1 !important}
  *,*::before,*::after{
    animation-duration:.001ms !important;
    animation-iteration-count:1 !important;
    transition-duration:.001ms !important;
  }
}

/* ============================================================
   PROMPT 01 — NOVAPAY BASELINE · motion for the ported patterns
   Same grammar: accelerate away, decelerate in. Nothing here
   introduces a new ease.
   ============================================================ */

/* ---------- STACK — segments arrive in sequence ----------
   They slide out from under the segment to their left, so the
   bar assembles itself rather than fading in as one block. */
@keyframes bf-segIn{
  from{opacity:0;transform:translateX(calc(var(--bf-s5) * -1)) scaleX(.96)}
  to  {opacity:1;transform:none}
}
.bf-stack .bf-stack__seg{opacity:0}
.bf-go .bf-stack__seg{
  animation:bf-segIn var(--bf-d-settle) var(--bf-e-arrive) both;
  animation-delay:calc(var(--seg) * 90ms);
  transform-origin:left center;
}

/* ---------- DOME — a breath, not a pulse ----------
   24s and under 2% scale. At section size anything faster reads
   as a wobble. */
@keyframes bf-domeBreathe{
  0%,100%{transform:scale(1)}
  50%    {transform:scale(1.018)}
}
.bf-dome--live{animation:bf-domeBreathe 24s var(--bf-e-soft) infinite}

/* ---------- ORB / GLOBE ----------
   There is deliberately NO CSS rotation here. The globe's rotation and
   its two orbiting arrow arcs are baked into the asset, because an arc
   that passes behind the sphere and returns in front cannot be done
   with a transform. A CSS rotate on the still would spin the rings and
   the continents as one flat disc — the exact thing that gave the first
   attempt away. The only motion we add is a slow drift, so a static
   still in a heading is not completely dead. */
@keyframes bf-orbFloat{
  0%,100%{transform:translateY(0)}
  50%    {transform:translateY(-4%)}
}
.bf-orb--float{animation:bf-orbFloat 9s var(--bf-e-soft) infinite}

/* ---------- NOTCH — settles onto the card edge ---------- */
@keyframes bf-notchIn{
  from{opacity:0;transform:scale(.4)}
  to  {opacity:1;transform:none}
}
.bf-notch{opacity:0}
.bf-go .bf-notch{animation:bf-notchIn var(--bf-d-settle) var(--bf-e-arrive) both;animation-delay:.18s}

/* The notch owns a translate for centring, so its keyframes must
   not clear transform wholesale — scale is applied on top via a
   wrapper-free approach: translate stays in the `translate`
   property, transform carries only the scale. */

/* ---------- Reduced motion ---------- */
@media (prefers-reduced-motion:reduce){
  .bf-stack__seg,.bf-notch{opacity:1 !important}
  .bf-dome--live,.bf-orb--float{animation:none !important}
}

/* ============================================================
   PROMPT 02 — REVEAL SET
   Three reveals and a subject-anchored stage. The grammar holds:
   wipes use the symmetric shutter ease, arrivals still decelerate.
   ============================================================ */

/* ---------- WIPE — clip-path, not opacity ----------
   A wipe uncovers an element along one edge with its geometry intact,
   so a glass panel keeps its blur and a solid keeps its colour the
   whole way. Fading the same element dims it through the middle of
   the transition, which on a glass chip looks like a bug. */
@keyframes bf-wipeX{from{clip-path:inset(0 100% 0 0)}to{clip-path:inset(0 0 0 0)}}
@keyframes bf-wipeUp{from{clip-path:inset(100% 0 0 0)}to{clip-path:inset(0 0 0 0)}}

.bf-wipe-x{clip-path:inset(0 100% 0 0)}
.bf-wipe-up{clip-path:inset(100% 0 0 0)}
.bf-go .bf-wipe-x{animation:bf-wipeX .75s var(--bf-e-shutter) both}
.bf-go .bf-wipe-up{animation:bf-wipeUp .75s var(--bf-e-arrive) both}

/* ---------- WORD REVEAL ----------
   Per word, not per line. Each word is its own mask so the line
   assembles left to right. The inner span starts at 132%, not 100%:
   at 100% a descender is still showing at frame one, because the mask
   is the line box and the glyph hangs below it. The vertical padding
   and matching negative margin give descenders and accents room
   without moving the text. */
.bf-words .bf-w{
  display:inline-block;overflow:hidden;vertical-align:top;
  padding:.08em 0 .2em;margin:-.08em 0 -.2em;
}
.bf-words .bf-w > span{display:inline-block;transform:translateY(132%)}
.bf-go .bf-words .bf-w > span{
  animation:bf-wordRise 1.05s var(--bf-e-arrive) both;
  animation-delay:calc(var(--w, 0) * 55ms);
}
@keyframes bf-wordRise{from{transform:translateY(132%)}to{transform:translateY(0)}}

/* ---------- LIFT — fade plus travel, scaled by viewport ---------- */
@keyframes bf-liftQ{
  from{opacity:0;transform:translateY(calc(8px * var(--bf-travel)))}
  to  {opacity:1;transform:none}
}
.bf-liftq{opacity:0}
.bf-go .bf-liftq{animation:bf-liftQ .8s var(--bf-e-quart) both}

/* ---------- RAIL — a measuring mark that draws itself ---------- */
.bf-rail{
  width:2px;background:var(--bf-rail, #989eac);border-radius:1px;
  transform-origin:50% 0;pointer-events:none;
}
.bf-rail--anim{transform:scaleY(0)}
.bf-go .bf-rail--anim{animation:bf-railDraw .9s var(--bf-e-arrive) both}
@keyframes bf-railDraw{from{transform:scaleY(0)}to{transform:scaleY(1)}}

@media (prefers-reduced-motion:reduce){
  .bf-wipe-x,.bf-wipe-up{clip-path:none !important}
  .bf-words .bf-w > span{transform:none !important}
  .bf-liftq{opacity:1 !important}
  .bf-rail--anim{transform:none !important}
}
