/* ============================================================
   ANIMATIONS.CSS — motion primitives only.
   This file used to be an orphaned legacy stylesheet that
   force-darkened the whole site with !important; nothing linked
   it. It has been rewritten from scratch as the animation layer:
   initial (pre-reveal) states, keyframes and the reduced-motion
   escape hatches. It contains NO colour or layout decisions.

   HOW THE REVEAL GATE WORKS
   The pre-paint script in includes/header.php adds `ch-js` to
   <html>. Only then are elements allowed to start invisible, so
   with JavaScript disabled the page renders fully visible
   instead of blank. animations.js adds `ch-anim-off` when it
   cannot animate (reduced motion, or GSAP failed to load), which
   restores everything immediately. A 3s CSS failsafe covers the
   remaining case where the script never executes at all.
   ============================================================ */

/* ---- Initial states for scroll reveals ----
   The :not() chain matches the guard in animations.js: an element that
   carries a §14 variant class gets ONLY the variant's from-state, so it
   can never be left at opacity 0 by a rule whose matching tween was
   skipped. */
.ch-js .anim-section:not(.anim-scale):not(.anim-rotate3d):not(.anim-clip):not(.anim-blur):not(.anim-slide-l):not(.anim-slide-r) {
    opacity: 0;
    transform: translateY(38px);
}
.ch-js .anim-stagger > * {
    opacity: 0;
    transform: translateY(30px);
}

/* Per-section entrance variants (brief §14 — every major section
   must enter differently). animations.js reads the class name and
   picks the matching tween; these rules only set the FROM state. */
.ch-js .anim-scale      { opacity: 0; transform: scale(0.9); }
.ch-js .anim-rotate3d   { opacity: 0; transform: perspective(1200px) rotateX(14deg) translateY(46px); }
.ch-js .anim-clip       { clip-path: inset(0 0 100% 0); }
.ch-js .anim-mask img   { clip-path: inset(0 0 100% 0); transform: scale(1.14); }
.ch-js .anim-blur       { opacity: 0; filter: blur(14px); transform: translateY(26px); }
.ch-js .anim-slide-l    { opacity: 0; transform: translateX(-56px); }
.ch-js .anim-slide-r    { opacity: 0; transform: translateX(56px); }
.ch-js .anim-chars .char { opacity: 0; transform: translateY(100%) rotateX(-40deg); }

/* Parallax layers: JS writes translateY, CSS only promotes them */
.ch-parallax,
.ch-parallax-slow,
.ch-parallax-fast { will-change: transform; }

/* ---- Escape hatches ------------------------------------------
   Any of these must fully restore the pre-reveal states.
   ------------------------------------------------------------- */
.ch-anim-off .anim-section,
.ch-anim-off .anim-stagger > *,
.ch-anim-off .anim-scale,
.ch-anim-off .anim-rotate3d,
.ch-anim-off .anim-blur,
.ch-anim-off .anim-slide-l,
.ch-anim-off .anim-slide-r,
.ch-anim-off .anim-chars .char {
    opacity: 1 !important;
    transform: none !important;
    filter: none !important;
}
.ch-anim-off .anim-clip,
.ch-anim-off .anim-mask img { clip-path: none !important; }
.ch-anim-off .anim-mask img { transform: none !important; }

/* Failsafe: if the reveal script never runs at all, un-hide after
   3s. Scoped to :not(.ch-anim-ready) because animations.js adds
   that class the moment it starts — otherwise a fill-forwards CSS
   animation would outrank GSAP's inline styles and freeze every
   reveal mid-tween. */
.ch-js:not(.ch-anim-ready) .anim-section,
.ch-js:not(.ch-anim-ready) .anim-stagger > *,
.ch-js:not(.ch-anim-ready) .anim-scale,
.ch-js:not(.ch-anim-ready) .anim-rotate3d,
.ch-js:not(.ch-anim-ready) .anim-blur,
.ch-js:not(.ch-anim-ready) .anim-slide-l,
.ch-js:not(.ch-anim-ready) .anim-slide-r {
    animation: ch-anim-failsafe 0.4s 3s forwards;
}
@keyframes ch-anim-failsafe {
    to { opacity: 1; transform: none; filter: none; }
}

/* Clip-based reveals need their own failsafe: the keyframe above never
   touches clip-path, so without this a section whose script never ran
   would stay clipped to nothing forever. */
.ch-js:not(.ch-anim-ready) .anim-clip,
.ch-js:not(.ch-anim-ready) .anim-mask img {
    animation: ch-anim-failsafe-clip 0.4s 3s forwards;
}
@keyframes ch-anim-failsafe-clip {
    to { clip-path: inset(0 0 0 0); transform: none; }
}

/* ============================================================
   KEYFRAMES
   Referenced from theme.css / components.css. Kept in one place
   so the same motion vocabulary is reused rather than reinvented
   per component (§23: motion should feel like one system).
   ============================================================ */

/* Loader spinner + its self-clearing fallback */
@keyframes ch-spin { to { transform: rotate(360deg); } }
@keyframes ch-loader-auto {
    to { opacity: 0; visibility: hidden; }
}

/* Save-button heart pop */
@keyframes ch-heart-pop {
    0%   { transform: scale(1); }
    40%  { transform: scale(1.35); }
    70%  { transform: scale(0.92); }
    100% { transform: scale(1); }
}

/* Success ring after a coupon code is revealed / copied */
@keyframes ch-burst {
    0%   { opacity: 0.9; transform: scale(0.4); }
    70%  { opacity: 0.25; }
    100% { opacity: 0; transform: scale(3.4); }
}

/* Slow vertical float, used for the hero badges when GSAP is the
   one driving them this is unused — it is the CSS-only fallback. */
@keyframes ch-float {
    0%, 100% { transform: translateY(0); }
    50%      { transform: translateY(-12px); }
}

/* Conic tint drift behind the category glass panels */
@keyframes ch-drift {
    to { transform: rotate(360deg); }
}

/* Skeleton shimmer for lazy-loaded imagery */
@keyframes ch-shimmer {
    0%   { background-position: -420px 0; }
    100% { background-position: 420px 0; }
}

/* Marquee fallback: animations.js normally drives the store strip
   with GSAP so it can pause on hover, but if GSAP is unavailable
   this keeps the strip moving instead of leaving it frozen.
   Gated on `.is-dup`, which animations.js adds only once it has
   appended the second copy of the items: the keyframe slides the
   track by exactly half its width, so on a strip that was never
   duplicated it would scroll the logos clean off the screen. */
@keyframes ch-marquee {
    from { transform: translateX(0); }
    to   { transform: translateX(-50%); }
}
.ch-anim-off .ch-marquee-track.is-dup,
.ch-js:not(.ch-anim-ready) .ch-marquee-track.is-dup {
    animation: ch-marquee 34s linear infinite;
}

/* Category panel tint rotates slowly only while hovered, so there
   is no permanently spinning element on the page (§23). */
@media (hover: hover) and (prefers-reduced-motion: no-preference) {
    .ch-cat-panel:hover::before {
        animation: ch-drift 14s linear infinite;
    }
}

/* Hero badges float via CSS when GSAP is not driving them */
.ch-anim-off .ch-hero-badge { animation: none; }

/* ---- Lazy image loading ------------------------------------- */
img[loading="lazy"] {
    background-color: var(--bg-elevated);
}
.ch-img-skeleton {
    background: linear-gradient(90deg,
        var(--bg-elevated) 0%, var(--bg-input) 40%, var(--bg-elevated) 80%);
    background-size: 420px 100%;
    animation: ch-shimmer 1.4s linear infinite;
}

/* ---- Animated link underline (§12) -------------------------- */
.ch-link-u {
    position: relative;
    display: inline-block;
    color: var(--accent);
    font-weight: 600;
}
.ch-link-u::after {
    content: '';
    position: absolute;
    left: 0; right: 0; bottom: -2px;
    height: 1.5px;
    background: currentColor;
    transform: scaleX(0);
    transform-origin: left;
    transition: transform 0.35s var(--ease-out-quint);
}
.ch-link-u:hover::after { transform: scaleX(1); }

/* ---- Image hover zoom + clip reveal (§12) ------------------- */
.ch-zoom-wrap { overflow: hidden; border-radius: inherit; }
.ch-zoom-wrap img {
    transition: transform 0.7s var(--ease-out-quint);
    will-change: transform;
}
.ch-zoom-wrap:hover img { transform: scale(1.07); }

/* ---- Reduced motion --------------------------------------- */
@media (prefers-reduced-motion: reduce) {
    .ch-marquee-track,
    .ch-cat-panel::before,
    .ch-hero-badge,
    .ch-img-skeleton { animation: none !important; }
    .ch-zoom-wrap:hover img { transform: none !important; }
    .ch-page-loader { animation: ch-loader-auto 0.001ms 0.3s forwards; }
}
