/* =====================================================================
   Here & Beyond - Giving Journey | Keystone Church
   Motion layer. Loads after styles.css and never overrides layout.

   Everything in this file is scoped to `.hb-motion` on <html>, a class
   js/motion.js adds only when motion is allowed. With JavaScript off, with
   prefers-reduced-motion, with ?static=1, or with <html data-motion="off">
   the class is never added and this stylesheet changes nothing at all -
   the page renders exactly as styles.css defines it.

   Only transform and opacity are animated. Every animated element has its motion classes stripped once
   it settles, so the resting DOM is the same as it was before.

   Author: Jose Torres <jose.torres@keystonechurch.com>
   ===================================================================== */

.hb-motion{
  --hb-ease-out: cubic-bezier(.22,.61,.36,1);
}

/* ============================================ 1 - stats count-up ====== */
/* Tabular figures only while the digits are churning, so the numbers do not
   jitter. motion.js removes the class on the last frame, which restores the
   stylesheet's own lining-nums rendering for the final value. */
.hb-motion .stat__num.hb-counting{
  font-variant-numeric:tabular-nums lining-nums;
}

/* ============================================ 2 - carousel ============ */

/* --- indicator bars ---------------------------------------------------
   The button keeps its own 80x7 box, radius and hover transform. The fill
   is a pseudo-element inside it, so nothing about the bar's geometry moves.
   Only a bar that is active AND currently timing gets .hb-bar-fill, so a
   bar that is active while the timer is stopped stays solid magenta, which
   is exactly how it looks today. */
.hb-motion .carousel__bars button{
  position:relative;
  overflow:hidden;
}
.hb-motion .carousel__bars button.hb-bar-fill[aria-current="true"]{
  background:#fff;
}
.hb-motion .carousel__bars button.hb-bar-fill[aria-current="true"]::after{
  content:"";
  position:absolute; inset:0;
  border-radius:inherit;
  background:var(--magenta,#9b119b);
  transform-origin:left center;
  transform:scaleX(var(--hb-fill,0));
}

/* --- Ken Burns on the live slide's photo ------------------------------ */
/* Every photo keeps its own compositor layer (will-change on all slides, not
   just the live one) so a slide change never promotes/demotes a layer
   mid-transition — that re-raster is what made the scrim lag the photo. */
.hb-motion .slide__media{ transform-origin:50% 50%; will-change:transform; }
.hb-motion .slide.hb-live .slide__media{
  transform:scale(var(--hb-kb,1));
}

/* --- staggered entrance for the incoming slide's copy ----------------- */
.hb-motion .slide .hb-stag{
  opacity:0;
  transform:translate3d(0,16px,0);
}
.hb-motion .slide .hb-stag.hb-stag-in{
  opacity:1;
  transform:none;
  transition:opacity .55s var(--hb-ease-out) var(--hb-d,0ms),
             transform .55s var(--hb-ease-out) var(--hb-d,0ms);
}
/* the slide being replaced dims out under the incoming one */
.hb-motion .slide.hb-leaving .slide__body{
  opacity:0;
  transition:opacity .28s linear;
}

/* ============================================ 3 - heading reveals ===== */
/* 20 Years, video and give only. The hero keeps its own load reveal and the
   carousel is handled above. */
.hb-motion .hb-rev{
  opacity:0;
  transform:translate3d(0,18px,0);
}
/* The eyebrows used to also tighten their letter-spacing as they came in; on phones the wider
   starting spacing wrapped the line and it jumped back at the end (2026-09-16), so they only
   fade and rise now, at their final spacing. */
.hb-motion .hb-rev.hb-rev-in{
  opacity:1;
  transform:none;
  transition:opacity .6s var(--hb-ease-out) var(--hb-d,0ms),
             transform .6s var(--hb-ease-out) var(--hb-d,0ms);
}

/* ============================================ safety net ============== */
/* motion.js already bails out under prefers-reduced-motion, but if the user
   flips the setting mid-session everything lands on its final state. */
@media (prefers-reduced-motion:reduce){
  .hb-motion .hb-rev,
  .hb-motion .slide .hb-stag{
    opacity:1 !important;
    transform:none !important;
    transition:none !important;
  }
  .hb-motion .slide.hb-leaving .slide__body{ opacity:1 !important; }
  .hb-motion .slide.hb-live .slide__media{ transform:none !important; }
  .hb-motion .carousel__bars button.hb-bar-fill[aria-current="true"]{
    background:var(--magenta,#9b119b);
  }
  .hb-motion .carousel__bars button.hb-bar-fill[aria-current="true"]::after{ content:none; }
}

/* =====================================================================
   ROUND 2
   ===================================================================== */

/* ---------------------------------------------------------------------
   Header lockup sizing. The <img> of here-and-beyond-logo.svg was inlined
   so the arrow paths could be masked, which took it out of the reach of
   styles.css's `.site-header__logo img` rule. These two declarations
   reproduce that rule exactly and are deliberately NOT scoped to
   .hb-motion - they are layout, not motion, and must apply with
   JavaScript off. Verified pixel-identical at 1600, 860 and 390.
   --------------------------------------------------------------------- */
.site-header__logo .site-header__mark{
  display:block;
  width:clamp(180px, 21.7vw, 347px);   /* mirrors styles.css line 158 */
  height:auto;
}
@media (max-width:560px){
  .site-header__logo .site-header__mark{ width:min(255px, 66vw); }
}

/* ============================================ 4 - logo arrow draw ===== */
/* Nothing at rest: motion.js strokes the two .hb-logo-arrow paths with a
   dash-offset trace, fades their fill in, then removes every attribute. */

/* ============================================ 5 - section depth ======= */
.hb-depth{ position:absolute; overflow:hidden; pointer-events:none; }
.hb-depth__img{
  position:absolute; inset:0;
  background-repeat:no-repeat;
  will-change:transform;
  /* The section's own identical background stays underneath, so feathering
     the top and bottom edges means a shifted copy dissolves into it instead
     of showing a seam. At zero offset the two are the same pixels, so any
     blend of them is also the same pixels - the at-rest render is exact. */
  -webkit-mask-image:linear-gradient(to bottom, transparent 0, #000 5%, #000 95%, transparent 100%);
          mask-image:linear-gradient(to bottom, transparent 0, #000 5%, #000 95%, transparent 100%);
}
/* ============================================ 20 Years photos ========= */
/* motion.js adds .hb-fade to the photo stack and walks .hb-on along it. The
   dissolve and the slow zoom are Web Animations on the photos themselves;
   these rules only decide which photos are visible and which sits on top. */
.hb-motion .hb-fade .twenty__photo{ opacity:0; }
.hb-motion .hb-fade .twenty__photo.hb-was{ opacity:1; z-index:1; }   /* outgoing: solid, underneath */
.hb-motion .hb-fade .twenty__photo.hb-on { opacity:1; z-index:2; }   /* incoming, then current */

/* .progress is not positioned in styles.css and is not a stacking context, so
   a z-index:-1 depth layer would escape behind its own background. Both are
   visually inert here: nothing overlaps the stats band.
   .give deliberately gets NEITHER - making it positioned would paint its
   background over the video frame that hangs into it, so it has no depth
   layer at all (see FIDELITY/BUILD notes). */
.hb-motion .progress{ position:relative; isolation:isolate; }

/* ============================================ 6 - video frame ========= */
@keyframes hbPulseRing{
  0%   { transform:scale(1);    opacity:.5; }
  60%  { transform:scale(1.42); opacity:0; }
  100% { transform:scale(1.42); opacity:0; }
}
@keyframes hbPlayBeat{
  0%, 60%, 100% { transform:scale(1); }
  14%           { transform:scale(1.05); }
}
.hb-motion .video__play.hb-pulse{ animation:hbPlayBeat 4s var(--hb-ease-out) infinite; }
.hb-motion .video__play.hb-pulse::after{
  content:""; position:absolute; inset:-9px;
  border:2px solid #fff; border-radius:999px;
  pointer-events:none; opacity:0;
  animation:hbPulseRing 4s var(--hb-ease-out) infinite;
}
/* hover hands control back to the stylesheet's own :hover transform */
.hb-motion .video__play.hb-pulse:hover,
.hb-motion .video__play.hb-pulse:hover::after{ animation:none; }
.hb-motion .video__play.hb-pulse:hover::after{ opacity:0; }

.hb-motion .video__frame{
  transition:transform .28s var(--hb-ease-out), box-shadow .28s var(--hb-ease-out);
}
.hb-motion .video__frame-wrap:hover .video__frame{
  transform:translateY(-4px);
  box-shadow:0 26px 62px rgba(12,4,14,.4);
}

/* poster + button cross-fade instead of main.js's instant display:none */
.hb-motion .video__frame.is-playing.hb-fading .video__poster{
  display:block; position:absolute; inset:0; z-index:2;
  opacity:1; transition:opacity .35s linear;
}
.hb-motion .video__frame.is-playing.hb-fading .video__play{
  display:grid; z-index:3;
  opacity:1; transition:opacity .35s linear;
}
.hb-motion .video__frame.is-playing.hb-fading.hb-fading-out .video__poster,
.hb-motion .video__frame.is-playing.hb-fading.hb-fading-out .video__play{ opacity:0; }

/* ============================================ 7 - one hover language == */
.hb-motion .btn--teal{
  transition:background-color .2s var(--hb-ease-out), transform .2s var(--hb-ease-out);
}
.hb-motion .btn--teal:hover{ background:#22acb4; transform:translateY(-1px); }
.hb-motion .btn--teal:active{ transform:translateY(1px); }
.hb-motion .gc__next .hb-label{ display:inline-block; transition:transform .2s var(--hb-ease-out); }
.hb-motion .gc__next:hover .hb-label{ transform:translateX(2px); }

.hb-motion .socials a{ transition:color .2s var(--hb-ease-out), transform .2s var(--hb-ease-out); }
.hb-motion .socials a:hover{ transform:scale(1.08); }

.hb-motion .carousel__viewport{ cursor:grab; }
.hb-motion .carousel.hb-dragging .carousel__viewport{ cursor:grabbing; }
.hb-motion .carousel.hb-dragging .slide__body{ pointer-events:none; }

/* ============================================ 8 - path lights ========= */
@keyframes hbTwinkle{ 0%,100%{ opacity:0; } 50%{ opacity:.85; } }
.hb-motion .hb-spark{
  position:absolute;
  width:10px; height:10px; margin:-5px 0 0 -5px;
  border-radius:50%;
  opacity:0;
  pointer-events:none;
  background:radial-gradient(circle,
    rgba(255,246,219,.95) 0%,
    rgba(255,206,122,.5) 45%,
    rgba(255,190,90,0) 70%);
  animation:hbTwinkle var(--hb-per, 2.4s) ease-in-out infinite;
  animation-delay:var(--hb-delay, 0s);
}

/* ============================================ round-2 safety net ====== */
@media (prefers-reduced-motion:reduce){
  .hb-motion .hb-depth__img.hb-drifting{ animation:none !important; transform:none !important; }
  .hb-motion .hb-fade .twenty__photo{ transform:none !important; }   /* motion.js also stops the cross-fade */
  .hb-motion .hb-spark{ animation:none !important; opacity:0 !important; }
  .hb-motion .video__play.hb-pulse,
  .hb-motion .video__play.hb-pulse::after{ animation:none !important; }
  .hb-motion .video__play.hb-pulse::after{ content:none; }
  .hb-motion .video__frame{ transition:none !important; }
}
