/* ============================================================
   VYRON — animations.css
   Slide transitions (fade + zoom), fragment reveals, ambient
   No classic PowerPoint wipes — only soft, cinematic motion.
   ============================================================ */

/* ---- Slide enter / exit ----------------------------------- */
.slide {
  transition:
    opacity var(--t-slide) var(--ease),
    transform var(--t-slide) var(--ease),
    filter var(--t-slide) var(--ease);
  transform: scale(1.04);
  filter: blur(6px);
}
.slide.is-active {
  transform: scale(1);
  filter: blur(0);
}
/* Outgoing slide eases away (set briefly by engine) */
.slide.is-leaving {
  opacity: 0;
  transform: scale(.985);
  filter: blur(4px);
}

/* Per-slide transition flavor via data-transition */
.slide[data-transition="zoom"]            { transform: scale(1.16); }
.slide[data-transition="zoom"].is-active  { transform: scale(1); }
.slide[data-transition="zoom-out"]            { transform: scale(.88); }
.slide[data-transition="zoom-out"].is-active  { transform: scale(1); }
.slide[data-transition="fade"]            { transform: none; filter: none; }
.slide[data-transition="fade"].is-active  { transform: none; }

/* ---- Fragment reveal (messages one by one) ---------------- */
.fragment {
  opacity: 0;
  transform: translateY(26px);
  filter: blur(8px);
  transition:
    opacity var(--t-frag) var(--ease-out),
    transform var(--t-frag) var(--ease-out),
    filter var(--t-frag) var(--ease-out);
  will-change: opacity, transform, filter;
}

/* Variant INITIAL (hidden) states — declared BEFORE .is-visible
   so the revealed reset below always wins at equal specificity. */
.fragment[data-fx="rise"]   { transform: translateY(46px); }
.fragment[data-fx="fall"]   { transform: translateY(-30px); }
.fragment[data-fx="left"]   { transform: translateX(-48px); }
.fragment[data-fx="right"]  { transform: translateX(48px); }
.fragment[data-fx="scale"]  { transform: scale(.86); }
.fragment[data-fx="soft"]   { transform: none; filter: blur(12px); }
.fragment[data-fx="xfade"]  { transform: none; filter: none; }   /* pure opacity (bg swaps) */

/* Revealed state — MUST come after the variants above. */
.fragment.is-visible {
  opacity: 1;
  transform: none;
  filter: blur(0);
}

/* Dim previously revealed lines to spotlight the newest (optional) */
.spotlight .fragment.is-visible { opacity: .35; transition: opacity .6s var(--ease); }
.spotlight .fragment.is-visible.is-current { opacity: 1; }

/* ---- Staggered children (e.g. timeline, grid) ------------- */
.stagger > * {
  opacity: 0; transform: translateY(24px);
  transition: opacity .8s var(--ease-out), transform .8s var(--ease-out);
}
.is-active .stagger.is-visible > * { opacity: 1; transform: none; }
.is-active .stagger.is-visible > *:nth-child(2) { transition-delay: .12s; }
.is-active .stagger.is-visible > *:nth-child(3) { transition-delay: .24s; }
.is-active .stagger.is-visible > *:nth-child(4) { transition-delay: .36s; }
.is-active .stagger.is-visible > *:nth-child(5) { transition-delay: .48s; }
.is-active .stagger.is-visible > *:nth-child(6) { transition-delay: .60s; }
.is-active .stagger.is-visible > *:nth-child(7) { transition-delay: .72s; }
.is-active .stagger.is-visible > *:nth-child(8) { transition-delay: .84s; }

/* ---- Ken Burns for full-bleed media ----------------------- */
@keyframes kenburns {
  0%   { transform: scale(1.08) translate3d(0,0,0); }
  100% { transform: scale(1.18) translate3d(-1.5%, -1.5%, 0); }
}
.bg-media[data-pan="right"] img { animation-name: kenburns-r !important; }
@keyframes kenburns-r {
  0%   { transform: scale(1.1) translate3d(-1.5%,0,0); }
  100% { transform: scale(1.18) translate3d(1.5%, -1%, 0); }
}

/* ---- Intro: slow logo breathe ----------------------------- */
@keyframes breathe {
  0%,100% { opacity: .92; letter-spacing: .42em; }
  50%     { opacity: 1;   letter-spacing: .46em; }
}
.slide.is-active .logo--breathe { animation: breathe 7s var(--ease) infinite; }

/* ---- Accent rule draw on active --------------------------- */
@keyframes drawline { from { transform: scaleX(0); } to { transform: scaleX(1); } }
.rule {
  height: 2px; width: clamp(60px, 8vw, 120px); background: var(--accent);
  transform-origin: left center; transform: scaleX(0);
}
.center .rule { transform-origin: center; }
.slide.is-active .rule { animation: drawline .9s .3s var(--ease-out) forwards; }

/* ---- Soft pulse for CTA / question mark ------------------- */
@keyframes glow {
  0%,100% { text-shadow: 0 0 0 rgba(201,162,75,0); }
  50%     { text-shadow: 0 0 40px rgba(201,162,75,.35); }
}
.slide.is-active .glow { animation: glow 4.5s var(--ease) infinite; }

/* ---- Fade-up used by engine for generic reveal ------------ */
@keyframes fadeUp { from { opacity: 0; transform: translateY(18px); } to { opacity: 1; transform: none; } }

/* ============================================================
   Colour-open backgrounds (selected ODAK 2–9 slides)
   A slide opens on a bright, full-colour still and settles into the
   darker, scrimmed photo once narration begins — keeping text legible.
   A .bg-color overlay (no scrim) sits on top of the dark base and fades
   out the instant the first message shows (the engine adds .is-told to
   the slide when step > 0). Exports always show the dark/legible frame
   (see styles/print.css and tools/export.mjs CAPTURE_CSS); the colour
   open is a live-only beat.
   ============================================================ */
.bg-color { opacity: 1; transition: opacity var(--t-slide) var(--ease); }
.slide.is-told .bg-color { opacity: 0; }
