- +EP-Float am Button + hochzählendes EP-Badge (EpFloat, useCountUp) - Level-/Streak-/Tagesziel-Overlay (MilestoneOverlay), getriggert aus der saveProgress-Response - Combo-Zähler + variables Lob, ermutigendes Fehler-Feedback statt stillem Verschwinden - Session-Summary mit Story-Zeilen statt End-Sackgasse - Profil führt mit %-bis-Level + Capability-Satz; Kategorie-Stufen, Wochenvergleich, Sound-Toggle - Level-Kurve gespiegelt (utils/leveling.js); Level deploy-unabhängig aus EP abgeleitet Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
36 lines
1.7 KiB
JavaScript
36 lines
1.7 KiB
JavaScript
import { useEffect } from 'react'
|
|
import confetti from 'canvas-confetti'
|
|
import './Moments.css'
|
|
|
|
const COLORS = ['#C4A85A', '#7A5C2E', '#3D7055', '#E8C9A8', '#fff']
|
|
|
|
function celebrate() {
|
|
const reduce = window.matchMedia?.('(prefers-reduced-motion: reduce)')?.matches
|
|
if (reduce) return
|
|
confetti({ particleCount: 140, spread: 90, origin: { y: 0.5 }, colors: COLORS, scalar: 1 })
|
|
setTimeout(() => confetti({ particleCount: 70, spread: 110, origin: { y: 0.45 }, colors: COLORS, scalar: 0.8 }), 220)
|
|
}
|
|
|
|
// Texte je Milestone-Art. value = Level-Nummer / Streak-Tage / Tagesziel-EP.
|
|
function content({ kind, value }) {
|
|
if (kind === 'level') return { cls: '', icon: '🏆', title: `Level ${value} erreicht!`, sub: 'Du wächst spürbar — weiter so.' }
|
|
if (kind === 'streak') return { cls: 'streak', icon: '🔥', title: `${value} Tage am Stück!`, sub: 'Dranbleiben zahlt sich aus.' }
|
|
if (kind === 'goal') return { cls: 'goal', icon: '🎯', title: 'Tagesziel erreicht!', sub: 'Stark — heute hast du dein Pensum geschafft.' }
|
|
return { cls: '', icon: '🎉', title: 'Geschafft!', sub: '' }
|
|
}
|
|
|
|
export default function MilestoneOverlay({ milestone, onClose }) {
|
|
useEffect(() => { celebrate() }, [milestone])
|
|
const { cls, icon, title, sub } = content(milestone)
|
|
return (
|
|
<div className="milestone-overlay" onClick={onClose} role="dialog" aria-label={title}>
|
|
<div className="milestone-card" onClick={e => e.stopPropagation()}>
|
|
<div className={`milestone-badge ${cls}`} aria-hidden="true">{icon}</div>
|
|
<h2 className="milestone-title">{title}</h2>
|
|
{sub && <p className="milestone-sub">{sub}</p>}
|
|
<button className="milestone-btn" onClick={onClose} autoFocus>Weiter</button>
|
|
</div>
|
|
</div>
|
|
)
|
|
}
|