Sunil Prasad c48a11b13c feat: Add smooth 5-second auto-rotation to Hero stories and ProductShowcase cards
- Hero auto-rotates story backdrops every 5 seconds with 500ms fade transitions
- ProductShowcase cards rotate with mint ring highlight on the active card
- Inactive cards fade to 60% opacity on mobile, full opacity on desktop
- New components: StoryBackdrop, StoryOnboarding, CountUp, lib/stories
- Manual Another-story control still works

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-12 14:10:48 -07:00

52 lines
1.3 KiB
TypeScript

"use client";
import { useEffect, useRef, useState } from "react";
/** Animates from 0 to `value` when scrolled into view. */
export function CountUp({
value,
prefix = "",
suffix = "",
decimals = 0,
duration = 1400,
}: {
value: number;
prefix?: string;
suffix?: string;
decimals?: number;
duration?: number;
}) {
const ref = useRef<HTMLSpanElement>(null);
const [display, setDisplay] = useState(0);
useEffect(() => {
const el = ref.current;
if (!el) return;
const io = new IntersectionObserver(
(entries) => {
if (!entries[0].isIntersecting) return;
io.disconnect();
const start = performance.now();
const tick = (now: number) => {
const p = Math.min((now - start) / duration, 1);
const eased = 1 - Math.pow(1 - p, 3); // ease-out cubic
setDisplay(value * eased);
if (p < 1) requestAnimationFrame(tick);
};
requestAnimationFrame(tick);
},
{ threshold: 0.4 }
);
io.observe(el);
return () => io.disconnect();
}, [value, duration]);
return (
<span ref={ref}>
{prefix}
{display.toLocaleString("en-US", { maximumFractionDigits: decimals, minimumFractionDigits: decimals })}
{suffix}
</span>
);
}