"use client"; import { useRef, useState } from "react"; import { AnimatePresence, motion, useMotionValue, useTransform, type PanInfo, type Variants } from "framer-motion"; import ObjectFace from "./ObjectFace"; import { useOpenWindow } from "../fenster/WindowProvider"; import { labelFor, windowFor } from "./windowFor"; import type { TischLabels, TischObject } from "./types"; interface DeckProps { objects: TischObject[]; labels: TischLabels; } const VISIBLE = 4; // Ab dieser Strecke oder Geschwindigkeit fliegt die Karte weg (Emil: ein kurzer Wisch reicht) const DISTANCE = 110; const VELOCITY = 500; const EASE_OUT: [number, number, number, number] = [0.23, 1, 0.32, 1]; const pad = (n: number) => String(n).padStart(2, "0"); type Exit = { kind: "fly"; dir: 1 | -1 } | { kind: "fade" }; // Bekommt den aktuellen Exit-Modus über `custom` von AnimatePresence const exitVariants: Variants = { leave: (e: Exit) => e.kind === "fly" ? { x: e.dir * 460, rotate: e.dir * 18, opacity: 0, transition: { duration: 0.3, ease: EASE_OUT } } : { scale: 0.85, opacity: 0, transition: { duration: 0.2, ease: EASE_OUT } }, }; /** Handy: Kartenstapel. Oberste Karte zur Seite wischen, antippen zum Umdrehen. */ export default function Deck({ objects, labels }: DeckProps) { const [order, setOrder] = useState(() => objects.map((_, i) => i)); // Wie die Karte geht, die den sichtbaren Stapel verlässt: oben weggeworfen oder hinten ausgeblendet const [exit, setExit] = useState({ kind: "fly", dir: 1 }); const next = (d: 1 | -1 = 1) => { setExit({ kind: "fly", dir: d }); setOrder((o) => [...o.slice(1), o[0]]); }; const prev = () => { setExit({ kind: "fade" }); setOrder((o) => [o[o.length - 1], ...o.slice(0, -1)]); }; const visible = order.slice(0, VISIBLE); return (

{labels.hintDeck}

{visible .map((idx, depth) => ( )) .reverse()}
← {pad(order[0] + 1)} / {pad(objects.length)} next(1)}> →
); } function RoundButton({ label, onClick, children }: { label: string; onClick: () => void; children: React.ReactNode }) { return ( ); } interface DeckCardProps { obj: TischObject; labels: TischLabels; depth: number; enteringFromLeft: boolean; onDismiss: (dir: 1 | -1) => void; } function DeckCard({ obj, labels, depth, enteringFromLeft, onDismiss }: DeckCardProps) { const dragged = useRef(false); const cardRef = useRef(null); const openWindow = useOpenWindow(); const openIt = () => openWindow?.(windowFor(obj), cardRef.current); const x = useMotionValue(0); const dragRotate = useTransform(x, [-220, 0, 220], [-14, 0, 14]); const isTop = depth === 0; const restRotate = depth === 0 ? 0 : depth % 2 ? 3 : -3; const handleDragEnd = (_: unknown, info: PanInfo) => { if (Math.abs(info.offset.x) > DISTANCE || Math.abs(info.velocity.x) > VELOCITY) { onDismiss(info.offset.x > 0 ? 1 : -1); } }; return ( { dragged.current = false; }} onDragStart={() => { dragged.current = true; }} onDragEnd={handleDragEnd} onTap={isTop ? () => !dragged.current && openIt() : undefined} onKeyDown={(e) => { if (isTop && e.key === " ") { e.preventDefault(); openIt(); } }} tabIndex={isTop ? 0 : -1} role="button" aria-haspopup="dialog" aria-label={labelFor(obj, labels)} aria-hidden={!isTop} > ); }