/** * 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 = { /** 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