mirror of
https://github.com/arthur-pbty/moon.git
synced 2026-06-03 23:36:19 +02:00
first commit
This commit is contained in:
@@ -0,0 +1,111 @@
|
||||
'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>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user