"use client" import { useEffect, useRef } from "react" import { INTRO_CLASS, INTRO_END_EVENT, INTRO_STORAGE_KEY, isIntroRunning, } from "@/modules/shared/lib/intro" const COUNT_DURATION_MS = 1600 const CURTAIN_DURATION_MS = 1000 // The hero starts animating while the curtain is still moving up const HERO_START_OFFSET_MS = 250 // Fast start, dramatic hold near 100 function easeCount(t: number) { return t < 0.7 ? (t / 0.7) * 0.86 : 0.86 + (1 - Math.pow(1 - (t - 0.7) / 0.3, 3)) * 0.14 } export function Preloader() { const rootRef = useRef(null) const countRef = useRef(null) const barRef = useRef(null) useEffect(() => { const root = rootRef.current if (!root || !isIntroRunning()) return const timers: number[] = [] let frame = 0 const start = performance.now() const finish = () => { root.dataset.state = "leaving" timers.push( window.setTimeout(() => { document.documentElement.classList.remove(INTRO_CLASS) try { sessionStorage.setItem(INTRO_STORAGE_KEY, "1") } catch { // Storage blocked: the intro simply plays again next time } window.dispatchEvent(new Event(INTRO_END_EVENT)) }, HERO_START_OFFSET_MS), window.setTimeout(() => { root.dataset.state = "done" }, CURTAIN_DURATION_MS) ) } const tick = (now: number) => { const t = Math.min((now - start) / COUNT_DURATION_MS, 1) const value = easeCount(t) if (countRef.current) { countRef.current.textContent = String(Math.round(value * 100)).padStart(3, "0") } if (barRef.current) barRef.current.style.scale = `${value} 1` if (t < 1) frame = requestAnimationFrame(tick) else finish() } frame = requestAnimationFrame(tick) return () => { cancelAnimationFrame(frame) timers.forEach(window.clearTimeout) } }, []) return (