feat(frontend): apply UI/UX design system - purple/green palette, gradient buttons, 3D flip, polished pages

Applied ui-ux-pro-max design system recommendations:
- Tailwind theme: study purple primary + correct green accent
- Inter + Plus Jakarta Sans typography
- Glassmorphic surfaces with soft shadows and mesh background
- Real 3D card flip with spring physics + answer feedback flash
- Gradient stat cards, progress bar, animated done screen with score ring
- Polished Layout, Dashboard, Admin, AdminLesson, CardTable, ImportDialog, PracticeSetup, Practice, PracticeDone
- E2E smoke updated for new accessible names
This commit is contained in:
2026-05-20 21:48:47 +02:00
parent 9300af2820
commit b984e83e2b
16 changed files with 977 additions and 200 deletions

View File

@@ -33,20 +33,51 @@ export function LessonTree({ nodes, depth = 0 }: { nodes: LessonTreeNode[]; dept
return (
<ul className="space-y-1">
{nodes.map((n) => (
<li key={n.id} style={{ paddingLeft: depth * 16 }}>
<div className="group flex items-center gap-2 rounded px-2 py-1 hover:bg-slate-100 dark:hover:bg-slate-800">
<Link to={`/admin/lessons/${n.id}`} className="flex-1">
{n.name} <span className="text-xs text-slate-500">({n.cardCount})</span>
<li key={n.id} style={{ paddingLeft: depth * 20 }}>
<div className="group flex items-center gap-2 rounded-2xl px-3 py-2 transition hover:bg-brand-50/70 dark:hover:bg-slate-800/60">
<span className={`h-2 w-2 rounded-full ${depth === 0 ? 'bg-brand-500' : 'bg-brand-300'}`} />
<Link to={`/admin/lessons/${n.id}`} className="flex-1 truncate font-medium text-slate-800 dark:text-slate-100">
{n.name}
<span className="ml-2 rounded-full bg-brand-100 px-2 py-0.5 text-xs font-semibold text-brand-700 dark:bg-brand-900/30 dark:text-brand-200">
{n.cardCount}
</span>
</Link>
<button className="text-xs opacity-0 group-hover:opacity-100" onClick={() => setAddingTo(n.id)}>+ subles</button>
<button className="text-xs opacity-0 group-hover:opacity-100" onClick={() => rename(n.id, n.name)}>rename</button>
<button className="text-xs text-red-600 opacity-0 group-hover:opacity-100" onClick={() => remove(n.id)}>delete</button>
<div className="flex items-center gap-1 opacity-0 transition group-hover:opacity-100">
<button
className="rounded-lg px-2 py-1 text-xs font-medium text-brand-700 hover:bg-brand-100 dark:text-brand-200 dark:hover:bg-brand-900/40"
onClick={() => setAddingTo(n.id)}
>
+ subles
</button>
<button
className="rounded-lg px-2 py-1 text-xs font-medium text-slate-600 hover:bg-slate-100 dark:text-slate-300 dark:hover:bg-slate-800"
onClick={() => rename(n.id, n.name)}
>
rename
</button>
<button
className="rounded-lg px-2 py-1 text-xs font-medium text-danger-600 hover:bg-danger-50 dark:hover:bg-danger-400/10"
onClick={() => remove(n.id)}
>
delete
</button>
</div>
</div>
{addingTo === n.id && (
<div className="ml-6 flex gap-1 py-1">
<input className="rounded border px-2 py-1 text-sm dark:bg-slate-900" value={name} onChange={(e) => setName(e.target.value)} placeholder="Naam" />
<button className="rounded bg-blue-600 px-2 py-1 text-sm text-white" onClick={() => addChild(n.id)}>Toevoegen</button>
<button className="text-sm" onClick={() => setAddingTo(null)}>Annuleren</button>
<div className="ml-6 mt-1 flex gap-2">
<input
autoFocus
className="input-field flex-1"
value={name}
onChange={(e) => setName(e.target.value)}
onKeyDown={(e) => {
if (e.key === 'Enter') addChild(n.id);
if (e.key === 'Escape') { setAddingTo(null); setName(''); }
}}
placeholder="Naam van subles"
/>
<button className="btn-primary px-3" onClick={() => addChild(n.id)}>Toevoegen</button>
<button className="btn-ghost px-3" onClick={() => { setAddingTo(null); setName(''); }}>Annuleren</button>
</div>
)}
{n.children.length > 0 && <LessonTree nodes={n.children} depth={depth + 1} />}