"use client" import { useEffect, useMemo, useRef, useState } from "react" import Link from "next/link" import { Button } from "@/components/ui/button" import { ArrowRight, ChevronDown, Sparkles } from "lucide-react" import { useParallax, useScrollY } from "@/hooks/use-parallax" import homeData from "@/data/home.json" function parseStatValue(raw: string) { const match = raw.match(/-?\d[\d,.]*/) if (!match || match.index === undefined) { return null } const numericRaw = match[0].replace(/,/g, "") const value = Number(numericRaw) if (Number.isNaN(value)) { return null } return { prefix: raw.slice(0, match.index), suffix: raw.slice(match.index + match[0].length), value, decimals: (numericRaw.split(".")[1] ?? "").length, } } function formatStatValue(parsed: NonNullable>, progress: number) { const currentValue = parsed.value * progress if (parsed.decimals > 0) { return `${parsed.prefix}${currentValue.toFixed(parsed.decimals)}${parsed.suffix}` } return `${parsed.prefix}${Math.round(currentValue).toLocaleString()}${parsed.suffix}` } function AnimatedStatValue({ value, start }: { value: string; start: boolean }) { const parsed = useMemo(() => parseStatValue(value), [value]) const [displayValue, setDisplayValue] = useState(() => parsed ? formatStatValue(parsed, 0) : value ) useEffect(() => { if (!parsed) { setDisplayValue(value) return } if (!start) { setDisplayValue(formatStatValue(parsed, 0)) return } if (window.matchMedia("(prefers-reduced-motion: reduce)").matches) { setDisplayValue(value) return } const duration = 1400 const startAt = performance.now() let frame = 0 const animate = (now: number) => { const progress = Math.min((now - startAt) / duration, 1) const eased = 1 - Math.pow(1 - progress, 3) setDisplayValue(formatStatValue(parsed, eased)) if (progress < 1) { frame = window.requestAnimationFrame(animate) } } frame = window.requestAnimationFrame(animate) return () => window.cancelAnimationFrame(frame) }, [parsed, start, value]) return ( {displayValue} ) } export function HeroSection() { const { hero } = homeData const heroAny = hero as any const title = heroAny.title ?? heroAny.headline1 ?? "" const titleHighlight = heroAny.titleHighlight ?? heroAny.headline2 ?? "" const primaryCta = heroAny.primaryCta ?? { label: heroAny.ctaPrimary ?? "免费预约演示", href: "#contact", } const secondaryCta = heroAny.secondaryCta ?? { label: heroAny.ctaSecondary ?? "查看产品方案", href: "#products", } const stats = Array.isArray(heroAny.stats) ? heroAny.stats : [] const tags = Array.isArray(heroAny.tags) ? heroAny.tags : [] const { ref: bgRef, translateY: bgY } = useParallax(0.45) const { ref: floatRef, translateY: floatY } = useParallax(-0.15) const scrollY = useScrollY() const contentOpacity = Math.max(0, 1 - scrollY / 500) const statsRef = useRef(null) const [statsVisible, setStatsVisible] = useState(false) useEffect(() => { const node = statsRef.current if (!node || statsVisible) { return } const observer = new IntersectionObserver( ([entry]) => { if (entry.isIntersecting) { setStatsVisible(true) observer.disconnect() } }, { threshold: 0.35 } ) observer.observe(node) return () => observer.disconnect() }, [statsVisible]) return (
{/* ── 深色渐变背景(视差层) ── */}
) }