"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,
};
}