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>
336 lines
9.3 KiB
TypeScript
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
|