Added countdown to each card
This commit is contained in:
parent
883b3d93f7
commit
ee830965db
25
app/page.tsx
25
app/page.tsx
|
|
@ -1,25 +1,10 @@
|
|||
import EventCard from "@/components/EventCard";
|
||||
import dynamic from 'next/dynamic';
|
||||
|
||||
const Starfield = dynamic(() => import('@/components/Starfield'), {
|
||||
ssr: false
|
||||
});
|
||||
// app/page.tsx
|
||||
import MissionControl from '@/components/MissionControl';
|
||||
|
||||
export default function Home() {
|
||||
return (
|
||||
<main className="min-h-screen flex flex-col items-center justify-center p-8">
|
||||
<Starfield />
|
||||
|
||||
<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>
|
||||
<MissionControl />
|
||||
</main>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -1,32 +1,67 @@
|
|||
"use client";
|
||||
import { motion } from "framer-motion";
|
||||
import { Rocket } from "lucide-react";
|
||||
import { EventCardProps } from "@/types/space"
|
||||
|
||||
import { useState, useEffect } from 'react';
|
||||
import { EventCardProps } from '@/types/space';
|
||||
|
||||
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 (
|
||||
<motion.div
|
||||
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="relative p-6 rounded-xl border border-slate-800 bg-slate-900/40 backdrop-blur-md overflow-hidden group">
|
||||
<div className="flex items-center gap-4 mb-4">
|
||||
<div className="p-2 bg-blue-500/10 rounded-lg text-blue-400">
|
||||
{icon || <Rocket size={20} />}
|
||||
{icon}
|
||||
</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 className="text-4xl font-mono text-white flex gap-2">
|
||||
<span>02</span><span className="text-slate-500">:</span>
|
||||
<span>14</span><span className="text-slate-500">:</span>
|
||||
<span>55</span>
|
||||
<span>{timeLeft.h}</span>
|
||||
<span className="text-slate-500">:</span>
|
||||
<span>{timeLeft.m}</span>
|
||||
<span className="text-slate-500">:</span>
|
||||
<span>{timeLeft.s}</span>
|
||||
</div>
|
||||
|
||||
<p className="text-xs text-slate-500 mt-2 font-mono uppercase">T-Minus to Horizon</p>
|
||||
</motion.div>
|
||||
<p className="text-[10px] text-slate-500 mt-2 font-mono uppercase tracking-widest leading-none">
|
||||
T-Minus to Horizon
|
||||
</p>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
65
components/MissionControl.tsx
Normal file
65
components/MissionControl.tsx
Normal 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>
|
||||
);
|
||||
}
|
||||
Loading…
Reference in a new issue