/* animations.css: every motion rule for the site (SPEC Part 3, CONTRACT section 7).
   style.css owns layout, color and state. This file only adds movement on top.

   Gate: motion.js puts exactly one of html.tier-high / html.tier-low / html.tier-reduced on <html>
   synchronously at the top of its IIFE. Before that class exists nothing here animates and nothing
   is hidden, so the page is fully visible and usable with this file or motion.js deleted.
   Every hidden state and every animation is scoped to a tier class. Bare selectors never hide.

   Rules of the file:
   - only transform, opacity, filter, clip-path, background-position (and the contract's two named
     exceptions: .faq-a grid-template-rows and .btn-check stroke-dashoffset) are ever animated
   - no compositor hint is declared in CSS; motion.js sets one on interaction start and clears it on end
   - :where() floors the specificity of reveal rules so .btn:active and .is-released still win
   - no em dash anywhere: periods, commas, colons and plain hyphens only */

/* ---------------------------------------------------------------------------
   1. Reveals (reveal.js adds .is-revealed and sets --i on each [data-reveal-child])
   A section that has reveal children stays visible itself; the children stagger in.
   A section with no reveal children fades and rises as one block.
--------------------------------------------------------------------------- */
:where(html.tier-high, html.tier-low) [data-reveal]:not(.is-revealed):not(:has([data-reveal-child])) {
  opacity: 0;
  transform: translateY(28px);
}
:where(html.tier-high, html.tier-low) [data-reveal]:not(.is-revealed) [data-reveal-child] {
  opacity: 0;
  transform: translateY(28px);
}
:where(html.tier-high, html.tier-low) [data-reveal].is-revealed {
  opacity: 1;
  transform: none;
  transition: opacity 700ms var(--spring-firm), transform 700ms var(--spring-firm);
}
:where(html.tier-high, html.tier-low) [data-reveal].is-revealed [data-reveal-child] {
  transition: opacity 700ms var(--spring-firm), transform 700ms var(--spring-firm);
  transition-delay: calc(var(--i, 0) * 80ms);
}

/* reduced tier: opacity only, and the global .01ms rule below makes the change instant */
:where(html.tier-reduced) [data-reveal]:not(.is-revealed):not(:has([data-reveal-child])),
:where(html.tier-reduced) [data-reveal]:not(.is-revealed) [data-reveal-child] {
  opacity: 0;
}
:where(html.tier-reduced) [data-reveal].is-revealed,
:where(html.tier-reduced) [data-reveal].is-revealed [data-reveal-child] {
  opacity: 1;
  transition: opacity .01ms linear;
}

/* gold hairline rule draws left to right when its section reveals (high tier only) */
html.tier-high .rule:not(.is-revealed) { transform: scaleX(0); }
html.tier-high .rule.is-revealed {
  transform: scaleX(1);
  transition: transform 900ms var(--spring-firm);
}

/* ---------------------------------------------------------------------------
   2. Touch feedback: press scale, release overshoot, ripple
--------------------------------------------------------------------------- */
:where(html.tier-high, html.tier-low) .btn:active,
:where(html.tier-high, html.tier-low) .qcard:active,
:where(html.tier-high, html.tier-low) .chip:active,
:where(html.tier-high, html.tier-low) .faq-q:active {
  transform: scale(.94);
  transition: transform 90ms var(--spring-snap);
}
/* motion.js adds .is-released on pointerup for 220ms */
:where(html.tier-high, html.tier-low) :is(.btn, .qcard, .chip, .faq-q).is-released {
  animation: release 220ms var(--spring-snap);
}
@keyframes release {
  0%   { transform: scale(1.05); }
  55%  { transform: scale(.985); }
  100% { transform: scale(1); }
}
/* the chosen card pops once as it locks in */
:where(html.tier-high, html.tier-low) .qcard.is-selected, :where(html.tier-high, html.tier-low) .chip.is-selected {
  animation: pickPop 320ms var(--spring-soft);
}
@keyframes pickPop {
  0%   { transform: scale(1); }
  40%  { transform: scale(1.035); }
  100% { transform: scale(1); }
}

/* the ripple span motion.js drops at the touch point (size and offset are set inline once) */
.ripple {
  position: absolute;
  z-index: -1;
  border-radius: 50%;
  background: var(--blush);
  opacity: .9;
  transform: scale(0);
  pointer-events: none;
  animation: ripple 400ms ease-out forwards;
}
.btn-primary .ripple { background: rgba(255, 255, 255, .35); }
@keyframes ripple {
  0%   { transform: scale(0); opacity: .9; }
  100% { transform: scale(1); opacity: 0; }
}
/* .faq-q has no stacking context in style.css; the ripple sits at z-index -1 so it needs one */
:where(html.tier-high, html.tier-low) .faq-q { isolation: isolate; }

/* ---------------------------------------------------------------------------
   3. Selected card / chip: blush fill sweeps left to right, gold hairline draws around it (250ms).
   style.css paints the blush at once; on the animated tiers the fill comes from ::before instead
   so the sweep is visible, and it ends in the same blush state.
--------------------------------------------------------------------------- */
:where(html.tier-high, html.tier-low) .qcard.is-selected,
:where(html.tier-high, html.tier-low) .chip.is-selected {
  background: var(--ivory);
}
:where(html.tier-high, html.tier-low) .qcard.is-selected::before,
:where(html.tier-high, html.tier-low) .chip.is-selected::before {
  content: "";
  position: absolute;
  inset: 0;
  z-index: -1;
  border-radius: inherit;
  background: var(--blush);
  transform-origin: left center;
  transform: scaleX(0);
  pointer-events: none;
  animation: fillSweep 250ms var(--spring-firm) forwards;
}
:where(html.tier-high, html.tier-low) .qcard.is-selected::after,
:where(html.tier-high, html.tier-low) .chip.is-selected::after {
  content: "";
  position: absolute;
  inset: 0;
  z-index: 1;
  border-radius: inherit;
  border: 1.5px solid var(--gold);
  clip-path: inset(0 100% 0 0);
  pointer-events: none;
  animation: hairline 250ms var(--spring-firm) forwards;
}
@keyframes fillSweep {
  0%   { transform: scaleX(0); }
  100% { transform: scaleX(1); }
}
@keyframes hairline {
  0%   { clip-path: inset(0 100% 0 0); }
  100% { clip-path: inset(0 0 0 0); }
}

/* ---------------------------------------------------------------------------
   4. Funnel step transitions. funnel.js toggles the classes; #funnel[data-dir="back"] mirrors X.
   .is-leaving is declared after .is-active so an outgoing step that still carries both wins as leaving.
--------------------------------------------------------------------------- */
:where(html.tier-high, html.tier-low) .step.is-active {
  animation: stepIn 260ms var(--spring-firm) both;
}
:where(html.tier-high, html.tier-low) #funnel[data-dir="back"] .step.is-active {
  animation-name: stepInBack;
}
:where(html.tier-high, html.tier-low) .step.is-leaving {
  animation: stepOut 180ms var(--spring-firm) forwards;
}
:where(html.tier-high, html.tier-low) #funnel[data-dir="back"] .step.is-leaving {
  animation-name: stepOutBack;
}
@keyframes stepOut {
  0%   { transform: translateX(0); opacity: 1; }
  100% { transform: translateX(-32px); opacity: 0; }
}
@keyframes stepIn {
  0%   { transform: translateX(32px); opacity: 0; }
  100% { transform: translateX(0); opacity: 1; }
}
@keyframes stepOutBack {
  0%   { transform: translateX(0); opacity: 1; }
  100% { transform: translateX(32px); opacity: 0; }
}
@keyframes stepInBack {
  0%   { transform: translateX(-32px); opacity: 0; }
  100% { transform: translateX(0); opacity: 1; }
}

/* card stagger inside a newly active step. backwards fill (not both) so the finished animation
   releases transform and :active feedback still works on the cards afterwards.
   motion.js sets --i on the chips (hero cards carry it inline), capped at 9 so the last delay is 540ms.
   Note: .is-released is deliberately NOT combined with cardIn here. Re-declaring the animation
   shorthand on release restarted cardIn, so a tapped chip jumped 20px at opacity 0 between pointerup
   and click and the click could land on the container. Step cards get their bounce from cardIn's
   spring-soft ease instead; .is-released still applies to buttons and the hero cards. */
:where(html.tier-high, html.tier-low) .step.is-active .qcard,
:where(html.tier-high, html.tier-low) .step.is-active .chip {
  animation: cardIn 320ms var(--spring-soft) backwards;
  animation-delay: calc(var(--i, 0) * 60ms);
}
@keyframes cardIn {
  0%   { transform: translateY(20px) scale(.97); opacity: 0; }
  100% { transform: translateY(0) scale(1); opacity: 1; }
}

/* reassurance line reads as a response, not a preload: 120ms after the choice */
:where(html.tier-high, html.tier-low) .reassure.is-visible {
  animation: reassureIn 320ms var(--spring-firm) 120ms both;
}
@keyframes reassureIn {
  0%   { transform: translateY(12px); opacity: 0; }
  100% { transform: translateY(0); opacity: 1; }
}

/* progress bar: funnel.js sets --p, the fill springs to it */
:where(html.tier-high, html.tier-low) .progress-fill {
  transition: transform 420ms var(--spring-firm);
}

/* ---------------------------------------------------------------------------
   5. Sticky CTA bar: slides up at 25% depth, breathes until the first funnel interaction
--------------------------------------------------------------------------- */
:where(html.tier-high, html.tier-low) #sticky {
  transition: transform 460ms var(--spring-firm);
}
:where(html.tier-high, html.tier-low) #sticky.is-shown.is-pulsing #sticky-cta {
  animation: breathe 2.6s ease-in-out infinite;
}
html.is-hidden #sticky-cta { animation-play-state: paused; }
@keyframes breathe {
  0%, 100% { transform: scale(1); }
  50%      { transform: scale(1.025); }
}

/* ---------------------------------------------------------------------------
   6. FAQ accordion: style.css toggles grid-template-rows 0fr / 1fr, this eases it
--------------------------------------------------------------------------- */
:where(html.tier-high, html.tier-low) .faq-a {
  transition: grid-template-rows 360ms var(--spring-firm);
}
:where(html.tier-high, html.tier-low) .faq-a-inner { transition: visibility 0s linear 360ms; }
:where(html.tier-high, html.tier-low) .faq-item.is-open .faq-a-inner { transition-delay: 0s; }

/* ---------------------------------------------------------------------------
   7. Shimmer sweep (SPEC rules verbatim, gated by tier). Runs on high and low, never on reduced.
--------------------------------------------------------------------------- */
.shimmer { position: relative; overflow: hidden; isolation: isolate; }
:where(html.tier-high, html.tier-low) .shimmer::after {
  content: "";
  position: absolute;
  inset: 0;
  background: linear-gradient(105deg, transparent 35%, rgba(255, 255, 255, .55) 48%, rgba(201, 169, 106, .75) 52%, transparent 65%);
  transform: translateX(-140%);
  animation: sweep 4.5s cubic-bezier(.25, .8, .35, 1) infinite;
  pointer-events: none;
}
@keyframes sweep {
  0%, 72% { transform: translateX(-140%); }
  100%    { transform: translateX(140%); }
}
/* on primary buttons the sheen passes behind the label (z-index -1 inside the button's own stacking
   context) and carries no white: a gold-only tint at 8% over deep-rose #B0546A keeps the white label at
   4.6:1 at the sweep's peak (SPEC: 4.5:1 at peak brightness; 12% would land at 4.44). The SPEC gradient
   at full strength would drop it to 1.8:1. Logo and non-primary buttons keep the SPEC values. */
:where(html.tier-high, html.tier-low) .btn-primary.shimmer::after {
  z-index: -1;
  background: linear-gradient(105deg, transparent 35%, rgba(201, 169, 106, .08) 50%, transparent 65%);
}
/* pause: off-screen (motion.js IntersectionObserver), pressed, keyboard focused, or tab hidden */
.shimmer.is-paused::after,
.shimmer:active::after,
.shimmer:focus-visible::after,
html.is-hidden .shimmer::after {
  animation-play-state: paused;
}
/* the brand logo is the attention piece (owner request): a fast repeating gold sweep across the wordmark
   plus a glow-and-scale flash, one flash per 2.4s cycle (well under the WCAG 2.3.1 limit of three per
   second). motion.js adds .logo-flash to #nav .logo; it pauses with the tab and is off on tier-reduced. */
:where(html.tier-high, html.tier-low) .logo-flash::after {
  animation: logoSweep 2.4s cubic-bezier(.25, .8, .35, 1) .4s infinite;
  background: linear-gradient(105deg, transparent 30%, rgba(255, 255, 255, .9) 46%, rgba(201, 169, 106, 1) 52%, transparent 70%);
  background-size: 300% 100%;
  background-position: 200% 0;
}
@keyframes logoSweep {
  0%, 60% { background-position: 200% 0; }
  100%    { background-position: -100% 0; }
}
html.is-hidden .logo-flash::after { animation-play-state: paused; }
:where(html.tier-high, html.tier-low) .logo-flash img {
  transform-origin: left center;
  animation: logoFlash 2.4s ease-in-out .4s infinite;
}
@keyframes logoFlash {
  0%, 100% { transform: scale(1); filter: drop-shadow(0 0 0 rgba(201, 169, 106, 0)) brightness(1); }
  40%      { transform: scale(1.05); filter: drop-shadow(0 0 5px rgba(201, 169, 106, .85)) brightness(1.12); }
  52%      { transform: scale(1.09); filter: drop-shadow(0 0 12px rgba(201, 169, 106, 1)) brightness(1.4); }
  70%      { transform: scale(1.02); filter: drop-shadow(0 0 3px rgba(201, 169, 106, .5)) brightness(1.05); }
}
html.is-hidden .logo-flash img { animation-play-state: paused; }
:where(html.tier-high, html.tier-low) .shimmer-once::after {
  animation-duration: 1.6s;
  animation-delay: .6s;
  animation-iteration-count: 1;
}

/* ---------------------------------------------------------------------------
   8. Ambient, high tier only: hero gradient drift (18s) and three gold particles.
   html.is-hidden (tab hidden) and html.is-busy (confetti running) pause both.
   html.no-drift (perf self-check) switches the drift off but keeps the still gradient.
--------------------------------------------------------------------------- */
html.tier-high #hero::before {
  content: "";
  position: absolute;
  inset: -50%;
  z-index: 0;
  pointer-events: none;
  background:
    radial-gradient(35% 30% at 34% 36%, var(--blush) 0%, rgba(247, 228, 231, 0) 62%),
    radial-gradient(28% 25% at 66% 40%, var(--ivory) 0%, rgba(253, 251, 249, 0) 60%),
    radial-gradient(30% 28% at 60% 65%, rgba(201, 169, 106, .04) 0%, rgba(201, 169, 106, 0) 65%);
  transform: translate3d(0, 0, 0);
  animation: drift 18s ease-in-out infinite alternate;
}
@keyframes drift {
  0%   { transform: translate3d(0, 0, 0); }
  100% { transform: translate3d(-14%, -10%, 0) scale(1.06); }
}
html.is-hidden #hero::before,
html.is-busy #hero::before,
html.hero-off #hero::before { animation-play-state: paused; }
html.no-drift #hero::before { animation: none; }

/* particles: motion.js appends 3 span.particle with --dur, --x, --y, --delay (position set inline once) */
html.tier-high .particle {
  animation: float var(--dur, 14s) ease-in-out infinite;
  animation-delay: var(--delay, 0s);
}
@keyframes float {
  0%, 100% { transform: translate3d(0, 0, 0); opacity: .08; }
  50%      { transform: translate3d(var(--x, 12px), var(--y, -44px), 0); opacity: .22; }
}
html.is-hidden .particle,
html.is-busy .particle,
html.hero-off .particle { animation-play-state: paused; }

/* schedule timeline: GSAP scrubs .tl-line scaleY on high tier; a node pops as the line passes */
html.tier-high .tl-node.is-lit {
  animation: nodePop 360ms var(--spring-soft);
}
@keyframes nodePop {
  0%   { transform: scale(1); }
  45%  { transform: scale(1.28); }
  100% { transform: scale(1); }
}

/* ---------------------------------------------------------------------------
   9. Submit button states: spinner rotates under .is-loading, the check draws under .is-success.
   style.css owns the ring's size and colors and shows/hides both.
--------------------------------------------------------------------------- */
:where(html.tier-high, html.tier-low) .btn.is-loading .btn-spinner {
  animation: spin 800ms linear infinite;
}
@keyframes spin {
  0%   { transform: rotate(0deg); }
  100% { transform: rotate(360deg); }
}
:where(html.tier-high, html.tier-low) .btn.is-success .btn-check path {
  stroke-dasharray: 24;
  stroke-dashoffset: 24;
  animation: check 400ms var(--spring-firm) forwards;
}
@keyframes check {
  0%   { stroke-dashoffset: 24; }
  100% { stroke-dashoffset: 0; }
}

/* ---------------------------------------------------------------------------
   10. Reduced motion: every animation and transition collapses to .01ms, one iteration, no delay.
   Applied via the tier class (motion.js) and again via the media query as a belt and braces.
--------------------------------------------------------------------------- */
html.tier-reduced *,
html.tier-reduced *::before,
html.tier-reduced *::after {
  animation-duration: .01ms !important;
  animation-iteration-count: 1 !important;
  animation-delay: 0s !important;
  transition-duration: .01ms !important;
  transition-delay: 0s !important;
  scroll-behavior: auto !important;
}
html.tier-reduced .shimmer::after { animation: none !important; }

@media (prefers-reduced-motion: reduce) {
  *,
  *::before,
  *::after {
    animation-duration: .01ms !important;
    animation-iteration-count: 1 !important;
    animation-delay: 0s !important;
    transition-duration: .01ms !important;
    transition-delay: 0s !important;
    scroll-behavior: auto !important;
  }
  .shimmer::after { animation: none !important; }
}


/* ---------------------------------------------------------------------------
   11. Sweat drips (owner request): droplets form under the event name, fall and fade, staggered so one is
   always moving. Transform and opacity only. The hero name also bobs gently. Off on tier-reduced.
--------------------------------------------------------------------------- */
:where(html.tier-high, html.tier-low) .drip .drop {
  animation: dripFall 3.2s cubic-bezier(.55, 0, .85, .55) var(--d, 0s) infinite;
}
@keyframes dripFall {
  0%   { transform: translateY(0) scale(.4, .5); opacity: 0; }
  18%  { transform: translateY(1px) scale(.9, 1.15); opacity: .95; }
  32%  { transform: translateY(3px) scale(1, 1.25); opacity: 1; }
  78%  { transform: translateY(34px) scale(.9, 1.1); opacity: .85; }
  100% { transform: translateY(52px) scale(.6, .8); opacity: 0; }
}
:where(html.tier-high) #hero .event-name-text {
  animation: sweatBob 4.2s ease-in-out infinite;
  transform-origin: left center;
}
@keyframes sweatBob {
  0%, 100% { transform: translateY(0) rotate(0deg); }
  50%      { transform: translateY(-2px) rotate(-1deg); }
}
html.is-hidden .drip .drop, html.is-hidden #hero .event-name-text { animation-play-state: paused; }

:where(html.tier-high, html.tier-low) .drip-mark .drop { animation-name: dripFallShort; animation-duration: 2.8s; }
@keyframes dripFallShort {
  0%   { transform: translateY(0) scale(.4, .5); opacity: 0; }
  20%  { transform: translateY(1px) scale(.9, 1.15); opacity: .95; }
  35%  { transform: translateY(2px) scale(1, 1.25); opacity: 1; }
  80%  { transform: translateY(18px) scale(.9, 1.1); opacity: .85; }
  100% { transform: translateY(26px) scale(.6, .8); opacity: 0; }
}

/* the trust pills drift a hair, staggered, so the bar feels alive without asking for attention */
:where(html.tier-high) .trust-item {
  animation: pillFloat 5.5s ease-in-out infinite;
}
:where(html.tier-high) .trust-item:nth-child(2) { animation-delay: -1.4s; }
:where(html.tier-high) .trust-item:nth-child(3) { animation-delay: -2.8s; }
:where(html.tier-high) .trust-item:nth-child(4) { animation-delay: -4.1s; }
@keyframes pillFloat {
  0%, 100% { transform: translateY(0); }
  50%      { transform: translateY(-4px); }
}
html.is-hidden .trust-item { animation-play-state: paused; }

/* a slow gold shine crosses the popular ticket */
:where(html.tier-high) .tier.is-popular { position: relative; overflow: hidden; }
:where(html.tier-high) .tier.is-popular::after {
  content: "";
  position: absolute;
  inset: 0;
  z-index: 0;
  pointer-events: none;
  background: linear-gradient(105deg, transparent 38%, rgba(255, 255, 255, .5) 50%, transparent 62%);
  transform: translateX(-140%);
  animation: sweep 6.5s cubic-bezier(.25, .8, .35, 1) infinite;
}
:where(html.tier-high) .tier.is-popular > * { position: relative; z-index: 1; }
html.is-hidden .tier.is-popular::after { animation-play-state: paused; }
