Added countdown to each card

This commit is contained in:
GeorgeWebberley 2026-01-28 15:37:16 +01:00
parent 883b3d93f7
commit ee830965db
3 changed files with 123 additions and 38 deletions

View file

@ -1,25 +1,10 @@
import EventCard from "@/components/EventCard"; // app/page.tsx
import dynamic from 'next/dynamic'; import MissionControl from '@/components/MissionControl';
const Starfield = dynamic(() => import('@/components/Starfield'), {
ssr: false
});
export default function Home() { export default function Home() {
return ( return (
<main className="min-h-screen flex flex-col items-center justify-center p-8"> <main>
<Starfield /> <MissionControl />
<h1 className="text-white font-mono text-2xl mb-12 tracking-[0.2em] uppercase">
Mission Control // Ground Station
</h1>
<div className="grid grid-cols-1 md:grid-cols-2 gap-6 w-full max-w-4xl">
<EventCard
title="ISS Pass: Home"
targetDate={new Date()}
/>
</div>
</main> </main>
); );
} }

View file

@ -1,32 +1,67 @@
"use client"; "use client";
import { motion } from "framer-motion"; import { useState, useEffect } from 'react';
import { Rocket } from "lucide-react"; import { EventCardProps } from '@/types/space';
import { EventCardProps } from "@/types/space"
export default function EventCard({ title, targetDate, icon }: EventCardProps) { export default function EventCard({ title, targetDate, icon }: EventCardProps) {
// 1. Setup the state to hold our strings
const [timeLeft, setTimeLeft] = useState({ h: "00", m: "00", s: "00" });
useEffect(() => {
const updateTimer = () => {
const now = new Date().getTime();
const distance = targetDate.getTime() - now;
// If the time has passed, keep it at zero
if (distance < 0) {
setTimeLeft({ h: "00", m: "00", s: "00" });
return;
}
// Math to convert milliseconds to Hours, Minutes, and Seconds
const h = Math.floor((distance % (1000 * 60 * 60 * 24)) / (1000 * 60 * 60));
const m = Math.floor((distance % (1000 * 60 * 60)) / (1000 * 60));
const s = Math.floor((distance % (1000 * 60)) / 1000);
// 2. Update the state (padStart ensures we always see '05' instead of '5')
setTimeLeft({
h: h.toString().padStart(2, '0'),
m: m.toString().padStart(2, '0'),
s: s.toString().padStart(2, '0')
});
};
// Run the timer every 1000ms (1 second)
const interval = setInterval(updateTimer, 1000);
// Call it once immediately so the user doesn't see 00:00:00 for the first second
updateTimer();
// Important: Clean up the timer if the component disappears
return () => clearInterval(interval);
}, [targetDate]);
return ( return (
<motion.div <div className="relative p-6 rounded-xl border border-slate-800 bg-slate-900/40 backdrop-blur-md overflow-hidden group">
initial={{ opacity: 0, y: 20 }}
animate={{ opacity: 1, y: 0 }}
className="relative p-6 rounded-xl border border-slate-800 bg-slate-900/50 backdrop-blur-md overflow-hidden group"
>
<div className="absolute inset-0 bg-gradient-to-r from-blue-500/20 to-purple-500/20 opacity-0 group-hover:opacity-100 transition-opacity pointer-events-none" />
<div className="flex items-center gap-4 mb-4"> <div className="flex items-center gap-4 mb-4">
<div className="p-2 bg-blue-500/10 rounded-lg text-blue-400"> <div className="p-2 bg-blue-500/10 rounded-lg text-blue-400">
{icon || <Rocket size={20} />} {icon}
</div> </div>
<h3 className="text-slate-300 font-mono tracking-widest uppercase text-sm">{title}</h3> <h3 className="text-slate-300 font-mono tracking-widest uppercase text-xs">
{title}
</h3>
</div> </div>
<div className="text-4xl font-mono text-white flex gap-2"> <div className="text-4xl font-mono text-white flex gap-2">
<span>02</span><span className="text-slate-500">:</span> <span>{timeLeft.h}</span>
<span>14</span><span className="text-slate-500">:</span> <span className="text-slate-500">:</span>
<span>55</span> <span>{timeLeft.m}</span>
<span className="text-slate-500">:</span>
<span>{timeLeft.s}</span>
</div> </div>
<p className="text-xs text-slate-500 mt-2 font-mono uppercase">T-Minus to Horizon</p> <p className="text-[10px] text-slate-500 mt-2 font-mono uppercase tracking-widest leading-none">
</motion.div> T-Minus to Horizon
</p>
</div>
); );
} }

View file

@ -0,0 +1,65 @@
"use client";
import { useMemo } from 'react';
import dynamic from 'next/dynamic';
import EventCard from '@/components/EventCard';
import { Satellite, Rocket, Moon, Sparkles } from 'lucide-react';
// Now we can safely call dynamic with ssr:false here
const Starfield = dynamic(() => import('@/components/Starfield'), {
ssr: false
});
export default function MissionControl() {
// Use the BASE_TIME from your terminal (Jan 2026 value)
const BASE_TIME = 1769610273000;
const events = useMemo(() => [
{
title: "ISS Overhead: Home",
date: new Date(BASE_TIME + 1000 * 60 * 60 * 2),
icon: <Satellite size={20} />,
},
{
title: "Next Lunar Phase: Full",
date: new Date(BASE_TIME + 1000 * 60 * 60 * 24 * 3),
icon: <Moon size={20} />,
},
{
title: "Starlink Train",
date: new Date(BASE_TIME + 1000 * 60 * 45),
icon: <Rocket size={20} />,
},
{
title: "Meteor Shower",
date: new Date(BASE_TIME + 1000 * 60 * 60 * 24 * 10),
icon: <Sparkles size={20} />,
}
], []);
return (
<div className="relative min-h-screen flex flex-col items-center p-8 overflow-hidden">
<Starfield />
<header className="z-10 text-center mb-16 mt-10">
<h1 className="text-4xl md:text-6xl font-mono font-bold text-white tracking-tighter mb-4 uppercase">
Mission<span className="text-blue-500 font-black">Control</span>
</h1>
<p className="text-slate-400 font-mono text-xs tracking-[0.3em] uppercase opacity-70">
Ground Station // [55.6761° N, 12.5683° E]
</p>
</header>
<div className="z-10 grid grid-cols-1 md:grid-cols-2 gap-6 w-full max-w-5xl">
{events.map((event, index) => (
<EventCard
key={index}
title={event.title}
targetDate={event.date}
icon={event.icon}
/>
))}
</div>
</div>
);
}