197 lines
6.7 KiB
TypeScript
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;
|