fix: Restore all files lost during destructive rebase
A previous `git pull --rebase origin main` dropped 177 local commits,
losing 3400+ files across admin-v2, backend, studio-v2, website,
klausur-service, and many other services. The partial restore attempt
(660295e2) only recovered some files.
This commit restores all missing files from pre-rebase ref 98933f5e
while preserving post-rebase additions (night-scheduler, night-mode UI,
NightModeWidget dashboard integration).
Restored features include:
- AI Module Sidebar (FAB), OCR Labeling, OCR Compare
- GPU Dashboard, RAG Pipeline, Magic Help
- Klausur-Korrektur (8 files), Abitur-Archiv (5+ files)
- Companion, Zeugnisse-Crawler, Screen Flow
- Full backend, studio-v2, website, klausur-service
- All compliance SDKs, agent-core, voice-service
- CI/CD configs, documentation, scripts
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
739
studio-v2/app/dashboard-experimental/page.tsx
Normal file
739
studio-v2/app/dashboard-experimental/page.tsx
Normal file
@@ -0,0 +1,739 @@
|
||||
'use client'
|
||||
|
||||
import { useState, useEffect, useCallback } from 'react'
|
||||
import { useRouter } from 'next/navigation'
|
||||
|
||||
// Spatial UI System
|
||||
import { PerformanceProvider, usePerformance } from '@/lib/spatial-ui/PerformanceContext'
|
||||
import { FocusProvider } from '@/lib/spatial-ui/FocusContext'
|
||||
import { FloatingMessage } from '@/components/spatial-ui/FloatingMessage'
|
||||
|
||||
/**
|
||||
* Apple Weather Style Dashboard - Refined Version
|
||||
*
|
||||
* Design principles:
|
||||
* - Photo/gradient background that sets the mood
|
||||
* - Ultra-translucent cards (~8% opacity)
|
||||
* - Cards blend INTO the background
|
||||
* - White text, monochrome palette
|
||||
* - Subtle blur, minimal shadows
|
||||
* - Useful info: time, weather, compass
|
||||
*/
|
||||
|
||||
// =============================================================================
|
||||
// GLASS CARD - Ultra Transparent
|
||||
// =============================================================================
|
||||
|
||||
interface GlassCardProps {
|
||||
children: React.ReactNode
|
||||
className?: string
|
||||
onClick?: () => void
|
||||
size?: 'sm' | 'md' | 'lg'
|
||||
delay?: number
|
||||
}
|
||||
|
||||
function GlassCard({ children, className = '', onClick, size = 'md', delay = 0 }: GlassCardProps) {
|
||||
const { settings } = usePerformance()
|
||||
const [isVisible, setIsVisible] = useState(false)
|
||||
const [isHovered, setIsHovered] = useState(false)
|
||||
|
||||
useEffect(() => {
|
||||
const timer = setTimeout(() => setIsVisible(true), delay)
|
||||
return () => clearTimeout(timer)
|
||||
}, [delay])
|
||||
|
||||
const sizeClasses = {
|
||||
sm: 'p-4',
|
||||
md: 'p-5',
|
||||
lg: 'p-6',
|
||||
}
|
||||
|
||||
const blur = settings.enableBlur ? 24 * settings.blurIntensity : 0
|
||||
|
||||
return (
|
||||
<div
|
||||
className={`
|
||||
rounded-3xl
|
||||
${sizeClasses[size]}
|
||||
${onClick ? 'cursor-pointer' : ''}
|
||||
${className}
|
||||
`}
|
||||
style={{
|
||||
background: isHovered
|
||||
? 'rgba(255, 255, 255, 0.12)'
|
||||
: 'rgba(255, 255, 255, 0.08)',
|
||||
backdropFilter: blur > 0 ? `blur(${blur}px) saturate(180%)` : 'none',
|
||||
WebkitBackdropFilter: blur > 0 ? `blur(${blur}px) saturate(180%)` : 'none',
|
||||
border: '1px solid rgba(255, 255, 255, 0.1)',
|
||||
boxShadow: '0 4px 24px rgba(0, 0, 0, 0.15), inset 0 1px 0 rgba(255, 255, 255, 0.05)',
|
||||
opacity: isVisible ? 1 : 0,
|
||||
transform: isVisible
|
||||
? `translateY(0) scale(${isHovered ? 1.01 : 1})`
|
||||
: 'translateY(20px)',
|
||||
transition: 'all 0.4s cubic-bezier(0.34, 1.56, 0.64, 1)',
|
||||
}}
|
||||
onMouseEnter={() => setIsHovered(true)}
|
||||
onMouseLeave={() => setIsHovered(false)}
|
||||
onClick={onClick}
|
||||
>
|
||||
{children}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// ANALOG CLOCK - Apple Style
|
||||
// =============================================================================
|
||||
|
||||
function AnalogClock() {
|
||||
const [time, setTime] = useState(new Date())
|
||||
|
||||
useEffect(() => {
|
||||
const timer = setInterval(() => setTime(new Date()), 1000)
|
||||
return () => clearInterval(timer)
|
||||
}, [])
|
||||
|
||||
const hours = time.getHours() % 12
|
||||
const minutes = time.getMinutes()
|
||||
const seconds = time.getSeconds()
|
||||
|
||||
const hourDeg = (hours * 30) + (minutes * 0.5)
|
||||
const minuteDeg = minutes * 6
|
||||
const secondDeg = seconds * 6
|
||||
|
||||
return (
|
||||
<div className="relative w-32 h-32">
|
||||
{/* Clock face */}
|
||||
<div
|
||||
className="absolute inset-0 rounded-full"
|
||||
style={{
|
||||
background: 'rgba(255, 255, 255, 0.05)',
|
||||
border: '2px solid rgba(255, 255, 255, 0.15)',
|
||||
}}
|
||||
>
|
||||
{/* Hour markers */}
|
||||
{[...Array(12)].map((_, i) => (
|
||||
<div
|
||||
key={i}
|
||||
className="absolute w-1 h-3 bg-white/40 rounded-full"
|
||||
style={{
|
||||
left: '50%',
|
||||
top: '8px',
|
||||
transform: `translateX(-50%) rotate(${i * 30}deg)`,
|
||||
transformOrigin: '50% 56px',
|
||||
}}
|
||||
/>
|
||||
))}
|
||||
|
||||
{/* Hour hand */}
|
||||
<div
|
||||
className="absolute w-1.5 h-10 bg-white rounded-full"
|
||||
style={{
|
||||
left: '50%',
|
||||
bottom: '50%',
|
||||
transform: `translateX(-50%) rotate(${hourDeg}deg)`,
|
||||
transformOrigin: 'bottom center',
|
||||
}}
|
||||
/>
|
||||
|
||||
{/* Minute hand */}
|
||||
<div
|
||||
className="absolute w-1 h-14 bg-white/80 rounded-full"
|
||||
style={{
|
||||
left: '50%',
|
||||
bottom: '50%',
|
||||
transform: `translateX(-50%) rotate(${minuteDeg}deg)`,
|
||||
transformOrigin: 'bottom center',
|
||||
}}
|
||||
/>
|
||||
|
||||
{/* Second hand */}
|
||||
<div
|
||||
className="absolute w-0.5 h-14 bg-orange-400 rounded-full"
|
||||
style={{
|
||||
left: '50%',
|
||||
bottom: '50%',
|
||||
transform: `translateX(-50%) rotate(${secondDeg}deg)`,
|
||||
transformOrigin: 'bottom center',
|
||||
transition: 'transform 0.1s ease-out',
|
||||
}}
|
||||
/>
|
||||
|
||||
{/* Center dot */}
|
||||
<div className="absolute w-3 h-3 bg-white rounded-full left-1/2 top-1/2 -translate-x-1/2 -translate-y-1/2" />
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// COMPASS - Apple Weather Style
|
||||
// =============================================================================
|
||||
|
||||
function Compass({ direction = 225 }: { direction?: number }) {
|
||||
return (
|
||||
<div className="relative w-24 h-24">
|
||||
{/* Compass face */}
|
||||
<div
|
||||
className="absolute inset-0 rounded-full"
|
||||
style={{
|
||||
background: 'rgba(255, 255, 255, 0.05)',
|
||||
border: '2px solid rgba(255, 255, 255, 0.15)',
|
||||
}}
|
||||
>
|
||||
{/* Cardinal directions */}
|
||||
<span className="absolute top-2 left-1/2 -translate-x-1/2 text-xs font-bold text-red-400">N</span>
|
||||
<span className="absolute bottom-2 left-1/2 -translate-x-1/2 text-xs font-medium text-white/50">S</span>
|
||||
<span className="absolute left-2 top-1/2 -translate-y-1/2 text-xs font-medium text-white/50">W</span>
|
||||
<span className="absolute right-2 top-1/2 -translate-y-1/2 text-xs font-medium text-white/50">O</span>
|
||||
|
||||
{/* Needle */}
|
||||
<div
|
||||
className="absolute inset-4"
|
||||
style={{
|
||||
transform: `rotate(${direction}deg)`,
|
||||
transition: 'transform 0.5s ease-out',
|
||||
}}
|
||||
>
|
||||
{/* North (red) */}
|
||||
<div
|
||||
className="absolute w-1.5 h-8 bg-gradient-to-t from-red-500 to-red-400 rounded-full"
|
||||
style={{
|
||||
left: '50%',
|
||||
bottom: '50%',
|
||||
transform: 'translateX(-50%)',
|
||||
transformOrigin: 'bottom center',
|
||||
}}
|
||||
/>
|
||||
{/* South (white) */}
|
||||
<div
|
||||
className="absolute w-1.5 h-8 bg-gradient-to-b from-white/80 to-white/40 rounded-full"
|
||||
style={{
|
||||
left: '50%',
|
||||
top: '50%',
|
||||
transform: 'translateX(-50%)',
|
||||
transformOrigin: 'top center',
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* Center */}
|
||||
<div className="absolute w-3 h-3 bg-white rounded-full left-1/2 top-1/2 -translate-x-1/2 -translate-y-1/2" />
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// BAR CHART - Apple Weather Hourly Style
|
||||
// =============================================================================
|
||||
|
||||
interface BarChartProps {
|
||||
data: { label: string; value: number; highlight?: boolean }[]
|
||||
maxValue?: number
|
||||
}
|
||||
|
||||
function BarChart({ data, maxValue }: BarChartProps) {
|
||||
const max = maxValue || Math.max(...data.map((d) => d.value))
|
||||
|
||||
return (
|
||||
<div className="flex items-end justify-between gap-2 h-32">
|
||||
{data.map((item, index) => {
|
||||
const height = (item.value / max) * 100
|
||||
return (
|
||||
<div key={index} className="flex flex-col items-center gap-2 flex-1">
|
||||
<span className="text-xs text-white/60 font-medium">{item.value}</span>
|
||||
<div
|
||||
className="w-full rounded-lg transition-all duration-500"
|
||||
style={{
|
||||
height: `${height}%`,
|
||||
minHeight: 8,
|
||||
background: item.highlight
|
||||
? 'linear-gradient(to top, rgba(96, 165, 250, 0.6), rgba(167, 139, 250, 0.6))'
|
||||
: 'rgba(255, 255, 255, 0.2)',
|
||||
boxShadow: item.highlight ? '0 0 20px rgba(139, 92, 246, 0.3)' : 'none',
|
||||
}}
|
||||
/>
|
||||
<span className="text-xs text-white/40">{item.label}</span>
|
||||
</div>
|
||||
)
|
||||
})}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// TEMPERATURE DISPLAY
|
||||
// =============================================================================
|
||||
|
||||
function TemperatureDisplay({ temp, condition }: { temp: number; condition: string }) {
|
||||
const conditionIcons: Record<string, string> = {
|
||||
sunny: '☀️',
|
||||
cloudy: '☁️',
|
||||
rainy: '🌧️',
|
||||
snowy: '🌨️',
|
||||
partly_cloudy: '⛅',
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="text-center">
|
||||
<div className="text-6xl mb-2">{conditionIcons[condition] || '☀️'}</div>
|
||||
<div className="flex items-start justify-center">
|
||||
<span className="text-6xl font-extralight text-white">{temp}</span>
|
||||
<span className="text-2xl text-white/60 mt-2">°C</span>
|
||||
</div>
|
||||
<p className="text-white/50 text-sm mt-1 capitalize">
|
||||
{condition.replace('_', ' ')}
|
||||
</p>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// PROGRESS RING
|
||||
// =============================================================================
|
||||
|
||||
interface ProgressRingProps {
|
||||
progress: number
|
||||
size?: number
|
||||
strokeWidth?: number
|
||||
label: string
|
||||
value: string
|
||||
color?: string
|
||||
}
|
||||
|
||||
function ProgressRing({ progress, size = 80, strokeWidth = 6, label, value, color = '#a78bfa' }: ProgressRingProps) {
|
||||
const radius = (size - strokeWidth) / 2
|
||||
const circumference = radius * 2 * Math.PI
|
||||
const offset = circumference - (progress / 100) * circumference
|
||||
|
||||
return (
|
||||
<div className="flex flex-col items-center">
|
||||
<div className="relative" style={{ width: size, height: size }}>
|
||||
<svg width={size} height={size} className="transform -rotate-90">
|
||||
<circle
|
||||
cx={size / 2}
|
||||
cy={size / 2}
|
||||
r={radius}
|
||||
fill="none"
|
||||
stroke="rgba(255, 255, 255, 0.1)"
|
||||
strokeWidth={strokeWidth}
|
||||
/>
|
||||
<circle
|
||||
cx={size / 2}
|
||||
cy={size / 2}
|
||||
r={radius}
|
||||
fill="none"
|
||||
stroke={color}
|
||||
strokeWidth={strokeWidth}
|
||||
strokeLinecap="round"
|
||||
strokeDasharray={circumference}
|
||||
strokeDashoffset={offset}
|
||||
style={{ transition: 'stroke-dashoffset 1s ease-out' }}
|
||||
/>
|
||||
</svg>
|
||||
<div className="absolute inset-0 flex items-center justify-center">
|
||||
<span className="text-lg font-light text-white">{value}</span>
|
||||
</div>
|
||||
</div>
|
||||
<p className="text-white/40 text-xs mt-2 font-medium uppercase tracking-wide">{label}</p>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// STAT DISPLAY
|
||||
// =============================================================================
|
||||
|
||||
function StatDisplay({ value, unit, label, icon }: { value: string; unit?: string; label: string; icon?: string }) {
|
||||
return (
|
||||
<div className="text-center">
|
||||
{icon && <div className="text-2xl mb-2 opacity-80">{icon}</div>}
|
||||
<div className="flex items-baseline justify-center gap-1">
|
||||
<span className="text-4xl font-light text-white">{value}</span>
|
||||
{unit && <span className="text-lg text-white/50 font-light">{unit}</span>}
|
||||
</div>
|
||||
<p className="text-white/40 text-xs mt-1 font-medium uppercase tracking-wide">{label}</p>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// LIST ITEM
|
||||
// =============================================================================
|
||||
|
||||
function ListItem({ icon, title, subtitle, value, delay = 0 }: {
|
||||
icon: string; title: string; subtitle?: string; value?: string; delay?: number
|
||||
}) {
|
||||
const [isVisible, setIsVisible] = useState(false)
|
||||
const [isHovered, setIsHovered] = useState(false)
|
||||
|
||||
useEffect(() => {
|
||||
const timer = setTimeout(() => setIsVisible(true), delay)
|
||||
return () => clearTimeout(timer)
|
||||
}, [delay])
|
||||
|
||||
return (
|
||||
<div
|
||||
className="flex items-center gap-4 p-3 rounded-2xl cursor-pointer transition-all"
|
||||
style={{
|
||||
background: isHovered ? 'rgba(255, 255, 255, 0.06)' : 'transparent',
|
||||
opacity: isVisible ? 1 : 0,
|
||||
transform: isVisible ? 'translateX(0)' : 'translateX(-10px)',
|
||||
transition: 'all 0.3s ease-out',
|
||||
}}
|
||||
onMouseEnter={() => setIsHovered(true)}
|
||||
onMouseLeave={() => setIsHovered(false)}
|
||||
>
|
||||
<div className="w-10 h-10 rounded-xl bg-white/8 flex items-center justify-center text-xl"
|
||||
style={{ background: 'rgba(255,255,255,0.08)' }}>
|
||||
{icon}
|
||||
</div>
|
||||
<div className="flex-1">
|
||||
<p className="text-white font-medium">{title}</p>
|
||||
{subtitle && <p className="text-white/40 text-sm">{subtitle}</p>}
|
||||
</div>
|
||||
{value && <span className="text-white/50 font-medium">{value}</span>}
|
||||
<svg className="w-4 h-4 text-white/30" fill="none" stroke="currentColor" viewBox="0 0 24 24"
|
||||
style={{ transform: isHovered ? 'translateX(2px)' : 'translateX(0)', transition: 'transform 0.2s' }}>
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M9 5l7 7-7 7" />
|
||||
</svg>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// ACTION BUTTON
|
||||
// =============================================================================
|
||||
|
||||
function ActionButton({ icon, label, primary = false, onClick, delay = 0 }: {
|
||||
icon: string; label: string; primary?: boolean; onClick?: () => void; delay?: number
|
||||
}) {
|
||||
const [isVisible, setIsVisible] = useState(false)
|
||||
const [isPressed, setIsPressed] = useState(false)
|
||||
|
||||
useEffect(() => {
|
||||
const timer = setTimeout(() => setIsVisible(true), delay)
|
||||
return () => clearTimeout(timer)
|
||||
}, [delay])
|
||||
|
||||
return (
|
||||
<button
|
||||
className="w-full flex items-center justify-center gap-3 p-4 rounded-2xl font-medium transition-all"
|
||||
style={{
|
||||
background: primary
|
||||
? 'linear-gradient(135deg, rgba(96, 165, 250, 0.3), rgba(167, 139, 250, 0.3))'
|
||||
: 'rgba(255, 255, 255, 0.06)',
|
||||
border: '1px solid rgba(255, 255, 255, 0.08)',
|
||||
color: 'white',
|
||||
opacity: isVisible ? 1 : 0,
|
||||
transform: isVisible ? `translateY(0) scale(${isPressed ? 0.97 : 1})` : 'translateY(10px)',
|
||||
transition: 'all 0.3s cubic-bezier(0.34, 1.56, 0.64, 1)',
|
||||
}}
|
||||
onMouseDown={() => setIsPressed(true)}
|
||||
onMouseUp={() => setIsPressed(false)}
|
||||
onMouseLeave={() => setIsPressed(false)}
|
||||
onClick={onClick}
|
||||
>
|
||||
<span className="text-xl">{icon}</span>
|
||||
<span>{label}</span>
|
||||
</button>
|
||||
)
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// QUALITY INDICATOR
|
||||
// =============================================================================
|
||||
|
||||
function QualityIndicator() {
|
||||
const { metrics, settings, forceQuality } = usePerformance()
|
||||
const [isExpanded, setIsExpanded] = useState(false)
|
||||
|
||||
return (
|
||||
<div
|
||||
className="fixed bottom-6 left-6 z-50"
|
||||
style={{
|
||||
background: 'rgba(0, 0, 0, 0.3)',
|
||||
backdropFilter: 'blur(20px)',
|
||||
WebkitBackdropFilter: 'blur(20px)',
|
||||
border: '1px solid rgba(255, 255, 255, 0.08)',
|
||||
borderRadius: 16,
|
||||
padding: isExpanded ? 16 : 12,
|
||||
minWidth: isExpanded ? 200 : 'auto',
|
||||
transition: 'all 0.3s ease-out',
|
||||
}}
|
||||
>
|
||||
<button
|
||||
onClick={() => setIsExpanded(!isExpanded)}
|
||||
className="flex items-center gap-3 text-white/70 text-sm"
|
||||
>
|
||||
<span className={`w-2 h-2 rounded-full ${
|
||||
metrics.qualityLevel === 'high' ? 'bg-green-400' :
|
||||
metrics.qualityLevel === 'medium' ? 'bg-yellow-400' : 'bg-red-400'
|
||||
}`} />
|
||||
<span className="font-mono">{metrics.fps} FPS</span>
|
||||
<span className="text-white/30">|</span>
|
||||
<span className="uppercase text-xs tracking-wide">{metrics.qualityLevel}</span>
|
||||
</button>
|
||||
|
||||
{isExpanded && (
|
||||
<div className="mt-4 pt-4 border-t border-white/10 space-y-2">
|
||||
<div className="flex gap-1">
|
||||
{(['high', 'medium', 'low'] as const).map((level) => (
|
||||
<button
|
||||
key={level}
|
||||
onClick={() => forceQuality(level)}
|
||||
className={`flex-1 py-1.5 rounded-lg text-xs font-medium transition-all ${
|
||||
metrics.qualityLevel === level
|
||||
? 'bg-white/15 text-white'
|
||||
: 'bg-white/5 text-white/40 hover:bg-white/10'
|
||||
}`}
|
||||
>
|
||||
{level[0].toUpperCase()}
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// MAIN DASHBOARD
|
||||
// =============================================================================
|
||||
|
||||
function DashboardContent() {
|
||||
const router = useRouter()
|
||||
const { settings } = usePerformance()
|
||||
const [mousePos, setMousePos] = useState({ x: 0, y: 0 })
|
||||
const [time, setTime] = useState(new Date())
|
||||
|
||||
useEffect(() => {
|
||||
const timer = setInterval(() => setTime(new Date()), 1000)
|
||||
return () => clearInterval(timer)
|
||||
}, [])
|
||||
|
||||
useEffect(() => {
|
||||
if (!settings.enableParallax) return
|
||||
const handleMouseMove = (e: MouseEvent) => setMousePos({ x: e.clientX, y: e.clientY })
|
||||
window.addEventListener('mousemove', handleMouseMove)
|
||||
return () => window.removeEventListener('mousemove', handleMouseMove)
|
||||
}, [settings.enableParallax])
|
||||
|
||||
const windowWidth = typeof window !== 'undefined' ? window.innerWidth : 1920
|
||||
const windowHeight = typeof window !== 'undefined' ? window.innerHeight : 1080
|
||||
const parallax = settings.enableParallax
|
||||
? { x: (mousePos.x / windowWidth - 0.5) * 15, y: (mousePos.y / windowHeight - 0.5) * 15 }
|
||||
: { x: 0, y: 0 }
|
||||
|
||||
const greeting = time.getHours() < 12 ? 'Guten Morgen' : time.getHours() < 18 ? 'Guten Tag' : 'Guten Abend'
|
||||
|
||||
// Weekly correction data
|
||||
const weeklyData = [
|
||||
{ label: 'Mo', value: 4, highlight: false },
|
||||
{ label: 'Di', value: 7, highlight: false },
|
||||
{ label: 'Mi', value: 3, highlight: false },
|
||||
{ label: 'Do', value: 8, highlight: false },
|
||||
{ label: 'Fr', value: 6, highlight: true },
|
||||
{ label: 'Sa', value: 2, highlight: false },
|
||||
{ label: 'So', value: 0, highlight: false },
|
||||
]
|
||||
|
||||
return (
|
||||
<div className="min-h-screen relative overflow-hidden">
|
||||
{/* Background */}
|
||||
<div
|
||||
className="absolute inset-0 bg-gradient-to-br from-slate-900 via-indigo-950 to-slate-900"
|
||||
style={{
|
||||
transform: `translate(${parallax.x * 0.5}px, ${parallax.y * 0.5}px) scale(1.05)`,
|
||||
transition: 'transform 0.3s ease-out',
|
||||
}}
|
||||
>
|
||||
{/* Stars */}
|
||||
<div className="absolute inset-0 opacity-30"
|
||||
style={{
|
||||
backgroundImage: `radial-gradient(2px 2px at 20px 30px, white, transparent),
|
||||
radial-gradient(2px 2px at 40px 70px, rgba(255,255,255,0.8), transparent),
|
||||
radial-gradient(1px 1px at 90px 40px, white, transparent),
|
||||
radial-gradient(2px 2px at 160px 120px, rgba(255,255,255,0.9), transparent),
|
||||
radial-gradient(1px 1px at 230px 80px, white, transparent),
|
||||
radial-gradient(2px 2px at 300px 150px, rgba(255,255,255,0.7), transparent)`,
|
||||
backgroundSize: '400px 200px',
|
||||
}}
|
||||
/>
|
||||
{/* Ambient glows */}
|
||||
<div className="absolute w-[500px] h-[500px] rounded-full opacity-20"
|
||||
style={{
|
||||
background: 'radial-gradient(circle, rgba(99, 102, 241, 0.5) 0%, transparent 70%)',
|
||||
left: '10%', top: '20%',
|
||||
transform: `translate(${parallax.x}px, ${parallax.y}px)`,
|
||||
transition: 'transform 0.5s ease-out',
|
||||
}}
|
||||
/>
|
||||
<div className="absolute w-[400px] h-[400px] rounded-full opacity-15"
|
||||
style={{
|
||||
background: 'radial-gradient(circle, rgba(167, 139, 250, 0.5) 0%, transparent 70%)',
|
||||
right: '5%', bottom: '10%',
|
||||
transform: `translate(${-parallax.x * 0.8}px, ${-parallax.y * 0.8}px)`,
|
||||
transition: 'transform 0.5s ease-out',
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* Content */}
|
||||
<div className="relative z-10 min-h-screen p-6">
|
||||
{/* Header */}
|
||||
<header className="flex items-start justify-between mb-8">
|
||||
<div>
|
||||
<p className="text-white/40 text-sm font-medium tracking-wide uppercase mb-1">
|
||||
{time.toLocaleDateString('de-DE', { weekday: 'long', day: 'numeric', month: 'long' })}
|
||||
</p>
|
||||
<h1 className="text-4xl font-light text-white tracking-tight">{greeting}</h1>
|
||||
</div>
|
||||
<div className="flex items-center gap-3">
|
||||
<GlassCard size="sm" className="!p-3">
|
||||
<div className="flex items-center gap-2">
|
||||
<span className="text-lg">🔔</span>
|
||||
<span className="text-white font-medium text-sm">3</span>
|
||||
</div>
|
||||
</GlassCard>
|
||||
<GlassCard size="sm" className="!p-3">
|
||||
<div className="w-8 h-8 rounded-full bg-gradient-to-br from-blue-400 to-purple-500" />
|
||||
</GlassCard>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
{/* Main Grid */}
|
||||
<div className="grid grid-cols-12 gap-4 max-w-7xl mx-auto">
|
||||
|
||||
{/* Clock & Weather Row */}
|
||||
<div className="col-span-3">
|
||||
<GlassCard size="lg" delay={50}>
|
||||
<div className="flex flex-col items-center">
|
||||
<AnalogClock />
|
||||
<p className="text-white text-2xl font-light mt-4">
|
||||
{time.toLocaleTimeString('de-DE', { hour: '2-digit', minute: '2-digit' })}
|
||||
</p>
|
||||
</div>
|
||||
</GlassCard>
|
||||
</div>
|
||||
|
||||
<div className="col-span-3">
|
||||
<GlassCard size="lg" delay={100}>
|
||||
<TemperatureDisplay temp={8} condition="partly_cloudy" />
|
||||
</GlassCard>
|
||||
</div>
|
||||
|
||||
<div className="col-span-3">
|
||||
<GlassCard size="lg" delay={150}>
|
||||
<div className="flex flex-col items-center">
|
||||
<Compass direction={225} />
|
||||
<p className="text-white/50 text-sm mt-3">SW Wind</p>
|
||||
<p className="text-white text-lg font-light">12 km/h</p>
|
||||
</div>
|
||||
</GlassCard>
|
||||
</div>
|
||||
|
||||
<div className="col-span-3">
|
||||
<GlassCard size="lg" delay={200}>
|
||||
<StatDisplay icon="📋" value="12" label="Offene Korrekturen" />
|
||||
<div className="mt-4 pt-4 border-t border-white/10 flex justify-around">
|
||||
<div className="text-center">
|
||||
<p className="text-xl font-light text-white">28</p>
|
||||
<p className="text-white/40 text-xs">Diese Woche</p>
|
||||
</div>
|
||||
<div className="text-center">
|
||||
<p className="text-xl font-light text-white">156</p>
|
||||
<p className="text-white/40 text-xs">Gesamt</p>
|
||||
</div>
|
||||
</div>
|
||||
</GlassCard>
|
||||
</div>
|
||||
|
||||
{/* Bar Chart */}
|
||||
<div className="col-span-6">
|
||||
<GlassCard size="lg" delay={250}>
|
||||
<div className="flex items-center justify-between mb-4">
|
||||
<h2 className="text-white text-sm font-medium uppercase tracking-wide opacity-60">Korrekturen diese Woche</h2>
|
||||
<span className="text-white/40 text-sm">30 gesamt</span>
|
||||
</div>
|
||||
<BarChart data={weeklyData} maxValue={10} />
|
||||
</GlassCard>
|
||||
</div>
|
||||
|
||||
{/* Progress Rings */}
|
||||
<div className="col-span-3">
|
||||
<GlassCard size="lg" delay={300}>
|
||||
<div className="flex justify-around">
|
||||
<ProgressRing progress={75} label="Fortschritt" value="75%" color="#60a5fa" />
|
||||
<ProgressRing progress={92} label="Qualitaet" value="92%" color="#a78bfa" />
|
||||
</div>
|
||||
</GlassCard>
|
||||
</div>
|
||||
|
||||
{/* Time Saved */}
|
||||
<div className="col-span-3">
|
||||
<GlassCard size="lg" delay={350}>
|
||||
<StatDisplay icon="⏱" value="4.2" unit="h" label="Zeit gespart" />
|
||||
<p className="text-center text-white/30 text-xs mt-3">durch KI-Unterstuetzung</p>
|
||||
</GlassCard>
|
||||
</div>
|
||||
|
||||
{/* Klausuren List */}
|
||||
<div className="col-span-8">
|
||||
<GlassCard size="lg" delay={400}>
|
||||
<div className="flex items-center justify-between mb-3">
|
||||
<h2 className="text-white text-sm font-medium uppercase tracking-wide opacity-60">Aktuelle Klausuren</h2>
|
||||
<button className="text-white/40 text-xs hover:text-white transition-colors">Alle anzeigen</button>
|
||||
</div>
|
||||
<div className="space-y-1">
|
||||
<ListItem icon="📝" title="Deutsch LK - Textanalyse" subtitle="24 Schueler" value="18/24" delay={450} />
|
||||
<ListItem icon="✅" title="Deutsch GK - Eroerterung" subtitle="Abgeschlossen" value="28/28" delay={500} />
|
||||
<ListItem icon="📝" title="Vorabitur - Gedichtanalyse" subtitle="22 Schueler" value="10/22" delay={550} />
|
||||
</div>
|
||||
</GlassCard>
|
||||
</div>
|
||||
|
||||
{/* Quick Actions */}
|
||||
<div className="col-span-4">
|
||||
<GlassCard size="lg" delay={450}>
|
||||
<h2 className="text-white text-sm font-medium uppercase tracking-wide opacity-60 mb-4">Schnellaktionen</h2>
|
||||
<div className="space-y-2">
|
||||
<ActionButton icon="➕" label="Neue Klausur" primary delay={500} />
|
||||
<ActionButton icon="📤" label="Arbeiten hochladen" delay={550} />
|
||||
<ActionButton icon="🎨" label="Worksheet Editor" onClick={() => router.push('/worksheet-editor')} delay={600} />
|
||||
</div>
|
||||
</GlassCard>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Floating Messages */}
|
||||
<FloatingMessage
|
||||
autoDismissMs={12000}
|
||||
maxQueue={3}
|
||||
position="top-right"
|
||||
offset={{ x: 24, y: 24 }}
|
||||
/>
|
||||
|
||||
{/* Quality Indicator */}
|
||||
<QualityIndicator />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// MAIN PAGE
|
||||
// =============================================================================
|
||||
|
||||
export default function ExperimentalDashboard() {
|
||||
return (
|
||||
<PerformanceProvider>
|
||||
<FocusProvider>
|
||||
<DashboardContent />
|
||||
</FocusProvider>
|
||||
</PerformanceProvider>
|
||||
)
|
||||
}
|
||||
Reference in New Issue
Block a user