import { useState, useMemo } from 'react' import confetti from 'canvas-confetti' import './PairCards.css' function triggerConfetti() { confetti({ particleCount: 90, spread: 70, origin: { y: 0.55 }, colors: ['#C4A85A', '#7A5C2E', '#3D7055', '#E8C9A8', '#fff'], scalar: 0.9, gravity: 1.1, }) } function SelectionOverlay({ chip }) { const sels = chip?.selections if (!sels?.length) return null const maskId = `selmask-${chip.id.slice(0, 8)}` const label = chip.label || '' const toPoints = pts => pts.map(p => `${p.x * 100},${p.y * 100}`).join(' ') const firstPts = sels[0].points const xs = firstPts.map(p => p.x * 100) const ys = firstPts.map(p => p.y * 100) const cx = (Math.min(...xs) + Math.max(...xs)) / 2 const labelY = Math.min(Math.max(...ys) + 6, 94) return ( ) } // Sentence format: {{label.w:uuid}} or {{label.o:uuid}} function resolveSentence(sentence, placeholders, onChipClick, activeId) { if (!sentence) return null const parts = sentence.split(/(\{\{[^}]+\.[wo]:[0-9a-f-]{36}\}\})/) return parts.map((part, i) => { const m = part.match(/^\{\{([^.]+)\.(w|o):([0-9a-f-]{36})\}\}$/) if (m) { const label = m[1] const type = m[2] === 'w' ? 'word' : 'object' const id = m[3] const entry = placeholders?.[id] || {} return ( { e.stopPropagation(); onChipClick?.(id, { label, type, ...entry }) }} > {label} ) } return part }) } function shuffle(arr) { const a = [...arr] for (let i = a.length - 1; i > 0; i--) { const j = Math.floor(Math.random() * (i + 1)); [a[i], a[j]] = [a[j], a[i]] } return a } export default function PairWordCard({ card, onComplete }) { const [selectedIds, setSelectedIds] = useState(new Set()) // toggled, not yet confirmed const [confirmed, setConfirmed] = useState(false) // after Bestätigen const [isCorrect, setIsCorrect] = useState(false) const [activeChip, setActiveChip] = useState(null) const lang = card.lang || 'de' const native = lang === 'de' ? 'en' : 'de' const isWord = activeChip && activeChip.type === 'word' const isObject = activeChip && activeChip.type === 'object' && activeChip.selections?.length const q = card.question const stmt = card.positive_statement const neg = card.negative_statement const sentence = q?.[`sentence_${lang}`] || q?.sentence_de const hint = q?.[`sentence_${native}`] || null const pic = card.picture?.url const options = useMemo(() => { const pos = (stmt?.positive_words || []).map(w => ({ ...w, correct: true })) const neg2 = (neg?.negative_words || []).map(w => ({ ...w, correct: false })) return shuffle([...pos, ...neg2]) }, [stmt, neg]) function handleChipClick(id, entry) { setActiveChip(prev => prev?.id === id ? null : { id, ...entry }) } function handleSelect(opt) { if (confirmed) return setSelectedIds(prev => { const next = new Set(prev) next.has(opt.id) ? next.delete(opt.id) : next.add(opt.id) return next }) } function handleConfirm() { if (selectedIds.size === 0 || confirmed) return setActiveChip(null) const correctIds = new Set(options.filter(o => o.correct).map(o => o.id)) const noWrongSelected = [...selectedIds].every(id => correctIds.has(id)) const ok = noWrongSelected setIsCorrect(ok) setConfirmed(true) if (ok) triggerConfetti() setTimeout(() => onComplete(ok ? 'correct' : 'wrong'), 900) } return (
{resolveSentence(sentence, card.placeholders, handleChipClick, activeChip?.id)}
{hint && !confirmed && ({resolveSentence(hint, card.placeholders, null, null)}
)}{isCorrect ? '✓ Richtig!' : `✗ Richtig wären: ${options.filter(o => o.correct).map(o => o[lang] || o.de).join(', ')}` }
)} {!confirmed && (