@import "tailwindcss";

/*
  Teleton Rifa design tokens.
  Mapped from DESIGN.md frontmatter (colors, typography, motion, breakpoints).
  Single-accent discipline: one brand red ({colors.primary}) carries the page.
  Light-only surface (no dark-mode tokens exist in the source).
*/

:root {
  /* ── Colors ── */
  --brand-primary: #dc0d15;        /* brand red — every CTA, progress fill, stat figures */
  --brand-primary-soft: #e94b52;   /* lighter red — secondary surfaces / soft CTA */
  --brand-primary-tint: #f28b8e;   /* pale pink — progress-bar diagonal stripes only */
  --brand-steelblue: #0066b1;      /* secondary accent — download links, supporting UI */
  --brand-powderblue: #4da6d4;     /* softer companion blue */
  --brand-ink: #000000;            /* true black — strongest figures */
  --brand-ink-strong: #111111;     /* headlines, primary text */
  --brand-muted-label: #555555;    /* field labels (modal) */
  --brand-muted: #666666;          /* secondary text, captions, footer */
  --brand-on-primary: #ffffff;     /* text on red CTAs */
  --brand-canvas: #ffffff;         /* page floor */
  --brand-track: #e0e0e0;          /* progress-bar empty track */
  --brand-hairline: #eeeeee;       /* 1px dividers */

  /* ── Campaign accents (Manual visual de campaña TLHT 2026) ── */
  --brand-teal: #88cdd3;           /* celeste — soft companion accent */
  --brand-amber: #fab400;          /* amarillo — ascentos / sparkle rays */
  --brand-violet: #aa67a2;         /* morado — tertiary accent */
  --brand-gray: #6c6c6c;           /* neutral gray (manual "negro 70%") */

  /* ── Type families (set as CSS vars; bound to next/font in layout.tsx) ── */
  /* Manual visual TLHT: Montserrat = títulos y encabezados, Poppins = cuerpos. */
  --font-body: var(--font-poppins), system-ui, sans-serif;        /* Poppins — cuerpos de texto */
  --font-display: var(--font-montserrat), system-ui, sans-serif;  /* Montserrat — títulos y encabezados */
  --font-impact: var(--font-montserrat), system-ui, sans-serif;   /* Montserrat — figuras de impacto */

  /* ── Type scale (size + line-height) ── */
  --fs-display-xl: 72px;     --lh-display-xl: 1.0;
  --fs-display-lg: 60px;     --lh-display-lg: 1.05;
  --fs-display-md: 47px;     --lh-display-md: 1.1;
  --fs-display-sm: 36px;     --lh-display-sm: 1.15;
  --fs-stat-figure: 40px;    --lh-stat-figure: 1.1;
  --fs-heading-lg: 34px;     --lh-heading-lg: 1.2;
  --fs-heading-md: 24px;     --lh-heading-md: 1.3;
  --fs-heading-sm: 20px;     --lh-heading-sm: 1.4;
  --fs-body-lg: 18px;        --lh-body-lg: 1.6;
  --fs-body-md: 14px;        --lh-body-md: 1.55;
  --fs-caption: 13px;        --lh-caption: 1.4;
  --fs-micro: 10px;          --lh-micro: 1.4;
  --fs-button: 18px;         --lh-button: 1.0;
  --fs-account-number: 13px; --lh-account-number: 1.4;

  /* ── Border radius ── */
  --radius-sm: 6px;
  --radius-md: 10px;
  --radius-lg: 12px;
  --radius-xl: 20px;
  --radius-full: 9999px;

  /* ── Spacing (10px base unit) ── */
  --space-xs: 10px;
  --space-sm: 20px;
  --space-md: 24px;
  --space-lg: 30px;
  --space-xl: 36px;
  --space-xxl: 40px;
  --space-section: 60px;

  /* ── Elevation ── */
  --shadow-drop: 0px 4px 20px rgba(0, 0, 0, 0.1);
  --shadow-drop-tight: 0px 4px 12px rgba(0, 0, 0, 0.15);
  --shadow-modal: 0 8px 40px rgba(0, 0, 0, 0.25);
  --shadow-video-modal: 0 8px 40px rgba(0, 0, 0, 0.6);

  /* ── Motion ── */
  --ease-standard: cubic-bezier(0.4, 0, 0.2, 1);
}

/*
  Tailwind v4 @theme block — exposes tokens as utilities:
    bg-primary, text-ink, font-display, rounded-xl, text-display-xl, etc.
*/
@theme inline {
  /* Colors */
  --color-primary: var(--brand-primary);
  --color-primary-soft: var(--brand-primary-soft);
  --color-primary-tint: var(--brand-primary-tint);
  --color-steelblue: var(--brand-steelblue);
  --color-powderblue: var(--brand-powderblue);
  --color-ink: var(--brand-ink);
  --color-ink-strong: var(--brand-ink-strong);
  --color-muted-label: var(--brand-muted-label);
  --color-muted: var(--brand-muted);
  --color-on-primary: var(--brand-on-primary);
  --color-canvas: var(--brand-canvas);
  --color-track: var(--brand-track);
  --color-hairline: var(--brand-hairline);
  --color-teal: var(--brand-teal);
  --color-amber: var(--brand-amber);
  --color-violet: var(--brand-violet);
  --color-gray: var(--brand-gray);

  /* Fonts (font-body, font-display, font-impact) */
  --font-body: var(--font-body);
  --font-display: var(--font-display);
  --font-impact: var(--font-impact);

  /* Radius */
  --radius-sm: var(--radius-sm);
  --radius-md: var(--radius-md);
  --radius-lg: var(--radius-lg);
  --radius-xl: var(--radius-xl);
  --radius-full: var(--radius-full);

  /* Type scale (font-size utilities: text-display-xl, text-stat-figure, ...) */
  --text-display-xl: var(--fs-display-xl);
  --text-display-xl--line-height: var(--lh-display-xl);
  --text-display-lg: var(--fs-display-lg);
  --text-display-lg--line-height: var(--lh-display-lg);
  --text-display-md: var(--fs-display-md);
  --text-display-md--line-height: var(--lh-display-md);
  --text-display-sm: var(--fs-display-sm);
  --text-display-sm--line-height: var(--lh-display-sm);
  --text-stat-figure: var(--fs-stat-figure);
  --text-stat-figure--line-height: var(--lh-stat-figure);
  --text-heading-lg: var(--fs-heading-lg);
  --text-heading-lg--line-height: var(--lh-heading-lg);
  --text-heading-md: var(--fs-heading-md);
  --text-heading-md--line-height: var(--lh-heading-md);
  --text-heading-sm: var(--fs-heading-sm);
  --text-heading-sm--line-height: var(--lh-heading-sm);
  --text-body-lg: var(--fs-body-lg);
  --text-body-lg--line-height: var(--lh-body-lg);
  --text-body-md: var(--fs-body-md);
  --text-body-md--line-height: var(--lh-body-md);
  --text-caption: var(--fs-caption);
  --text-caption--line-height: var(--lh-caption);
  --text-micro: var(--fs-micro);
  --text-micro--line-height: var(--lh-micro);
  --text-button: var(--fs-button);
  --text-button--line-height: var(--lh-button);

  /* Spacing scale extensions (p-section, gap-xxl, ...) */
  --spacing-xs: var(--space-xs);
  --spacing-sm: var(--space-sm);
  --spacing-md: var(--space-md);
  --spacing-lg: var(--space-lg);
  --spacing-xl: var(--space-xl);
  --spacing-xxl: var(--space-xxl);
  --spacing-section: var(--space-section);

  /* Shadows (shadow-drop, shadow-modal, ...) */
  --shadow-drop: var(--shadow-drop);
  --shadow-drop-tight: var(--shadow-drop-tight);
  --shadow-modal: var(--shadow-modal);
  --shadow-video-modal: var(--shadow-video-modal);

  /* Breakpoints (DESIGN.md frontmatter) */
  --breakpoint-xs: 417px;
  --breakpoint-sm: 480px;
  --breakpoint-modal: 520px;
  --breakpoint-md: 768px;
  --breakpoint-tablet: 820px;
  --breakpoint-lg: 1024px;
  --breakpoint-xl: 1440px;
}

body {
  background: var(--brand-canvas);
  color: var(--brand-ink-strong);
  font-family: var(--font-body);
}

/*
  Signature motion (DESIGN.md § motion):
    - moveStripes: the progress-bar barber-pole stripe animation.
    - modalIn: the bank-account / payment modal scale-in.
  The stripe gradient and growth transition are applied on the component itself.
*/
@keyframes moveStripes {
  0% { background-position: 0 0; }
  100% { background-position: 40px 0; }
}

@keyframes modalIn {
  from { opacity: 0; transform: scale(0.92); }
  to { opacity: 1; transform: scale(1); }
}

/*
  Accessibility gate (WCAG 2.1 AA): disable the stripe + modal animations and
  the floating-button slide for users who prefer reduced motion. Components also
  read this query in JS, but this is the global CSS backstop.
*/
@media (prefers-reduced-motion: reduce) {
  *,
  *::before,
  *::after {
    animation-duration: 0.001ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.001ms !important;
    scroll-behavior: auto !important;
  }
}

/* Visible keyboard focus ring (token-colored, AA-contrast against white). */
:focus-visible {
  outline: 3px solid var(--brand-steelblue);
  outline-offset: 2px;
}

/*
  ── Lenis smooth scroll ──
  Recommended Lenis base styles. Only active once SmoothScrollProvider mounts
  (Lenis adds `.lenis` / `.lenis-smooth` to <html>); inert otherwise. The
  prefers-reduced-motion backstop above already forces scroll-behavior:auto.
*/
html.lenis,
html.lenis body {
  height: auto;
}
.lenis.lenis-smooth {
  scroll-behavior: auto !important;
}
.lenis.lenis-smooth [data-lenis-prevent] {
  overscroll-behavior: contain;
}
.lenis.lenis-stopped {
  overflow: hidden;
}
.lenis.lenis-smooth iframe {
  pointer-events: none;
}

/*
  ── Scroll-reveal initial state ──
  [data-reveal] sections start hidden ONLY when the pre-paint bootstrap in
  app/layout.tsx has added `motion-ready` to <html> — which it does solely when
  JS runs AND motion is allowed. So with JS disabled, or under reduced motion,
  the class is never added and every section renders fully visible (no FOUC, and
  content can never be stranded invisible). GSAP (SmoothScrollProvider) reveals
  each element on scroll-in, then clears the inline transform.

  Setting the hidden state pre-paint (via the class) is what prevents the
  flash-of-visible-then-hidden that a JS-only initial state would cause for
  above-the-fold sections.
*/
html.motion-ready [data-reveal] {
  /* Fade only — no translate. A vertical slide on the section wrapper layered
     on top of the scroll motion read as a "jump" as each section entered, and
     it shifted the position the heading ScrollTriggers (nested inside) measured
     against. Opacity-only is jump-proof; the entrance energy now lives in the
     heading spring + the boletos pop. will-change is set inline by the reveal
     tween (not here) so clearProps can actually remove it afterwards. */
  opacity: 0;
}

/* Animated-heading words (AnimatedHeading, spring-scale-in). Hidden pre-paint
   by opacity only; GSAP owns the scale (0.7 -> 1 overshoot) inline and clears
   the transform on completion, so no stylesheet transform lingers. */
html.motion-ready [data-ah-word] {
  opacity: 0;
}

/* Watchdog reveal: if the motion engine never initialises (chunk fails to load
   / hydration never commits), the pre-paint bootstrap timer adds this class so
   content is forced visible rather than stranded blank. Wins over the hidden
   rules above. SmoothScrollProvider cancels the timer when it comes up, so on
   the happy path this never applies. */
html.motion-failsafe [data-reveal],
html.motion-failsafe [data-ah-word] {
  opacity: 1 !important;
  transform: none !important;
  will-change: auto !important;
}

/* Belt-and-suspenders: even if `motion-ready` is somehow present under reduced
   motion, never hide content from a reduced-motion user. */
@media (prefers-reduced-motion: reduce) {
  html.motion-ready [data-reveal],
  html.motion-ready [data-ah-word] {
    opacity: 1 !important;
    transform: none !important;
  }
}

/*
  ── Number pop-in (Transitions.dev) ──
  Per-digit pop with blur + staggered rise. PopInNumber splits a formatted
  figure into `.t-digit` spans inside a `.t-digit-group`, then adds
  `.is-animating` when the group scrolls into view. The reduced-motion query
  disables it (the digits just render in place).
*/
:root {
  --digit-dur: 500ms;
  --digit-distance: 8px;
  --digit-stagger: 70ms;
  --digit-blur: 2px;
  --digit-ease: cubic-bezier(0.34, 1.45, 0.64, 1);
  --digit-dir-x: 0;
  --digit-dir-y: 1;
}

@keyframes t-digit-pop-in {
  0% {
    transform: translate(
      calc(var(--digit-distance) * var(--digit-dir-x)),
      calc(var(--digit-distance) * var(--digit-dir-y))
    );
    opacity: 0;
    filter: blur(var(--digit-blur));
  }
  100% {
    transform: translate(0, 0);
    opacity: 1;
    filter: blur(0);
  }
}

.t-digit-group {
  display: inline-flex;
  align-items: baseline;
}
.t-digit {
  display: inline-block;
}
/* will-change is scoped to the animating phase only (not the static .t-digit
   rule) so the compositor hint is not held for the whole session — matching the
   reveal/heading will-change discipline elsewhere in this file. Per-digit
   stagger is applied inline by PopInNumber (animation-delay), which supports
   numbers of any length; the original 2-level [data-stagger] rules were dropped. */
.t-digit-group.is-animating .t-digit {
  will-change: transform, opacity, filter;
  animation: t-digit-pop-in var(--digit-dur) var(--digit-ease) both;
}

@media (prefers-reduced-motion: reduce) {
  .t-digit-group .t-digit {
    animation: none !important;
  }
}

/*
  ── Transitions.dev recipes (.claude/skills/transitions-dev) ──
  Tokens + CSS for the three transitions used on the purchase flow:
    - text-states-swap : in-place button-label swaps  (TextSwap)
    - success-check    : the donation-confirmed checkmark (SuccessCheck)
    - error shake      : the "try again" jolt on flow error alerts (useErrorShake)
  Each ships its prefers-reduced-motion guard (kept verbatim). will-change is
  scoped to the animating phase where practical, per this file's discipline.
*/
:root {
  /* Text states swap (recipe 04) */
  --text-swap-dur: 150ms;
  --text-swap-translate-y: 4px;
  --text-swap-blur: 2px;
  --text-swap-ease: ease-in-out;

  /* Success check (recipe 10) */
  --check-opacity-dur: 550ms;
  --check-rotate-dur: 550ms;
  --check-rotate-from: 80deg;
  --check-bob-dur: 450ms;
  --check-y-amount: 40px;
  --check-blur-dur: 500ms;
  --check-blur-from: 10px;
  --check-path-dur: 550ms;
  --check-path-delay: 80ms;
  --check-ease-out: cubic-bezier(0.22, 1, 0.36, 1);
  --check-ease-opacity: cubic-bezier(0.22, 1, 0.36, 1);
  --check-ease-rotate: cubic-bezier(0.22, 1, 0.36, 1);
  --check-ease-bob: cubic-bezier(0.34, 1.35, 0.64, 1);
  --check-ease-path: cubic-bezier(0.22, 1, 0.36, 1);

  /* Error shake (recipe 12) */
  --shake-distance: 6px;
  --shake-overshoot: 4px;
  --shake-dur-a: 80ms;
  --shake-dur-b: 60ms;
  --shake-ease: cubic-bezier(0.22, 1, 0.36, 1);
}

/* Text states swap — old text exits up + blur, new enters from below. */
.t-text-swap {
  display: inline-block;
  transform: translateY(0);
  filter: blur(0);
  opacity: 1;
  transition:
    transform var(--text-swap-dur) var(--text-swap-ease),
    filter var(--text-swap-dur) var(--text-swap-ease),
    opacity var(--text-swap-dur) var(--text-swap-ease);
  will-change: transform, filter, opacity;
}
.t-text-swap.is-exit {
  transform: translateY(calc(var(--text-swap-translate-y) * -1));
  filter: blur(var(--text-swap-blur));
  opacity: 0;
}
.t-text-swap.is-enter-start {
  transform: translateY(var(--text-swap-translate-y));
  filter: blur(var(--text-swap-blur));
  opacity: 0;
  transition: none;
}
@media (prefers-reduced-motion: reduce) {
  .t-text-swap {
    transition: none !important;
  }
}

/* Success check — wrapper fades + rotates + blurs + Y-bobs; the <path> draws. */
.t-success-check {
  display: inline-block;
  transform-origin: center;
  opacity: 0;
  will-change: transform, opacity, filter;
}
.t-success-check svg {
  display: block;
  overflow: visible;
}
.t-success-check svg path {
  stroke-dasharray: 20;
  stroke-dashoffset: 20;
}
.t-success-check[data-state="in"] {
  animation:
    t-check-fade var(--check-opacity-dur) var(--check-ease-opacity) forwards,
    t-check-rotate var(--check-rotate-dur) var(--check-ease-rotate) forwards,
    t-check-blur var(--check-blur-dur) var(--check-ease-out) forwards,
    t-check-bob var(--check-bob-dur) var(--check-ease-bob) forwards;
}
.t-success-check[data-state="in"] svg path {
  animation: t-check-draw var(--check-path-dur) var(--check-ease-path)
    var(--check-path-delay, 0ms) forwards;
}
@keyframes t-check-fade {
  from {
    opacity: 0;
  }
  to {
    opacity: 1;
  }
}
@keyframes t-check-rotate {
  from {
    transform: rotate(var(--check-rotate-from));
  }
  to {
    transform: rotate(0deg);
  }
}
@keyframes t-check-blur {
  from {
    filter: blur(var(--check-blur-from));
  }
  to {
    filter: blur(0);
  }
}
@keyframes t-check-bob {
  from {
    translate: 0 var(--check-y-amount);
  }
  to {
    translate: 0 0;
  }
}
@keyframes t-check-draw {
  to {
    stroke-dashoffset: 0;
  }
}
@media (prefers-reduced-motion: reduce) {
  .t-success-check {
    animation: none !important;
    opacity: 1;
  }
  .t-success-check svg path {
    animation: none !important;
    stroke-dashoffset: 0 !important;
  }
}

/* Error shake — percussive "try again" jolt. Keyframe is recipe 12 verbatim;
   applied to a flow error alert (not an input), so the trigger class is scoped
   to `.t-shake` and will-change is held only while shaking. */
.t-shake.is-shaking {
  will-change: transform;
  animation: t-input-shake
    calc(var(--shake-dur-a) * 2 + var(--shake-dur-b) * 2) linear;
}
@keyframes t-input-shake {
  0% {
    transform: translateX(0);
    animation-timing-function: var(--shake-ease);
  }
  28.57% {
    transform: translateX(var(--shake-distance));
    animation-timing-function: var(--shake-ease);
  }
  57.14% {
    transform: translateX(calc(var(--shake-distance) * -1));
    animation-timing-function: var(--shake-ease);
  }
  78.57% {
    transform: translateX(var(--shake-overshoot));
    animation-timing-function: var(--shake-ease);
  }
  100% {
    transform: translateX(0);
  }
}
@media (prefers-reduced-motion: reduce) {
  .t-shake {
    animation: none !important;
    transform: none !important;
  }
}
