"use client"; import { type HTMLMotionProps, motion, useReducedMotion } from "motion/react"; import { type ReactNode, useEffect, useState } from "react"; import { cn } from "@/lib/utils"; const FALL = 0.24; const LAND = 0.32; // Opaque on purpose: flaps stack, so an alpha token would show the layers through. const tile = "bg-[color-mix(in_srgb,var(--color-foreground)_3%,var(--color-card))] dark:bg-[color-mix(in_srgb,var(--color-foreground)_7%,var(--color-card))]"; type HalfProps = HTMLMotionProps<"span"> & { side: "top" | "bottom"; children: ReactNode }; function Half({ side, children, className, ...props }: HalfProps) { return ( {children} ); } export interface FlipDigitProps { value: string; className?: string; } export function FlipDigit({ value, className }: FlipDigitProps) { const reduce = useReducedMotion(); const [shown, setShown] = useState({ current: value, previous: value, flip: 0 }); if (shown.current !== value) setShown((s) => ({ current: value, previous: s.current, flip: s.flip + 1 })); const { current, previous, flip } = shown; const flipping = !reduce && current !== previous; return ( {current} {flipping ? previous : current} {flipping && ( <> {previous} setShown((s) => (s.flip === flip ? { ...s, previous: s.current } : s))} side="bottom" transition={{ delay: FALL, duration: LAND, times: [0, 0.55, 0.78, 1], ease: [ [0.5, 0, 1, 0.6], [0, 0, 0.4, 1], [0.6, 0, 1, 1], ], }} > {current} )} ); } export interface FlipClockUnit { label: string; value: number; digits?: number; } export interface FlipClockProps { units: FlipClockUnit[]; label: string; className?: string; } export function FlipClock({ units, label, className }: FlipClockProps) { return (
{label} {units.map((u, i) => (
{i > 0 && ( )}
{String(u.value) .padStart(u.digits ?? 2, "0") .split("") .map((d, j) => ( ))}
{u.label}
))}
); } export function useCountdown(target: number | null) { const [now, setNow] = useState(null); useEffect(() => { setNow(Date.now()); let id: ReturnType; const tick = () => { setNow(Date.now()); id = setTimeout(tick, 1000 - (Date.now() % 1000)); }; id = setTimeout(tick, 1000 - (Date.now() % 1000)); return () => clearTimeout(id); }, []); const left = target && now ? Math.max(0, Math.floor((target - now) / 1000)) : 0; return { ready: now !== null && target !== null, days: Math.floor(left / 86400), hours: Math.floor((left % 86400) / 3600), minutes: Math.floor((left % 3600) / 60), seconds: left % 60, }; }