Some checks failed
Build & Deploy / 🔍 Prepare (push) Successful in 1m25s
Build & Deploy / 🧪 QA (push) Failing after 1m25s
Build & Deploy / 🏗️ Build (push) Has been skipped
Build & Deploy / 🚀 Deploy (push) Has been skipped
Build & Deploy / 🧪 Post-Deploy Verification (push) Has been skipped
Build & Deploy / 🔔 Notify (push) Successful in 3s
- Replaced synchronous `motion` imports with `m` and `LazyMotion` across all components - Removed 1-second `animate-in` delay on LCP element in HeroVideo - Added AVIF image format support to next.config.mjs - Fixed ReferencesSlider carousel left padding alignment - Dropped Total Blocking Time (TBT) to 0ms
49 lines
1.1 KiB
TypeScript
49 lines
1.1 KiB
TypeScript
'use client';
|
|
|
|
import React, { useEffect, useState } from 'react';
|
|
import { useMotionValue, useTransform, animate, useInView, m, LazyMotion, domAnimation } from 'framer-motion';
|
|
|
|
interface AnimatedCounterProps {
|
|
value: number;
|
|
duration?: number;
|
|
delay?: number;
|
|
suffix?: string;
|
|
prefix?: string;
|
|
className?: string;
|
|
}
|
|
|
|
export function AnimatedCounter({
|
|
value,
|
|
duration = 2,
|
|
delay = 0,
|
|
suffix = '',
|
|
prefix = '',
|
|
className = '',
|
|
}: AnimatedCounterProps) {
|
|
const count = useMotionValue(0);
|
|
const rounded = useTransform(count, (latest) => {
|
|
// Format with thousands separator
|
|
return new Intl.NumberFormat('de-DE').format(Math.round(latest));
|
|
});
|
|
|
|
const ref = React.useRef(null);
|
|
const inView = useInView(ref, { once: true, margin: "-100px" });
|
|
|
|
useEffect(() => {
|
|
if (inView) {
|
|
const controls = animate(count, value, {
|
|
duration: duration,
|
|
delay: delay,
|
|
ease: 'easeOut',
|
|
});
|
|
return controls.stop;
|
|
}
|
|
}, [inView, value, duration, delay, count]);
|
|
|
|
return (
|
|
<m.span ref={ref} className={className}>
|
|
{prefix}<m.span>{rounded}</m.span>{suffix}
|
|
</m.span>
|
|
);
|
|
}
|