website (17 pages + 3 components): - multiplayer/wizard, middleware/wizard+test-wizard, communication - builds/wizard, staff-search, voice, sbom/wizard - foerderantrag, mail/tasks, tools/communication, sbom - compliance/evidence, uni-crawler, brandbook (already done) - CollectionsTab, IngestionTab, RiskHeatmap backend-lehrer (5 files): - letters_api (641 → 2), certificates_api (636 → 2) - alerts_agent/db/models (636 → 3) - llm_gateway/communication_service (614 → 2) - game/database already done in prior batch klausur-service (2 files): - hybrid_vocab_extractor (664 → 2) - klausur-service/frontend: api.ts (620 → 3), EHUploadWizard (591 → 2) voice-service (3 files): - bqas/rag_judge (618 → 3), runner (529 → 2) - enhanced_task_orchestrator (519 → 2) studio-v2 (6 files): - korrektur/[klausurId] (578 → 4), fairness (569 → 2) - AlertsWizard (552 → 2), OnboardingWizard (513 → 2) - korrektur/api.ts (506 → 3), geo-lernwelt (501 → 2) Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
61 lines
1.8 KiB
TypeScript
61 lines
1.8 KiB
TypeScript
'use client'
|
|
|
|
import { useState, useEffect } from 'react'
|
|
|
|
interface GlassCardProps {
|
|
children: React.ReactNode
|
|
className?: string
|
|
onClick?: () => void
|
|
size?: 'sm' | 'md' | 'lg'
|
|
delay?: number
|
|
isDark?: boolean
|
|
}
|
|
|
|
export function GlassCard({ children, className = '', onClick, size = 'md', delay = 0, isDark = true }: GlassCardProps) {
|
|
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',
|
|
}
|
|
|
|
return (
|
|
<div
|
|
className={`
|
|
rounded-3xl
|
|
${sizeClasses[size]}
|
|
${onClick ? 'cursor-pointer' : ''}
|
|
${className}
|
|
`}
|
|
style={{
|
|
background: isDark
|
|
? (isHovered ? 'rgba(255, 255, 255, 0.12)' : 'rgba(255, 255, 255, 0.08)')
|
|
: (isHovered ? 'rgba(255, 255, 255, 0.9)' : 'rgba(255, 255, 255, 0.7)'),
|
|
backdropFilter: 'blur(24px) saturate(180%)',
|
|
WebkitBackdropFilter: 'blur(24px) saturate(180%)',
|
|
border: isDark ? '1px solid rgba(255, 255, 255, 0.1)' : '1px solid rgba(0, 0, 0, 0.1)',
|
|
boxShadow: isDark
|
|
? '0 4px 24px rgba(0, 0, 0, 0.15), inset 0 1px 0 rgba(255, 255, 255, 0.05)'
|
|
: '0 4px 24px rgba(0, 0, 0, 0.08), inset 0 1px 0 rgba(255, 255, 255, 0.5)',
|
|
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>
|
|
)
|
|
}
|