1. Removed stale 'explanations' export that broke build 2. Explanation banner now spans full width ABOVE the 2/3+1/3 layout so exercise cards and native words start at the same height 3. Both columns use items-start for visual alignment 4. Impressum link added at bottom of learn layout Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
133 lines
4.8 KiB
TypeScript
133 lines
4.8 KiB
TypeScript
'use client'
|
|
|
|
import React from 'react'
|
|
import { useTheme } from '@/lib/ThemeContext'
|
|
import { useNativeLanguage } from '@/lib/useNativeLanguage'
|
|
import { exerciseExplanations } from '@/lib/exerciseExplanations'
|
|
|
|
interface ExerciseLayoutProps {
|
|
/** Main exercise content (2/3 left) */
|
|
children: React.ReactNode
|
|
/** Native language helper content for the right panel */
|
|
nativeHelper?: React.ReactNode
|
|
/** Explanation text for parents (shown above native words) */
|
|
exerciseExplanation?: string
|
|
/** Exercise type key for auto-explanation lookup (e.g. 'match', 'flashcards') */
|
|
exerciseType?: string
|
|
/** Title for the exercise in the header */
|
|
title: string
|
|
/** Progress: current / total */
|
|
progress?: { current: number; total: number }
|
|
/** Back button handler */
|
|
onBack?: () => void
|
|
/** Score display */
|
|
score?: React.ReactNode
|
|
}
|
|
|
|
// Explanations imported from exerciseExplanations.ts (26 languages each)
|
|
|
|
/**
|
|
* Standard exercise layout: 2/3 work area (left) + 1/3 native helper (right).
|
|
* The right panel only appears for non-DE/EN speakers.
|
|
*/
|
|
export function ExerciseLayout({
|
|
children,
|
|
nativeHelper,
|
|
exerciseExplanation,
|
|
exerciseType,
|
|
title,
|
|
progress,
|
|
onBack,
|
|
score,
|
|
}: ExerciseLayoutProps) {
|
|
const { isDark } = useTheme()
|
|
const { nativeLang, isThirdLanguage, t } = useNativeLanguage()
|
|
|
|
const glassCard = isDark
|
|
? 'bg-white/10 backdrop-blur-xl border border-white/10'
|
|
: 'bg-white/80 backdrop-blur-xl border border-black/5'
|
|
|
|
const typeKey = exerciseType || title.toLowerCase()
|
|
const explanation = exerciseExplanation
|
|
|| exerciseExplanations[typeKey]?.[nativeLang]
|
|
|| exerciseExplanations[typeKey]?.['en']
|
|
|| exerciseExplanations[typeKey]?.['de']
|
|
|| ''
|
|
|
|
return (
|
|
<>
|
|
{/* Header */}
|
|
<div className={`${glassCard} border-0 border-b`}>
|
|
<div className="max-w-5xl mx-auto px-6 py-3 flex items-center justify-between">
|
|
{onBack ? (
|
|
<button onClick={onBack} className={`flex items-center gap-2 text-sm ${isDark ? 'text-white/60 hover:text-white' : 'text-slate-500 hover:text-slate-900'}`}>
|
|
<svg className="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M15 19l-7-7 7-7" /></svg>
|
|
{t('back')}
|
|
</button>
|
|
) : <span />}
|
|
<h1 className={`text-lg font-bold ${isDark ? 'text-white' : 'text-slate-900'}`}>{title}</h1>
|
|
{score || <span />}
|
|
</div>
|
|
</div>
|
|
|
|
{/* Progress bar */}
|
|
{progress && (
|
|
<div className="max-w-5xl mx-auto w-full px-6 pt-3">
|
|
<div className="flex items-center gap-3">
|
|
<div className={`flex-1 h-2 rounded-full ${isDark ? 'bg-white/10' : 'bg-slate-200'}`}>
|
|
<div className="h-full rounded-full bg-gradient-to-r from-indigo-500 to-violet-500 transition-all"
|
|
style={{ width: `${(progress.current / Math.max(progress.total, 1)) * 100}%` }} />
|
|
</div>
|
|
<span className={`text-xs font-medium tabular-nums ${isDark ? 'text-white/50' : 'text-slate-400'}`}>
|
|
{progress.current}/{progress.total}
|
|
</span>
|
|
</div>
|
|
</div>
|
|
)}
|
|
|
|
{/* Main content */}
|
|
<div className="max-w-5xl mx-auto w-full px-6 py-6">
|
|
{/* Explanation banner — full width above exercise, only for non-DE/EN */}
|
|
{isThirdLanguage && explanation && (
|
|
<div className={`rounded-2xl p-4 mb-5 ${isDark ? 'bg-cyan-500/5 border border-cyan-400/20' : 'bg-cyan-50 border border-cyan-200'}`}>
|
|
<div className="flex items-start gap-3">
|
|
<span className="text-lg">💡</span>
|
|
<div>
|
|
<h3 className={`text-xs font-semibold mb-1 ${isDark ? 'text-cyan-300' : 'text-cyan-700'}`}>
|
|
{nativeLang.toUpperCase()} · {t('english')} · {t('german')}
|
|
</h3>
|
|
<p className={`text-xs leading-relaxed ${isDark ? 'text-white/60' : 'text-slate-600'}`}>
|
|
{explanation}
|
|
</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
)}
|
|
|
|
{/* 2/3 left + 1/3 right — both start at same height */}
|
|
<div className="flex gap-0 items-start">
|
|
{/* Left: Exercise area (2/3 or full) */}
|
|
<div className={isThirdLanguage ? 'w-2/3 pr-6' : 'w-full'}>
|
|
{children}
|
|
</div>
|
|
|
|
{/* Divider line */}
|
|
{isThirdLanguage && (
|
|
<div className={`w-px self-stretch ${isDark ? 'bg-white/10' : 'bg-slate-200'}`} />
|
|
)}
|
|
|
|
{/* Right: Native language words (1/3) */}
|
|
{isThirdLanguage && (
|
|
<div className="w-1/3 pl-6">
|
|
<div className="sticky top-20">
|
|
{nativeHelper}
|
|
</div>
|
|
</div>
|
|
)}
|
|
</div>
|
|
</div>
|
|
</>
|
|
)
|
|
}
|
|
|