mirror of
https://github.com/Since-3/since3-website.git
synced 2026-10-12 19:51:16 +00:00
105 lines
3 KiB
TypeScript
105 lines
3 KiB
TypeScript
"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<HTMLDivElement>(null)
|
|
const countRef = useRef<HTMLSpanElement>(null)
|
|
const barRef = useRef<HTMLDivElement>(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 (
|
|
<div
|
|
ref={rootRef}
|
|
aria-hidden="true"
|
|
className="preloader fixed inset-0 z-[var(--z-preloader)] flex-col justify-between bg-black p-gutter text-white"
|
|
>
|
|
<div className="flex items-start justify-between font-mono text-label text-ink-400">
|
|
<span>Digitalagentur</span>
|
|
<span>Steinfurt, DE</span>
|
|
</div>
|
|
|
|
<p className="self-center font-mono text-2xl sm:text-4xl">
|
|
<span className="text-ink-500"><</span>since3
|
|
<span className="text-ink-500">></span>
|
|
</p>
|
|
|
|
<div className="flex flex-col gap-4">
|
|
<div className="flex items-end justify-between">
|
|
<span className="font-mono text-label text-ink-400">Lädt Erlebnis</span>
|
|
<span
|
|
ref={countRef}
|
|
className="text-mega leading-none tabular-nums type-poster"
|
|
>
|
|
000
|
|
</span>
|
|
</div>
|
|
<div className="h-px w-full bg-white/15">
|
|
<div ref={barRef} className="h-full origin-left bg-white [scale:0_1]" />
|
|
</div>
|
|
</div>
|
|
</div>
|
|
)
|
|
}
|