"use client"; import { useEffect, useRef, useState } from "react"; import { AnimatePresence, motion } from "framer-motion"; import { Pencil, Plus, Sparkles, X } from "lucide-react"; import { gsap } from "@/lib/gsap"; import { popConfetti } from "@/lib/confetti"; import { useSound } from "@/components/providers/sound-provider"; import { useLocalStorage } from "@/hooks/use-local-storage"; import { usePrefersReducedMotion } from "@/hooks/use-prefers-reduced-motion"; const DEFAULT_ANSWERS = [ "Ja, definitiv.", "Ohne Zweifel.", "Sehr wahrscheinlich.", "Sieht gut aus.", "Frag später nochmal.", "Kann ich jetzt nicht sagen.", "Konzentrier dich und frag erneut.", "Rechne nicht damit.", "Meine Antwort: Nein.", "Eher nicht.", ]; const MIN_ANSWERS = 2; const MAX_ANSWERS = 20; export default function Magic8Ball() { const [answers, setAnswers] = useLocalStorage( "ball-answers", DEFAULT_ANSWERS, ); const [question, setQuestion] = useState(""); const [answer, setAnswer] = useState(null); const [shaking, setShaking] = useState(false); const [editing, setEditing] = useState(false); const ballRef = useRef(null); const answerInputRefs = useRef>([]); const justAddedRef = useRef(false); const { play } = useSound(); const reduced = usePrefersReducedMotion(); const validAnswers = answers.map((a) => a.trim()).filter(Boolean); const shake = () => { if (shaking || validAnswers.length === 0) return; const pick = validAnswers[Math.floor(Math.random() * validAnswers.length)]; setShaking(true); setAnswer(null); play("whoosh"); const reveal = () => { setAnswer(pick); setShaking(false); play("pop"); popConfetti(); }; if (reduced) { gsap.delayedCall(0.25, reveal); return; } const tl = gsap.timeline({ onComplete: reveal }); tl.to(ballRef.current, { x: -18, rotation: -9, duration: 0.08, ease: "power1.inOut", }) .to(ballRef.current, { x: 16, rotation: 8, duration: 0.08, ease: "power1.inOut", }) .repeat(4) .to(ballRef.current, { x: 0, rotation: 0, duration: 0.22, ease: "power2.out", }); }; const updateAnswer = (i: number, value: string) => setAnswers((list) => list.map((a, idx) => (idx === i ? value : a))); const addAnswer = () => { if (answers.length >= MAX_ANSWERS) return; setAnswers((list) => [...list, ""]); justAddedRef.current = true; }; useEffect(() => { if (!justAddedRef.current) return; justAddedRef.current = false; const last = answerInputRefs.current[answers.length - 1]; last?.scrollIntoView({ block: "nearest", behavior: "smooth" }); last?.focus(); }, [answers.length]); const removeAnswer = (i: number) => { if (answers.length <= MIN_ANSWERS) return; setAnswers((list) => list.filter((_, idx) => idx !== i)); }; return (
{/* Bühne */}
setQuestion(e.target.value)} maxLength={80} placeholder="Deine Frage (optional) …" aria-label="Deine Frage an den 8-Ball" className="w-full max-w-sm rounded-full border border-line bg-surface px-5 py-3 text-center text-sm font-medium shadow-soft outline-none transition focus:border-primary/60 focus:ring-2 focus:ring-primary/20" />
{shaking ? "Schüttelt …" : "8-Ball schütteln"}

{question.trim() ? `„${question.trim()}“` : "Stell eine Ja/Nein-Frage – oder lass dich einfach überraschen."}

{/* Antworten-Editor */}

Antworten ({validAnswers.length})

{editing && (
{answers.map((a, i) => (
{ answerInputRefs.current[i] = el; }} value={a} onChange={(e) => updateAnswer(i, e.target.value)} maxLength={60} placeholder={`Antwort ${i + 1}`} aria-label={`Antwort ${i + 1}`} className="w-full rounded-xl border border-line bg-surface px-3.5 py-2 text-sm font-medium outline-none transition focus:border-primary/60 focus:ring-2 focus:ring-primary/20" />
))}
)}

Der Ball sagt, was du ihm vorgibst – von klassisch bis fies.{" "} {MIN_ANSWERS}–{MAX_ANSWERS} Antworten, automatisch gespeichert.

); }