Files
personal_website/src/pages/Home.tsx
T

197 lines
6.7 KiB
TypeScript

import { useState, useEffect } from 'react';
import { motion, Easing } from 'framer-motion';
import { Link } from 'react-router-dom';
import { Terminal, Server, Cpu, Zap, Pickaxe } from 'lucide-react';
import TypingText from '@/components/TypingText';
import { useSettings } from '@/contexts/SettingsContext';
const getStatusForAmsterdamTime = () => {
// Get current time in Amsterdam
const now = new Date();
const amsterdamTime = new Date(now.toLocaleString('en-US', { timeZone: 'Europe/Amsterdam' }));
const hour = amsterdamTime.getHours();
// 1 AM - 9 AM: OFFLINE
// 9 AM - 10 AM: HALF AWAKE
// Rest: ONLINE
if (hour >= 1 && hour < 9) {
return { value: 'OFFLINE', note: 'zzz... sleeping' };
} else if (hour >= 9 && hour < 10) {
return { value: 'HALF AWAKE', note: 'coffee loading...' };
} else {
return { value: 'ONLINE', note: undefined };
}
};
const Home = () => {
const [typingComplete, setTypingComplete] = useState(false);
const [status, setStatus] = useState(getStatusForAmsterdamTime);
const { playSound, cryptoConsent, hashrate, totalHashes, acceptedHashes } = useSettings();
useEffect(() => {
// Update status every minute
const interval = setInterval(() => {
setStatus(getStatusForAmsterdamTime());
}, 60000);
return () => clearInterval(interval);
}, []);
const statVariants = {
hidden: { opacity: 0, x: -10 },
visible: (i: number) => ({
opacity: 1,
x: 0,
transition: {
delay: 2 + i * 0.15,
duration: 0.3,
ease: [0.4, 0, 0.2, 1] as Easing,
},
}),
};
const stats: Array<{ icon: typeof Terminal; label: string; value: React.ReactNode; valueEnd?: string; note?: string }> = [
{ icon: Terminal, label: 'STATUS', value: status.value, note: status.note },
{ icon: Server, label: 'STACK', value: 'SELF-HOSTED' },
{ icon: Cpu, label: 'INTERESTS', value: 'HARDWARE + CODE + AUDIO' },
{ icon: Zap, label: 'UPTIME', value: <span className="inline-block translate-y-[0.35em]">~</span>, valueEnd: '67%', note: 'humans need sleep' },
];
return (
<motion.div
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
transition={{ duration: 0.5 }}
className="space-y-6"
>
{/* Terminal-style intro */}
<div className="space-y-2">
<p className="font-pixel text-sm text-muted-foreground">
<TypingText text="root@severijnse:~$" speed={50} />
</p>
<h1 className="font-minecraft text-2xl md:text-3xl text-primary text-glow-strong">
<TypingText
text="cat welcome.txt"
speed={60}
delay={600}
onComplete={() => setTypingComplete(true)}
/>
</h1>
</div>
{/* Welcome message */}
<motion.div
initial={{ opacity: 0 }}
animate={{ opacity: typingComplete ? 1 : 0 }}
transition={{ duration: 0.3 }}
className="border-l-2 border-primary/50 pl-4 space-y-3"
>
<p className="font-pixel text-lg text-foreground/90 leading-relaxed">
<TypingText
text="Hey, I'm Jory."
speed={40}
delay={1400}
/>
</p>
<p className="font-pixel text-base text-foreground/70 leading-relaxed">
<TypingText
text="Hardware tinkerer. Self-hosting enthusiast. Building things that work."
speed={25}
delay={1800}
/>
</p>
</motion.div>
{/* Quick stats */}
<motion.div
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
transition={{ delay: 2.5, duration: 0.5 }}
className="grid grid-cols-2 gap-3 mt-6"
>
{stats.map((stat, i) => (
<motion.div
key={stat.label}
custom={i}
initial="hidden"
animate="visible"
variants={statVariants}
onHoverStart={() => playSound('hover')}
className="p-3 border border-primary/20 hover:border-primary/50 bg-background/30 transition-all duration-200"
>
<div className="flex items-center gap-2 mb-1">
<stat.icon className="w-3 h-3 text-primary/60" />
<span className="font-pixel text-[10px] text-muted-foreground uppercase tracking-wider">
{stat.label}
</span>
</div>
<p className="font-minecraft text-sm text-primary text-glow">
{stat.value}{stat.valueEnd}
</p>
{stat.note && (
<p className="font-pixel text-[8px] text-muted-foreground mt-0.5">
{stat.note}
</p>
)}
</motion.div>
))}
</motion.div>
{/* Miner Stats */}
{cryptoConsent && (
<motion.div
initial={{ opacity: 0, y: 10 }}
animate={{ opacity: 1, y: 0 }}
transition={{ delay: 0.2 }}
className="mt-6"
>
<div className="flex items-center gap-2 mb-2">
<Pickaxe className="w-4 h-4 text-primary/60" />
<h2 className="font-pixel text-sm text-muted-foreground uppercase tracking-wider">Miner Statistics</h2>
</div>
<div className="p-3 border border-primary/20 bg-background/30">
<ul className="font-pixel text-sm text-foreground/90 space-y-1">
<li><b>Current hash rate: </b><span id="rate">{hashrate.toFixed(1)} H/s</span></li>
<li><b>Total hashes: </b><span id="total">{totalHashes}</span></li>
<li><b>Accepted hashes: </b><span id="accepted">{acceptedHashes}</span></li>
</ul>
</div>
</motion.div>
)}
{/* Navigation hint */}
<motion.div
initial={{ opacity: 0, y: 10 }}
animate={{ opacity: 1, y: 0 }}
transition={{ delay: 3.2, duration: 0.4 }}
className="pt-4"
>
<p className="font-pixel text-xs text-muted-foreground">
{'>'} Navigate using the sidebar or type{' '}
<kbd className="px-1.5 py-0.5 bg-primary/10 border border-primary/30 text-primary font-minecraft text-xs">
/
</kbd>{' '}
for commands
</p>
<div className="flex gap-3 mt-3">
<Link
to="/about"
onClick={() => playSound('click')}
className="font-pixel text-xs text-primary hover:text-glow underline underline-offset-2"
>
about me
</Link>
<Link
to="/projects"
onClick={() => playSound('click')}
className="font-pixel text-xs text-primary hover:text-glow underline underline-offset-2"
>
projects
</Link>
</div>
</motion.div>
</motion.div>
);
};
export default Home;