'use client' import { useState, useEffect, useMemo } from 'react' import { type Milestone, type StatItem, MILESTONES, STATS, TODAY_POSITION } from './MilestonesSlide.data' import { type Theme, MONO } from './MilestonesSlide.themes' // ── Star Field ──────────────────────────────────────────────────────────────── export function StarField() { const stars = useMemo(() => { let s = 77 const r = () => { s = (s * 9301 + 49297) % 233280; return s / 233280 } return Array.from({ length: 95 }, () => ({ x: r() * 100, y: r() * 100, size: r() * 1.4 + 0.3, op: r() * 0.5 + 0.15 })) }, []) return (
{stars.map((st, i) => (
))}
) } export function SoftGrid({ t }: { t: Theme }) { return (
) } // ── Timeline ────────────────────────────────────────────────────────────────── interface MilestoneWithPos extends Milestone { x: number; row: 'top' | 'bottom' } export function Timeline({ onSelect, selectedId, t, de }: { onSelect: (m: Milestone) => void selectedId: string | null t: Theme de: boolean }) { const trackW = 1160 const innerPad = 120 const usableW = trackW - innerPad * 2 const positions = MILESTONES.map((_, i) => innerPad + (usableW * i) / (MILESTONES.length - 1)) const todayX = innerPad + usableW * TODAY_POSITION const layout: MilestoneWithPos[] = MILESTONES.map((m, i) => ({ ...m, x: positions[i], row: i % 2 === 0 ? 'top' : 'bottom', })) const railColor = t.key === 'dark' ? '#a78bfa' : '#7c3aed' return (
{/* rail background */} {/* past progress */} {/* future dashed */} {/* connector stubs */} {layout.map((m) => ( ))} {/* HEUTE marker -- circles only; pill is HTML below */} {/* HEUTE pill -- HTML so it sits above milestone cards */}
HEUTE
{layout.map((m) => ( onSelect(m)} active={selectedId === m.id} /> ))}
) } function MilestoneNode({ m, onClick, active, t, de }: { m: MilestoneWithPos; onClick: () => void; active: boolean; t: Theme; de: boolean }) { const [hover, setHover] = useState(false) const lit = hover || active const isTop = m.row === 'top' const cardY = isTop ? 4 : 200 const nodeColor = m.done ? t.done : m.tint const bgTopA = lit ? m.tint + t.cardTintTopH : m.tint + t.cardTintTop const bgMidA = lit ? m.tint + t.cardTintMidH : m.tint + t.cardTintMid const cardBg = `linear-gradient(180deg, ${bgTopA} 0%, ${bgMidA} 55%, ${t.cardBase}${lit ? t.cardBaseAH : t.cardBaseA})` const badge = m.done ? (de ? 'erledigt' : 'done') : (m.next ? (de ? 'als n\u00e4chstes' : 'next') : (de ? 'geplant' : 'plan')) return ( <> {/* dot */}
setHover(true)} onMouseLeave={() => setHover(false)} style={{ position: 'absolute', left: m.x - 14, top: 180 - 14, width: 28, height: 28, borderRadius: '50%', background: m.done ? `radial-gradient(circle at 35% 30%, ${t.doneBright}, ${t.doneSolid} 60%, ${t.doneDeep})` : `radial-gradient(circle at 35% 30%, ${m.tint}dd, ${m.tint}66 60%, ${t.dotTodoDeep})`, border: `2px solid ${lit ? '#fff' : nodeColor}`, boxShadow: lit ? `0 0 22px ${nodeColor}, 0 0 44px ${nodeColor}66, inset 0 1px 0 ${t.dotLitHi}` : `0 0 10px ${nodeColor}88, inset 0 1px 0 ${t.dotSoftHi}`, display: 'flex', alignItems: 'center', justifyContent: 'center', color: '#fff', fontSize: 11, fontWeight: 700, cursor: 'pointer', zIndex: 5, transition: 'all .25s', transform: lit ? 'scale(1.15)' : 'scale(1)', }}> {m.done ? '\u2713' : (m.next ? '\u25c9' : '\u25cb')}
{/* card */}
setHover(true)} onMouseLeave={() => setHover(false)} style={{ position: 'absolute', left: m.x - 112, top: cardY, width: 224, height: 150, padding: '12px 14px', borderRadius: 12, background: cardBg, border: `1px solid ${lit ? m.tint : m.tint + '55'}`, boxShadow: lit ? t.cardShadowLift(m.tint) : t.cardShadowSoft, cursor: 'pointer', zIndex: 4, transition: 'all .25s', transform: lit ? `translateY(${isTop ? -2 : 2}px)` : 'translateY(0)', display: 'flex', flexDirection: 'column', gap: 6, backdropFilter: t.key === 'light' ? 'blur(6px)' : 'none', }}>
{m.tick} {badge}
{de ? m.title.de : m.title.en}
{de ? m.short.de : m.short.en}
{m.when} {de ? 'Details \u2192' : 'Details \u2192'}
) } // ── Stat Card ───────────────────────────────────────────────────────────────── export function StatCard({ item, t, de }: { item: StatItem; t: Theme; de: boolean }) { const [hover, setHover] = useState(false) const bgTop = hover ? item.tint + t.statTintTopH : item.tint + t.statTintTop const bgMid = item.tint + t.statTintMid return (
setHover(true)} onMouseLeave={() => setHover(false)} style={{ position: 'relative', padding: '14px 18px', borderRadius: 12, background: `linear-gradient(180deg, ${bgTop} 0%, ${bgMid} 60%, ${t.cardBase}${t.cardBaseA})`, border: `1px solid ${hover ? item.tint : item.tint + '55'}`, boxShadow: hover ? t.statShadowLift(item.tint) : t.statShadowSoft, transform: hover ? 'translateY(-3px)' : 'translateY(0)', transition: 'all .25s', overflow: 'hidden', backdropFilter: t.key === 'light' ? 'blur(6px)' : 'none', }}>
{de ? item.k.de : item.k.en}
{item.v}
) } // ── Detail modal ────────────────────────────────────────────────────────────── export function DetailModal({ item, onClose, t, de }: { item: Milestone | null; onClose: () => void; t: Theme; de: boolean }) { useEffect(() => { if (!item) return const onKey = (e: KeyboardEvent) => { if (e.key === 'Escape') onClose() } window.addEventListener('keydown', onKey) return () => window.removeEventListener('keydown', onKey) }, [item, onClose]) if (!item) return null const tint = item.tint const badge = item.done ? (de ? 'ABGESCHLOSSEN' : 'COMPLETED') : (item.next ? (de ? 'ALS N\u00c4CHSTES' : 'NEXT UP') : (de ? 'GEPLANT' : 'PLANNED')) const badgeColor = item.done ? t.done : tint return (
e.stopPropagation()} style={{ width: 580, maxWidth: '88%', background: `linear-gradient(180deg, ${tint}22 0%, ${t.modalBgMid} 50%, ${t.modalBgLow} 100%)`, border: `1px solid ${tint}77`, borderRadius: 16, boxShadow: t.modalShadow(tint), padding: '24px 28px', color: t.fg, animation: 'msScaleIn .22s ease-out', }}>
{item.done ? '\u2713' : (item.next ? '\u25c9' : '\u25cb')}
{badge} {item.when}
{de ? item.title.de : item.title.en}
{de ? item.body.de : item.body.en}
{(de ? item.bullets.de : item.bullets.en).map((b, i) => (
{item.done ? '\u2713' : '\u25b8'} {b}
))}
) } // ── Inner slide (fixed 1280x600) ───────────────────────────────────────────── export function MilestonesInner({ t, de, sel, setSel }: { t: Theme; de: boolean sel: Milestone | null setSel: (m: Milestone | null) => void }) { const doneCnt = useMemo(() => MILESTONES.filter(m => m.done).length, []) const total = MILESTONES.length return (
{/* Ambient glow */}
{t.stars ? : } {/* Progress indicator */}
{de ? 'Fortschritt' : 'Progress'}
{doneCnt} / {total}
{/* Tip */}
{de ? 'Tipp:' : 'Tip:'} {de ? 'Klick auf einen Meilenstein' : 'Click any milestone'}
{/* Timeline */}
{/* Stats */}
{STATS.map(s => )}
{/* Footer */}
{de ? 'Stand heute \u00b7 live-Metriken aus der Plattform' : 'As of today \u00b7 live metrics from the platform'}
setSel(null)} t={t} de={de} />
) }