"use client"; import { useRef, useState } from "react"; import { AnimatePresence, motion } from "framer-motion"; import { Dices } 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 SIZE = 104; const HALF = SIZE / 2; /** Würfelaugen-Positionen im 3×3-Raster (viewBox 40×40). */ const PIPS: Record = { 1: [4], 2: [0, 8], 3: [0, 4, 8], 4: [0, 2, 6, 8], 5: [0, 2, 4, 6, 8], 6: [0, 2, 3, 5, 6, 8], }; /** Platzierung der Seiten im Würfel (gegenüberliegende Seiten = 7). */ const FACE_TRANSFORMS = [ `translateZ(${HALF}px)`, // 1 – vorne `rotateX(90deg) translateZ(${HALF}px)`, // 2 – oben `rotateY(90deg) translateZ(${HALF}px)`, // 3 – rechts `rotateY(-90deg) translateZ(${HALF}px)`, // 4 – links `rotateX(-90deg) translateZ(${HALF}px)`, // 5 – unten `rotateY(180deg) translateZ(${HALF}px)`, // 6 – hinten ]; /** Welche Würfel-Rotation bringt Wert v nach vorne? */ const FACE_ROTATION: Record = { 1: { rx: 0, ry: 0 }, 2: { rx: -90, ry: 0 }, 3: { rx: 0, ry: -90 }, 4: { rx: 0, ry: 90 }, 5: { rx: 90, ry: 0 }, 6: { rx: 0, ry: 180 }, }; const DEFAULT_CUSTOM = ["Ja", "Nein", "Vielleicht", "Frag später", "Gute Idee", "Lass es"]; export default function DiceRoller() { const [mode, setMode] = useLocalStorage<"numbers" | "custom">( "dice-mode", "numbers", ); const [customFaces, setCustomFaces] = useLocalStorage( "dice-custom", DEFAULT_CUSTOM, ); const [rolling, setRolling] = useState(false); const [result, setResult] = useState(null); const diceRef = useRef(null); const stageRef = useRef(null); const rotRef = useRef({ rx: -18, ry: 24 }); // hübscher Startwinkel const { play } = useSound(); const reduced = usePrefersReducedMotion(); const roll = () => { if (rolling) return; const value = 1 + Math.floor(Math.random() * 6); setRolling(true); setResult(null); play("dice"); const { rx, ry } = rotRef.current; const target = FACE_ROTATION[value]; const targetRx = Math.ceil(rx / 360) * 360 + 720 + target.rx; const targetRy = Math.ceil(ry / 360) * 360 + 720 + target.ry; const dur = reduced ? 0.35 : 1.9; gsap.to(diceRef.current, { rotationX: targetRx, rotationY: targetRy, duration: dur, ease: reduced ? "power1.out" : "power3.out", onComplete: () => { rotRef.current = { rx: targetRx, ry: targetRy }; setRolling(false); setResult(value); play("pop"); popConfetti(); }, }); if (!reduced) { gsap.to(stageRef.current, { y: -40, duration: dur / 2, yoyo: true, repeat: 1, ease: "power2.out", }); } }; const resultLabel = result === null ? null : mode === "custom" ? customFaces[result - 1]?.trim() || `Seite ${result}` : String(result); return (
{/* Bühne */}
{resultLabel !== null && ( {resultLabel} )}
{rolling ? "Rollt …" : "Würfeln!"}
{/* Einstellungen */}

Würfel-Seiten

{( [ { id: "numbers", label: "Zahlen 1–6" }, { id: "custom", label: "Eigene Texte" }, ] as const ).map((m) => ( ))}
{mode === "custom" && (
{customFaces.map((face, i) => ( ))}
)}

Klassisch mit Würfelaugen oder mit eigenen Texten – perfekt für „Wer spült heute ab?“.

); }