mirror of
https://github.com/arthur-pbty/moon.git
synced 2026-06-03 15:07:31 +02:00
112 lines
4.6 KiB
TypeScript
112 lines
4.6 KiB
TypeScript
'use client';
|
|
|
|
import { useState, useEffect, useMemo } from 'react';
|
|
import { useLocale } from './LocaleProvider';
|
|
import { ts } from '@/lib/i18n';
|
|
import { getMoonPhaseInfo, getNextFullMoon, FULL_MOON_NAMES } from '@/lib/lunar';
|
|
|
|
function computeCountdown(target: Date) {
|
|
const now = new Date();
|
|
const totalMs = target.getTime() - now.getTime();
|
|
if (totalMs <= 0) return { days: 0, hours: 0, minutes: 0, seconds: 0 };
|
|
|
|
return {
|
|
days: Math.floor(totalMs / (1000 * 60 * 60 * 24)),
|
|
hours: Math.floor((totalMs % (1000 * 60 * 60 * 24)) / (1000 * 60 * 60)),
|
|
minutes: Math.floor((totalMs % (1000 * 60 * 60)) / (1000 * 60)),
|
|
seconds: Math.floor((totalMs % (1000 * 60)) / 1000),
|
|
};
|
|
}
|
|
|
|
export default function HeroSection() {
|
|
const { locale } = useLocale();
|
|
const nextFull = useMemo(() => getNextFullMoon(), []);
|
|
const currentPhase = useMemo(() => getMoonPhaseInfo(new Date()), []);
|
|
const [countdown, setCountdown] = useState(() => computeCountdown(nextFull));
|
|
const [mounted, setMounted] = useState(false);
|
|
|
|
useEffect(() => {
|
|
const mountTimer = setTimeout(() => setMounted(true), 0);
|
|
|
|
const updateCountdown = () => {
|
|
setCountdown(computeCountdown(nextFull));
|
|
};
|
|
|
|
const timer = setInterval(updateCountdown, 1000);
|
|
|
|
return () => {
|
|
clearTimeout(mountTimer);
|
|
clearInterval(timer);
|
|
};
|
|
}, [nextFull]);
|
|
|
|
const nextFullName =
|
|
FULL_MOON_NAMES[nextFull.getUTCMonth() + 1]?.[locale as keyof typeof FULL_MOON_NAMES[1]] ||
|
|
FULL_MOON_NAMES[nextFull.getUTCMonth() + 1]?.en;
|
|
|
|
const formatDate = (d: Date) => {
|
|
try {
|
|
return d.toLocaleDateString(locale, { weekday: 'long', year: 'numeric', month: 'long', day: 'numeric' });
|
|
} catch {
|
|
return d.toLocaleDateString('en', { weekday: 'long', year: 'numeric', month: 'long', day: 'numeric' });
|
|
}
|
|
};
|
|
|
|
return (
|
|
<section id="hero" aria-label="Moon Phases Hero" className="relative min-h-screen flex items-center justify-center overflow-hidden pt-16">
|
|
{/* Moon glow */}
|
|
<div className="absolute top-1/4 left-1/2 -translate-x-1/2 -translate-y-1/2 w-75 h-75 md:w-125 md:h-125 rounded-full gradient-radial-glow blur-3xl pointer-events-none" />
|
|
|
|
<div className="section-container px-3 sm:px-6 text-center relative z-10">
|
|
{/* Current phase display */}
|
|
<div className="mb-4 sm:mb-6 inline-flex max-w-full flex-wrap items-center justify-center gap-2 sm:gap-3 px-3 sm:px-5 py-2 sm:py-2.5 rounded-full bg-white/5 border border-white/10">
|
|
<span className="text-2xl sm:text-3xl">{currentPhase.emoji}</span>
|
|
<span className="text-xs sm:text-sm text-white/60 leading-snug wrap-break-word">
|
|
{ts('current_phase', locale)}: {ts(currentPhase.phaseName, locale)} — {currentPhase.illumination}%
|
|
</span>
|
|
</div>
|
|
|
|
<h1 className="text-2xl sm:text-5xl md:text-7xl font-bold mb-3 sm:mb-6 leading-tight">
|
|
<span className="gradient-text-hero">
|
|
{ts('hero_title', locale)}
|
|
</span>
|
|
</h1>
|
|
|
|
<p className="text-base md:text-xl text-white/50 max-w-2xl mx-auto mb-8 sm:mb-12 leading-relaxed">
|
|
{ts('hero_subtitle', locale)}
|
|
</p>
|
|
|
|
{/* Next Full Moon Card */}
|
|
<div className="glass-card w-full max-w-lg mx-auto p-4 sm:p-6 md:p-8 mb-8">
|
|
<h2 className="text-sm uppercase tracking-widest text-indigo-300 mb-3">{ts('next_full_moon', locale)}</h2>
|
|
<div className="text-3xl mb-1">🌕</div>
|
|
<>
|
|
<p className="text-lg sm:text-xl font-semibold text-yellow-200 mb-1">{nextFullName}</p>
|
|
<p className="text-white/60 text-xs sm:text-sm mb-4 sm:mb-6 wrap-break-word">{mounted ? formatDate(nextFull) : ''}</p>
|
|
</>
|
|
|
|
{/* Countdown */}
|
|
<div className="flex flex-wrap justify-center gap-1.5 sm:gap-3 md:gap-4">
|
|
{[
|
|
{ value: countdown.days, label: ts('countdown_days', locale) },
|
|
{ value: countdown.hours, label: ts('countdown_hours', locale) },
|
|
{ value: countdown.minutes, label: ts('countdown_minutes', locale) },
|
|
{ value: countdown.seconds, label: ts('countdown_seconds', locale) },
|
|
].map((item, i) => (
|
|
<div key={i} className="countdown-box">
|
|
<span className="countdown-number">{mounted ? String(item.value).padStart(2, '0') : '--'}</span>
|
|
<span className="countdown-label">{item.label}</span>
|
|
</div>
|
|
))}
|
|
</div>
|
|
</div>
|
|
|
|
{/* CTA */}
|
|
<a href="#calendar" className="glow-btn inline-block text-base">
|
|
{ts('explore', locale)} ↓
|
|
</a>
|
|
</div>
|
|
</section>
|
|
);
|
|
}
|