Files
portifolio-evandrodevbr/app/components/animations/CounterValue.tsx
T
Evandro Fonseca Junior b791b7de9e feat: control room hero, SEO/AEO optimization and design system fixes
Hero: replace particle field with cobe globe (Capricorn view, multi-axis drag, reduced-motion safe), telemetry HUD with pointer parallax, particle text build effect, Space Grotesk display font. SEO: short titles with template, PT-BR accents, twitter cards on dynamic cases, escaped JSON-LD via lib/jsonld.ts, CSP report-only, 25 service FAQs answer-first. Design: fix TW4 layered reset killing spacing utilities, literal @theme tokens, mobile hero overflow, header overlap, empty case cards. Remove framer-motion (unified on motion/react), header version badge, phone number from hero CTA. Production build verified (24 static pages).
2026-08-27 10:55:21 -03:00

65 lines
1.5 KiB
TypeScript

"use client";
import { useInView } from "motion/react";
import { useEffect, useRef, useState } from "react";
import { useReducedMotion } from "@/app/hooks/useReducedMotion";
interface CounterValueProps {
value: number;
className?: string;
duration?: number;
prefix?: string;
suffix?: string;
once?: boolean;
amount?: number;
}
export function CounterValue({
value,
className = "",
duration = 1200,
prefix = "",
suffix = "",
once = true,
amount = 0.45,
}: Readonly<CounterValueProps>) {
const shouldReduceMotion = useReducedMotion();
const ref = useRef<HTMLSpanElement | null>(null);
const isInView = useInView(ref, { once, amount });
const [displayValue, setDisplayValue] = useState(0);
useEffect(() => {
if (!isInView) return;
if (shouldReduceMotion) {
setDisplayValue(value);
return;
}
const start = performance.now();
let rafId = 0;
const frame = (now: number) => {
const elapsed = now - start;
const progress = Math.min(elapsed / duration, 1);
const eased = 1 - (1 - progress) ** 3;
setDisplayValue(Math.round(value * eased));
if (progress < 1) {
rafId = requestAnimationFrame(frame);
}
};
rafId = requestAnimationFrame(frame);
return () => cancelAnimationFrame(rafId);
}, [isInView, value, duration, shouldReduceMotion]);
return (
<span ref={ref} className={className}>
{prefix}
{displayValue}
{suffix}
</span>
);
}