pnpm add motion motionCreate components/retro-clock.tsx and paste the code below:
"use client";
import { motion } from "motion/react";
import { useEffect, useState } from "react";
export default function RetroClock() {
const [time, setTime] = useState<Date | null>(null);
const [initialTime, setInitialTime] = useState<Date | null>(null);
const [location, setLocation] = useState({
city: "...",
region: "...",
offset: "...",
});
useEffect(() => {
const now = new Date();
setInitialTime(now);
setTime(now);
const timer = setInterval(() => {
setTime(new Date());
}, 1000);
fetch("https://ipwho.is/")
.then((res) => res.json())
.then((data) => {
if (data.success !== false) {
const tz =
data.timezone.id ||
Intl.DateTimeFormat().resolvedOptions().timeZone;
const [geoRegion] = tz.includes("/") ? tz.split("/") : ["UTC"];
setLocation({
city: data.city.toUpperCase(),
region: geoRegion.toUpperCase(),
offset: `UTC ${data.timezone.utc || ""}`,
});
} else {
throw new Error("API Limit or Error");
}
})
.catch(() => {
const tz = Intl.DateTimeFormat().resolvedOptions().timeZone;
const [reg, cit] = tz.includes("/")
? tz.split("/")
: ["UTC", "Universal"];
setLocation((prev) => ({
...prev,
city: cit.toUpperCase().replace("_", " "),
region: reg.toUpperCase().replace("_", " "),
}));
});
return () => clearInterval(timer);
}, []);
if (!time || !initialTime) return null;
const timeDifference = time.getTime() - initialTime.getTime();
const secondsElapsed = timeDifference / 1000;
const initialSeconds = initialTime.getSeconds();
const initialMinutes = initialTime.getMinutes();
const initialHours = initialTime.getHours();
const secondDegrees = initialSeconds * 6 + secondsElapsed * 6;
const minuteDegrees =
initialMinutes * 6 + initialSeconds * 0.1 + secondsElapsed * 0.1;
const hourDegrees =
(initialHours % 12) * 30 +
initialMinutes * 0.5 +
secondsElapsed * (30 / 3600);
const dateStr = time
.toLocaleDateString("en-US", {
month: "short",
day: "numeric",
year: "numeric",
})
.toUpperCase();
const dayStr = time
.toLocaleDateString("en-US", { weekday: "short" })
.toUpperCase();
const timeZoneAbbr = time
.toLocaleTimeString("en-us", { timeZoneName: "short" })
.split(" ")
.pop();
return (
<div className="font-tech text-muted-foreground flex aspect-square h-full w-full items-center justify-center overflow-hidden tracking-[0.2em] uppercase">
<div className="relative flex h-75 w-75 items-center justify-center text-[10px] font-medium">
<div className="absolute top-[10%] left-1/2 -translate-x-1/2 text-center leading-tight">
<span className="text-foreground block text-[1.2em] font-bold">
{dayStr}
</span>
<span>{dateStr}</span>
</div>
<div className="absolute top-1/2 right-[5%] -translate-y-1/2 text-right">
<span className="block text-[0.8em] opacity-70">REGION</span>
<span className="text-foreground font-bold">{location.region}</span>
</div>
<div className="absolute top-1/2 left-[5%] -translate-y-1/2 text-left">
<span className="block text-[0.8em] opacity-70">OFFSET</span>
<span className="text-foreground font-bold">
{location.offset || "..."}
</span>
</div>
<div className="absolute bottom-[10%] left-1/2 -translate-x-1/2 text-center leading-tight">
<span className="block text-[0.8em] opacity-70">LOCATION</span>
<span className="text-foreground mb-1 block font-bold">
{location.city}
</span>
<span className="text-[0.9em]">{timeZoneAbbr}</span>
</div>
<motion.div
className="absolute w-0.5 origin-bottom bg-blue-600 dark:bg-blue-500"
style={{ height: "36%", bottom: "50%", left: "calc(50% - 1px)" }}
animate={{ rotate: minuteDegrees }}
transition={{ type: "spring", stiffness: 300, damping: 30 }}
/>
<motion.div
className="absolute w-0.5 origin-top bg-blue-600 dark:bg-blue-500"
style={{ height: "8%", top: "50%", left: "calc(50% - 1px)" }}
animate={{ rotate: minuteDegrees }}
transition={{ type: "spring", stiffness: 300, damping: 30 }}
/>
<motion.div
className="absolute w-0.5 origin-bottom bg-red-600 dark:bg-red-500"
style={{ height: "30%", bottom: "50%", left: "calc(50% - 1px)" }}
animate={{ rotate: secondDegrees }}
transition={{ type: "spring", stiffness: 300, damping: 30 }}
/>
<motion.div
className="absolute w-0.5 origin-top bg-red-600 dark:bg-red-500"
style={{ height: "6%", top: "50%", left: "calc(50% - 1px)" }}
animate={{ rotate: secondDegrees }}
transition={{ type: "spring", stiffness: 300, damping: 30 }}
/>
<motion.div
className="absolute w-1 origin-bottom rounded-full bg-neutral-900 dark:bg-white"
style={{ height: "20%", bottom: "50%", left: "calc(50% - 2px)" }}
animate={{ rotate: hourDegrees }}
transition={{ type: "spring", stiffness: 300, damping: 30 }}
/>
<motion.div
className="absolute w-1 origin-top rounded-full bg-neutral-900 dark:bg-white"
style={{ height: "5%", top: "50%", left: "calc(50% - 2px)" }}
animate={{ rotate: hourDegrees }}
transition={{ type: "spring", stiffness: 300, damping: 30 }}
/>
<div
className="absolute h-px w-[24%] bg-neutral-900 opacity-40 dark:bg-white"
style={{ right: "50%" }}
/>
<div
className="pointer-events-none absolute h-full w-[0.5px] bg-neutral-900 opacity-10 dark:bg-white"
style={{ left: "50%" }}
/>
<div
className="pointer-events-none absolute h-[0.5px] w-full bg-neutral-900 opacity-10 dark:bg-white"
style={{ top: "50%" }}
/>
<div className="absolute z-20 aspect-square w-[3.2%] rounded-full border-2 border-black bg-yellow-400 shadow-[0_0_10px_rgba(250,204,21,0.5)]" />
</div>
</div>
);
}
import RetroClock from "@/components/retro-clock";
export default function MyComponent() {
return (
<div className="h-[300px] w-full flex items-center justify-center">
<RetroClock />
</div>
);
}