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 45s
CI / test-python-agent-core (push) Has been cancelled
CI / test-nodejs-website (push) Has been cancelled
CI / test-python-klausur (push) Has started running
Phase 3.2 — MicrophoneInput.tsx: Browser Web Speech API for
speech-to-text recognition (EN+DE), integrated for pronunciation practice.
Phase 4.1 — Story Generator: LLM-powered mini-stories using vocabulary
words, with highlighted vocab in HTML output. Backend endpoint
POST /learning-units/{id}/generate-story + frontend /learn/[unitId]/story.
Phase 4.2 — SyllableBow.tsx: SVG arc component for syllable visualization
under words, clickable for per-syllable TTS.
Phase 4.3 — Gamification system:
- CoinAnimation.tsx: Floating coin rewards with accumulator
- CrownBadge.tsx: Crown/medal display for milestones
- ProgressRing.tsx: Circular progress indicator
- progress_api.py: Backend tracking coins, crowns, streaks per unit
Also adds "Geschichte" exercise type button to UnitCard.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
68 lines
1.8 KiB
TypeScript
68 lines
1.8 KiB
TypeScript
'use client'
|
|
|
|
import React from 'react'
|
|
|
|
interface ProgressRingProps {
|
|
progress: number // 0-100
|
|
size?: number
|
|
strokeWidth?: number
|
|
label: string
|
|
value: string
|
|
color?: string
|
|
isDark?: boolean
|
|
}
|
|
|
|
export function ProgressRing({
|
|
progress,
|
|
size = 80,
|
|
strokeWidth = 6,
|
|
label,
|
|
value,
|
|
color = '#60a5fa',
|
|
isDark = true,
|
|
}: ProgressRingProps) {
|
|
const radius = (size - strokeWidth) / 2
|
|
const circumference = radius * 2 * Math.PI
|
|
const offset = circumference - (Math.min(progress, 100) / 100) * circumference
|
|
|
|
return (
|
|
<div className="flex flex-col items-center gap-1">
|
|
<div className="relative" style={{ width: size, height: size }}>
|
|
<svg width={size} height={size} className="-rotate-90">
|
|
{/* Background circle */}
|
|
<circle
|
|
cx={size / 2}
|
|
cy={size / 2}
|
|
r={radius}
|
|
fill="none"
|
|
stroke={isDark ? 'rgba(255,255,255,0.1)' : 'rgba(0,0,0,0.08)'}
|
|
strokeWidth={strokeWidth}
|
|
/>
|
|
{/* Progress circle */}
|
|
<circle
|
|
cx={size / 2}
|
|
cy={size / 2}
|
|
r={radius}
|
|
fill="none"
|
|
stroke={color}
|
|
strokeWidth={strokeWidth}
|
|
strokeDasharray={circumference}
|
|
strokeDashoffset={offset}
|
|
strokeLinecap="round"
|
|
className="transition-all duration-700 ease-out"
|
|
/>
|
|
</svg>
|
|
{/* Center text */}
|
|
<div className="absolute inset-0 flex items-center justify-center">
|
|
<span className={`text-sm font-bold ${isDark ? 'text-white' : 'text-slate-900'}`}>
|
|
{value}
|
|
</span>
|
|
</div>
|
|
</div>
|
|
<span className={`text-xs ${isDark ? 'text-white/50' : 'text-slate-500'}`}>
|
|
{label}
|
|
</span>
|
|
</div>
|
|
)
|
|
}
|