Files
Benjamin Boenisch 5a31f52310 Initial commit: breakpilot-lehrer - Lehrer KI Platform
Services: Admin-Lehrer, Backend-Lehrer, Studio v2, Website,
Klausur-Service, School-Service, Voice-Service, Geo-Service,
BreakPilot Drive, Agent-Core

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-11 23:47:26 +01:00

336 lines
9.3 KiB
TypeScript

/**
* Spatial UI - Depth System
*
* Design tokens for creating consistent depth and layering across the UI.
* Based on the "Fake-3D without 3D" principle - 2.5D composition using
* z-hierarchy, shadows, blur, and subtle motion.
*/
// =============================================================================
// LAYER DEFINITIONS
// =============================================================================
export const LAYERS = {
/** Base content layer - main page content */
base: {
name: 'base',
zIndex: 0,
elevation: 0,
},
/** Content cards, lists, primary UI elements */
content: {
name: 'content',
zIndex: 10,
elevation: 1,
},
/** Floating elements, dropdowns, popovers */
floating: {
name: 'floating',
zIndex: 20,
elevation: 2,
},
/** Sticky headers, navigation */
sticky: {
name: 'sticky',
zIndex: 30,
elevation: 2,
},
/** Overlays, notifications, chat bubbles */
overlay: {
name: 'overlay',
zIndex: 40,
elevation: 3,
},
/** Modal dialogs */
modal: {
name: 'modal',
zIndex: 50,
elevation: 4,
},
/** Tooltips, cursor assists */
tooltip: {
name: 'tooltip',
zIndex: 60,
elevation: 5,
},
} as const
export type LayerName = keyof typeof LAYERS
// =============================================================================
// SHADOW DEFINITIONS (Dynamic based on elevation)
// =============================================================================
export const SHADOWS = {
/** No shadow - flat on surface */
none: 'none',
/** Subtle lift - barely visible */
xs: '0 1px 2px rgba(0,0,0,0.05)',
/** Small lift - cards at rest */
sm: '0 2px 4px rgba(0,0,0,0.06), 0 1px 2px rgba(0,0,0,0.04)',
/** Medium lift - cards on hover */
md: '0 4px 8px rgba(0,0,0,0.08), 0 2px 4px rgba(0,0,0,0.04)',
/** Large lift - active/dragging */
lg: '0 8px 16px rgba(0,0,0,0.10), 0 4px 8px rgba(0,0,0,0.06)',
/** Extra large - floating overlays */
xl: '0 12px 24px rgba(0,0,0,0.12), 0 6px 12px rgba(0,0,0,0.08)',
/** 2XL - modals, maximum elevation */
'2xl': '0 20px 40px rgba(0,0,0,0.15), 0 10px 20px rgba(0,0,0,0.10)',
/** Glow effect for focus states */
glow: (color: string, intensity: number = 0.3) =>
`0 0 20px rgba(${color}, ${intensity}), 0 4px 12px rgba(0,0,0,0.1)`,
} as const
// Dark mode shadows (slightly more pronounced)
export const SHADOWS_DARK = {
none: 'none',
xs: '0 1px 3px rgba(0,0,0,0.2)',
sm: '0 2px 6px rgba(0,0,0,0.25), 0 1px 3px rgba(0,0,0,0.15)',
md: '0 4px 12px rgba(0,0,0,0.3), 0 2px 6px rgba(0,0,0,0.2)',
lg: '0 8px 20px rgba(0,0,0,0.35), 0 4px 10px rgba(0,0,0,0.25)',
xl: '0 12px 32px rgba(0,0,0,0.4), 0 6px 16px rgba(0,0,0,0.3)',
'2xl': '0 20px 50px rgba(0,0,0,0.5), 0 10px 25px rgba(0,0,0,0.35)',
glow: (color: string, intensity: number = 0.4) =>
`0 0 30px rgba(${color}, ${intensity}), 0 4px 16px rgba(0,0,0,0.3)`,
} as const
// =============================================================================
// BLUR DEFINITIONS (Material simulation)
// =============================================================================
export const BLUR = {
/** No blur */
none: 0,
/** Subtle - barely perceptible */
xs: 4,
/** Small - light frosted glass */
sm: 8,
/** Medium - standard glass effect */
md: 12,
/** Large - heavy frosted glass */
lg: 16,
/** Extra large - maximum blur */
xl: 24,
/** 2XL - extreme blur for backgrounds */
'2xl': 32,
} as const
// =============================================================================
// MOTION DEFINITIONS (Physics-based transitions)
// =============================================================================
export const MOTION = {
/** Micro-interactions (hover, focus) */
micro: {
duration: 150,
easing: 'cubic-bezier(0.25, 0.1, 0.25, 1)',
},
/** Standard transitions */
standard: {
duration: 220,
easing: 'cubic-bezier(0.4, 0, 0.2, 1)',
},
/** Emphasis (entering elements) */
emphasis: {
duration: 300,
easing: 'cubic-bezier(0.0, 0, 0.2, 1)',
},
/** Spring-like bounce */
spring: {
duration: 400,
easing: 'cubic-bezier(0.34, 1.56, 0.64, 1)',
},
/** Deceleration (entering from off-screen) */
decelerate: {
duration: 350,
easing: 'cubic-bezier(0, 0, 0.2, 1)',
},
/** Acceleration (exiting to off-screen) */
accelerate: {
duration: 250,
easing: 'cubic-bezier(0.4, 0, 1, 1)',
},
} as const
// =============================================================================
// PARALLAX DEFINITIONS (Subtle depth cues)
// =============================================================================
export const PARALLAX = {
/** No parallax */
none: 0,
/** Minimal - barely perceptible (1-2px) */
subtle: 0.02,
/** Light - noticeable but not distracting (2-4px) */
light: 0.04,
/** Medium - clear depth separation (4-6px) */
medium: 0.06,
/** Strong - pronounced effect (use sparingly) */
strong: 0.1,
} as const
// =============================================================================
// MATERIAL DEFINITIONS (Surface types)
// =============================================================================
export interface Material {
background: string
backgroundDark: string
blur: number
opacity: number
border: string
borderDark: string
}
export const MATERIALS: Record<string, Material> = {
/** Solid surface - no transparency */
solid: {
background: 'rgb(255, 255, 255)',
backgroundDark: 'rgb(15, 23, 42)',
blur: 0,
opacity: 1,
border: 'rgba(0, 0, 0, 0.1)',
borderDark: 'rgba(255, 255, 255, 0.1)',
},
/** Frosted glass - standard glassmorphism */
glass: {
background: 'rgba(255, 255, 255, 0.7)',
backgroundDark: 'rgba(255, 255, 255, 0.1)',
blur: 12,
opacity: 0.7,
border: 'rgba(0, 0, 0, 0.1)',
borderDark: 'rgba(255, 255, 255, 0.2)',
},
/** Thin glass - more transparent */
thinGlass: {
background: 'rgba(255, 255, 255, 0.5)',
backgroundDark: 'rgba(255, 255, 255, 0.05)',
blur: 8,
opacity: 0.5,
border: 'rgba(0, 0, 0, 0.05)',
borderDark: 'rgba(255, 255, 255, 0.1)',
},
/** Thick glass - more opaque */
thickGlass: {
background: 'rgba(255, 255, 255, 0.85)',
backgroundDark: 'rgba(255, 255, 255, 0.15)',
blur: 16,
opacity: 0.85,
border: 'rgba(0, 0, 0, 0.15)',
borderDark: 'rgba(255, 255, 255, 0.25)',
},
/** Acrylic - Windows 11 style */
acrylic: {
background: 'rgba(255, 255, 255, 0.6)',
backgroundDark: 'rgba(30, 30, 30, 0.6)',
blur: 20,
opacity: 0.6,
border: 'rgba(255, 255, 255, 0.3)',
borderDark: 'rgba(255, 255, 255, 0.15)',
},
}
// =============================================================================
// UTILITY FUNCTIONS
// =============================================================================
/**
* Get shadow based on elevation level and theme
*/
export function getShadow(
elevation: 'none' | 'xs' | 'sm' | 'md' | 'lg' | 'xl' | '2xl',
isDark: boolean = false
): string {
return isDark ? SHADOWS_DARK[elevation] : SHADOWS[elevation]
}
/**
* Get CSS transition string for depth changes
*/
export function getDepthTransition(motion: keyof typeof MOTION = 'standard'): string {
const m = MOTION[motion]
return `box-shadow ${m.duration}ms ${m.easing}, transform ${m.duration}ms ${m.easing}`
}
/**
* Get transform for elevation effect (slight scale + Y offset)
*/
export function getElevationTransform(
elevation: 'rest' | 'hover' | 'active' | 'dragging'
): string {
switch (elevation) {
case 'rest':
return 'translateY(0) scale(1)'
case 'hover':
return 'translateY(-2px) scale(1.01)'
case 'active':
return 'translateY(0) scale(0.99)'
case 'dragging':
return 'translateY(-4px) scale(1.02)'
default:
return 'translateY(0) scale(1)'
}
}
/**
* Calculate parallax offset based on cursor position
*/
export function calculateParallax(
cursorX: number,
cursorY: number,
elementRect: DOMRect,
intensity: number = PARALLAX.subtle
): { x: number; y: number } {
const centerX = elementRect.left + elementRect.width / 2
const centerY = elementRect.top + elementRect.height / 2
const deltaX = (cursorX - centerX) * intensity
const deltaY = (cursorY - centerY) * intensity
// Clamp to reasonable values
const maxOffset = 6
return {
x: Math.max(-maxOffset, Math.min(maxOffset, deltaX)),
y: Math.max(-maxOffset, Math.min(maxOffset, deltaY)),
}
}
/**
* Get CSS variables for a material
*/
export function getMaterialCSS(material: keyof typeof MATERIALS, isDark: boolean): string {
const m = MATERIALS[material]
return `
background: ${isDark ? m.backgroundDark : m.background};
backdrop-filter: blur(${m.blur}px);
-webkit-backdrop-filter: blur(${m.blur}px);
border-color: ${isDark ? m.borderDark : m.border};
`
}
// =============================================================================
// CSS CLASS GENERATORS (for Tailwind-like usage)
// =============================================================================
export const depthClasses = {
// Layer z-index
'layer-base': 'z-0',
'layer-content': 'z-10',
'layer-floating': 'z-20',
'layer-sticky': 'z-30',
'layer-overlay': 'z-40',
'layer-modal': 'z-50',
'layer-tooltip': 'z-60',
// Transitions
'depth-transition': 'transition-all duration-200 ease-out',
'depth-transition-spring': 'transition-all duration-400',
} as const