Files
breakpilot-lehrer/studio-v2/components/learn/FlashCard.tsx
Benjamin Admin f3b9617fc3
Some checks failed
CI / go-lint (push) Has been skipped
CI / python-lint (push) Has been skipped
CI / nodejs-lint (push) Has been skipped
CI / test-go-school (push) Successful in 37s
CI / test-go-edu-search (push) Successful in 34s
CI / test-python-klausur (push) Failing after 2m28s
CI / test-python-agent-core (push) Successful in 20s
CI / test-nodejs-website (push) Successful in 25s
Add 6 Anton-inspired features for vocabulary learning
Feature 1 — StarRating: 1-3 stars per exercise (100%=3, 70%=2, <70%=1)
Feature 2 — Progress bar in UnitCard with Leitner box distribution
Feature 3 — Listening exercise: hear word via TTS, choose correct translation
Feature 4 — Matching game: tap-to-match EN↔DE pairs (6 per round)
Feature 5 — Pronunciation: word with syllable bows + mic → STT comparison
Feature 6 — Syllable bows in FlashCards (SyllableBow under word + IPA)

UnitCard now shows 6 exercise types: Karten, Quiz, Tippen, Hoeren,
Zuordnen, Sprechen. Progress bar and star count displayed.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-25 16:10:58 +02:00

156 lines
5.3 KiB
TypeScript

'use client'
import React, { useState, useCallback } from 'react'
import { SyllableBow, simpleSyllableSplit } from './SyllableBow'
interface FlashCardProps {
front: string
back: string
cardNumber: number
totalCards: number
leitnerBox: number
onCorrect: () => void
onIncorrect: () => void
isDark: boolean
syllablesFront?: string[]
syllablesBack?: string[]
ipaFront?: string
ipaBack?: string
}
const boxLabels = ['Neu', 'Gelernt', 'Gefestigt']
const boxColors = ['text-yellow-400', 'text-blue-400', 'text-green-400']
export function FlashCard({
front,
back,
cardNumber,
totalCards,
leitnerBox,
onCorrect,
onIncorrect,
isDark,
syllablesFront,
syllablesBack,
ipaFront,
ipaBack,
}: FlashCardProps) {
const [isFlipped, setIsFlipped] = useState(false)
const handleFlip = useCallback(() => {
setIsFlipped((f) => !f)
}, [])
const handleCorrect = useCallback(() => {
setIsFlipped(false)
onCorrect()
}, [onCorrect])
const handleIncorrect = useCallback(() => {
setIsFlipped(false)
onIncorrect()
}, [onIncorrect])
return (
<div className="flex flex-col items-center gap-6 w-full max-w-lg mx-auto">
{/* Card */}
<div
onClick={handleFlip}
className="w-full cursor-pointer select-none"
style={{ perspective: '1000px' }}
>
<div
className="relative w-full transition-transform duration-500"
style={{
transformStyle: 'preserve-3d',
transform: isFlipped ? 'rotateY(180deg)' : 'rotateY(0deg)',
}}
>
{/* Front */}
<div
className={`w-full min-h-[280px] rounded-3xl p-8 flex flex-col items-center justify-center ${
isDark
? 'bg-white/10 backdrop-blur-xl border border-white/20'
: 'bg-white shadow-xl border border-slate-200'
}`}
style={{ backfaceVisibility: 'hidden' }}
>
<span className={`text-xs font-medium mb-4 ${isDark ? 'text-white/40' : 'text-slate-400'}`}>
ENGLISCH
</span>
{syllablesFront && syllablesFront.length > 0 ? (
<SyllableBow word={front} syllables={syllablesFront} isDark={isDark} size="md" />
) : (
<span className={`text-3xl font-bold text-center ${isDark ? 'text-white' : 'text-slate-900'}`}>
{front}
</span>
)}
{ipaFront && <span className={`text-sm mt-2 ${isDark ? 'text-white/30' : 'text-slate-400'}`}>{ipaFront}</span>}
<span className={`text-sm mt-4 ${isDark ? 'text-white/30' : 'text-slate-400'}`}>
Klick zum Umdrehen
</span>
</div>
{/* Back */}
<div
className={`w-full min-h-[280px] rounded-3xl p-8 flex flex-col items-center justify-center absolute inset-0 ${
isDark
? 'bg-gradient-to-br from-blue-500/20 to-cyan-500/20 backdrop-blur-xl border border-blue-400/30'
: 'bg-gradient-to-br from-blue-50 to-cyan-50 shadow-xl border border-blue-200'
}`}
style={{ backfaceVisibility: 'hidden', transform: 'rotateY(180deg)' }}
>
<span className={`text-xs font-medium mb-4 ${isDark ? 'text-blue-300/60' : 'text-blue-500'}`}>
DEUTSCH
</span>
{syllablesBack && syllablesBack.length > 0 ? (
<SyllableBow word={back} syllables={syllablesBack} isDark={isDark} size="md" />
) : (
<span className={`text-3xl font-bold text-center ${isDark ? 'text-white' : 'text-slate-900'}`}>
{back}
</span>
)}
{ipaBack && <span className={`text-sm mt-2 ${isDark ? 'text-blue-300/40' : 'text-blue-400'}`}>{ipaBack}</span>}
</div>
</div>
</div>
{/* Status Bar */}
<div className={`flex items-center justify-between w-full px-2 ${isDark ? 'text-white/50' : 'text-slate-500'}`}>
<span className="text-sm">
Karte {cardNumber} von {totalCards}
</span>
<span className={`text-sm font-medium ${boxColors[leitnerBox] || boxColors[0]}`}>
Box: {boxLabels[leitnerBox] || boxLabels[0]}
</span>
</div>
{/* Progress */}
<div className="w-full h-1.5 rounded-full bg-white/10 overflow-hidden">
<div
className="h-full rounded-full bg-gradient-to-r from-blue-500 to-cyan-500 transition-all"
style={{ width: `${(cardNumber / totalCards) * 100}%` }}
/>
</div>
{/* Answer Buttons */}
{isFlipped && (
<div className="flex gap-4 w-full">
<button
onClick={handleIncorrect}
className="flex-1 py-4 rounded-2xl font-semibold text-lg transition-all bg-gradient-to-r from-red-500 to-rose-500 text-white hover:shadow-lg hover:shadow-red-500/25 hover:scale-[1.02]"
>
Falsch
</button>
<button
onClick={handleCorrect}
className="flex-1 py-4 rounded-2xl font-semibold text-lg transition-all bg-gradient-to-r from-green-500 to-emerald-500 text-white hover:shadow-lg hover:shadow-green-500/25 hover:scale-[1.02]"
>
Richtig
</button>
</div>
)}
</div>
)
}