/* ==========================================================================
   motion-polish.css — ADDITIVE motion layer (hero entrance + card depth +
   button ripple + section/stat reveal). Layers on top of main.css /
   woocommerce.css; never re-times or overrides existing transitions.

   RULES ENFORCED IN THIS FILE:
   - Animates transform / opacity / filter ONLY (GPU-composited). No
     width/height/top/left/margin/box-shadow-size is ever animated.
   - Zero CLS: entrance states are opacity + translate only; nothing animates
     from display:none or collapsed height; JS-injected bars reserve space up
     front (see motion-polish.js).
   - Real tokens only (verified in main.css :root): --ease-out, --ease-spring,
     --ease-in-out, --duration-fast/normal/slow, --cta-rgb, --neon-cyan-rgb,
     --alpha-cyan-08, --gradient-vapor, --radius-full, --stagger-step/-max,
     --mouse-x/--mouse-y. Reuses existing keyframe hero-title-enter.
   - One global @media (prefers-reduced-motion: reduce) block at the bottom
     neutralizes EVERY animation/transition declared here.
   ========================================================================== */

/* ==========================================================================
   1. HERO — extend the on-load cascade to above-fold elements main.css
   leaves static (main.css already animates title/subtitle/actions/social-
   proof). Reuses the existing hero-title-enter keyframe. transform+opacity.
   ========================================================================== */
.hero .hero__promo-badge,
.hero .hero__trust-badges,
.hero .hero__fanned-cards,
.hero .hero__categories {
    opacity: 0;
    transform: translateY(14px);
    animation: hero-title-enter 0.7s var(--ease-out) forwards;
}
.hero .hero__promo-badge  { animation-delay: 0.05s; } /* leads, just before H1 */
.hero .hero__trust-badges { animation-delay: 0.82s; }
/* Container only — each .hero__fanned-card keeps its own rotate() fan layout. */
.hero .hero__fanned-cards { animation-delay: 0.92s; }
.hero .hero__categories   { animation-delay: 1s; }

/* Scroll hint: gentle fade to its design resting opacity (0.5). Dedicated
   keyframe so it does not overshoot to 1; scroll-bounce + main.js untouched. */
.hero .hero__scroll-hint {
    opacity: 0;
    transform: translateY(8px);
    animation: tdc-hint-in 0.7s var(--ease-out) 1.15s forwards;
}
@keyframes tdc-hint-in {
    to { opacity: 0.5; transform: translateY(0); }
}

/* Soft breathing glow on the primary hero CTA. .btn--accent is overflow:hidden
   (sweep-shine ::before) so an outer pseudo glow would clip — use filter:
   drop-shadow (renders outside the clip, filter-composited, no box-shadow-size
   / layout change). Starts after CTA entrance (~1.3s) so it does not breathe
   while flying in. */
.hero__actions .btn--accent {
    animation: tdc-cta-breathe 3.4s var(--ease-in-out) 1.3s infinite;
}
@keyframes tdc-cta-breathe {
    0%, 100% { filter: drop-shadow(0 4px 14px rgba(var(--cta-rgb), 0.30)); }
    50%      { filter: drop-shadow(0 6px 26px rgba(var(--cta-rgb), 0.55)); }
}
/* Hover/focus holds a steady stronger glow; we only own `filter`, so main.css's
   hover translateY + box-shadow still apply. Touch users keep ambient breathe. */
.hero__actions .btn--accent:hover,
.hero__actions .btn--accent:focus-visible {
    animation: none;
    filter: drop-shadow(0 6px 28px rgba(var(--cta-rgb), 0.60));
}

/* ==========================================================================
   2. PRODUCT CARD DEPTH — cursor-tracked cyan sheen over the product image.
   Reuses --mouse-x/--mouse-y already set by initCardGlow() on .glass-card.
   Pure opacity transition; z-index:1 sits below .product-card__quick-view
   (z:2), above the image. Container is already position:relative with no
   pre-existing ::after, so collision-free.
   ========================================================================== */
.product-card__image-container::after {
    content: '';
    position: absolute;
    inset: 0;
    z-index: 1;
    pointer-events: none;
    opacity: 0;
    border-radius: inherit;
    background: radial-gradient(
        320px circle at var(--mouse-x, 50%) var(--mouse-y, 50%),
        rgba(var(--neon-cyan-rgb), 0.18) 0%,
        rgba(var(--neon-cyan-rgb), 0) 60%
    );
    mix-blend-mode: screen;
    transition: opacity var(--duration-normal) var(--ease-out);
}
@media (hover: hover) {
    .product-card__inner:hover .product-card__image-container::after,
    .woocommerce ul.products li.product:hover .product-card__image-container::after {
        opacity: 1;
    }
}

/* Image saturation/contrast pop on hover. The frame already clips
   (overflow:hidden, aspect-ratio:1/1) and the <img> already scales — we only
   add a filter on the wrap (no prior transition → purely additive, no CLS). */
.product-card__image-wrap {
    transition: filter var(--duration-normal) var(--ease-out);
}
@media (hover: hover) {
    .woocommerce ul.products li.product:hover .product-card__image-wrap {
        filter: saturate(1.06) contrast(1.02);
    }
}

/* Quick-view reveal: upgrade only the transform-channel easing to the playful
   spring overshoot; keeps the existing :active scale(0.92) press feedback. */
@media (hover: hover) {
    .product-card__inner:hover .product-card__quick-view {
        transition: opacity var(--duration-fast) var(--ease-out),
                    transform var(--duration-normal) var(--ease-spring),
                    background var(--duration-fast) var(--ease-out);
    }
}

/* ==========================================================================
   3. BUTTON RIPPLE — tasteful ink ripple injected by motion-polish.js on
   pointerdown. .btn is already position:relative + overflow:hidden so the
   ripple clips to the pill. Does NOT touch the sweep-shine ::before, the
   :active scale, or the :focus-visible ring. transform + opacity only.
   ========================================================================== */
.mp-ripple {
    position: absolute;
    z-index: 0;
    border-radius: 50%;
    pointer-events: none;
    transform: scale(0);
    opacity: 0.55;
    background: radial-gradient(
        circle,
        var(--mp-ripple-color, rgba(255, 255, 255, 0.5)) 0%,
        rgba(255, 255, 255, 0) 70%
    );
    animation: mp-ripple-expand 600ms var(--ease-out) forwards;
}
/* Light/transparent buttons: white ripple would vanish → cyan ink instead. */
.btn--outline .mp-ripple,
.btn--outline-light .mp-ripple,
.btn--white .mp-ripple {
    --mp-ripple-color: rgba(var(--neon-cyan-rgb), 0.35);
}
@keyframes mp-ripple-expand {
    to { transform: scale(2.4); opacity: 0; }
}

/* ==========================================================================
   4. SECTION-LABEL reveal accent — a thin gradient underline scale-sweeps in
   under each eyebrow as its section enters. Rides the existing .is-visible
   class added by main.js initScrollAnimations(); no extra observer needed.
   ========================================================================== */
.section-label {
    position: relative;
}
.section-label::after {
    content: "";
    position: absolute;
    left: 0;
    right: 0;
    bottom: -6px;
    height: 2px;
    border-radius: var(--radius-full);
    background: var(--gradient-vapor);
    transform: scaleX(0);
    transform-origin: left center;
    opacity: 0;
    transition:
        transform var(--duration-slow) var(--ease-out) 120ms,
        opacity var(--duration-normal) var(--ease-out) 120ms;
    pointer-events: none;
}
.section-label.is-visible::after {
    transform: scaleX(1);
    opacity: 0.9;
}

/* ==========================================================================
   5. STAT COUNTERS — count-up itself is owned by main.js initAnimatedCounters()
   (.is-counted is stamped when done). We only punctuate the finish with a
   subtle GPU-safe scale pop, and add thin gradient progress bars.
   ========================================================================== */
@keyframes motion-count-pop {
    0%   { transform: scale(1); }
    42%  { transform: scale(1.055); }
    100% { transform: scale(1); }
}
.lab-stat__value.counter-value.is-counted {
    animation: motion-count-pop 520ms var(--ease-out);
    transform-origin: center bottom;
}

/* Thin gradient progress bars under each stat. Bar markup is injected by
   motion-polish.js at load, so it reserves space immediately (no CLS); the
   fill animates via transform:scaleX only. Cascade reuses the --i index that
   main.js sets on .stagger-children. */
.lab-stat .motion-statbar {
    display: block;
    width: 100%;
    max-width: 120px;
    height: 3px;
    margin: 0.7rem auto 0;
    border-radius: var(--radius-full);
    background: var(--alpha-cyan-08);
    overflow: hidden;
}
.lab-stat .motion-statbar__fill {
    position: relative;
    display: block;
    height: 100%;
    width: 100%;
    border-radius: inherit;
    overflow: hidden;
    background: var(--gradient-vapor);
    transform: scaleX(0);
    transform-origin: left center;
    transition: transform 1.35s var(--ease-out);
    transition-delay: calc(min(var(--i, 0), var(--stagger-max, 8)) * var(--stagger-step, 80ms));
}
.lab-stat.motion-bar-in .motion-statbar__fill {
    transform: scaleX(var(--bar-fill, 1));
}
/* Decorative per-column rhythm (aesthetic only, not data claims). */
.lab-stat:nth-child(1) { --bar-fill: 0.82; }
.lab-stat:nth-child(2) { --bar-fill: 1; }
.lab-stat:nth-child(3) { --bar-fill: 1; }
.lab-stat:nth-child(4) { --bar-fill: 0.6; }

/* One-shot sheen that travels the bar once after it fills. */
.lab-stat .motion-statbar__fill::after {
    content: "";
    position: absolute;
    inset: 0;
    background: linear-gradient(
        100deg,
        transparent 0%,
        rgba(var(--neon-cyan-rgb), 0.55) 50%,
        transparent 100%
    );
    transform: translateX(-120%);
    opacity: 0;
}
@keyframes motion-statbar-sheen {
    0%   { transform: translateX(-120%); opacity: 0; }
    18%  { opacity: 0.85; }
    100% { transform: translateX(220%); opacity: 0; }
}
.lab-stat.motion-bar-in .motion-statbar__fill::after {
    animation: motion-statbar-sheen 1.1s var(--ease-out) 1.5s 1;
}

/* ==========================================================================
   6. REDUCED MOTION — MANDATORY. Single global block neutralizing ALL motion
   declared in this file to static, visible, final states (no CLS, no flash).
   ========================================================================== */
@media (prefers-reduced-motion: reduce) {
    /* Hero entrance */
    .hero .hero__promo-badge,
    .hero .hero__trust-badges,
    .hero .hero__fanned-cards,
    .hero .hero__categories {
        opacity: 1;
        transform: none;
        animation: none;
    }
    .hero .hero__scroll-hint {
        opacity: 0.5;
        transform: none;
        animation: none;
    }
    .hero__actions .btn--accent,
    .hero__actions .btn--accent:hover,
    .hero__actions .btn--accent:focus-visible {
        animation: none;
        filter: none;
    }
    /* Card depth */
    .product-card__image-container::after {
        display: none;
    }
    .woocommerce ul.products li.product:hover .product-card__image-wrap {
        filter: none;
    }
    .product-card__inner:hover .product-card__quick-view {
        transition: opacity var(--duration-fast) ease,
                    background var(--duration-fast) ease;
    }
    /* Button ripple */
    .mp-ripple {
        display: none;
        animation: none;
    }
    /* Section label */
    .section-label::after {
        transition: none;
        transform: scaleX(1);
        opacity: 0.9;
    }
    /* Stat counters + bars */
    .lab-stat__value.counter-value.is-counted {
        animation: none;
    }
    .lab-stat .motion-statbar__fill {
        transition: none;
        transform: scaleX(var(--bar-fill, 1));
    }
    .lab-stat .motion-statbar__fill::after {
        display: none;
        animation: none;
    }
}