'use client' import React, { createContext, useContext, useState, useEffect, useCallback, useRef } from 'react' /** * Performance Context - Adaptive Quality System * * Monitors device capabilities and runtime performance to automatically * adjust visual quality. This ensures smooth 60fps (or 120fps on capable devices) * by degrading effects when needed. */ // ============================================================================= // TYPES // ============================================================================= export type QualityLevel = 'high' | 'medium' | 'low' | 'minimal' export interface PerformanceMetrics { /** Frames per second (rolling average) */ fps: number /** Dropped frames in last second */ droppedFrames: number /** Device memory in GB (if available) */ deviceMemory: number | null /** Number of logical CPU cores */ hardwareConcurrency: number /** Whether device prefers reduced motion */ prefersReducedMotion: boolean /** Whether device is on battery/power-save */ isLowPowerMode: boolean /** Current quality level */ qualityLevel: QualityLevel } export interface QualitySettings { /** Enable backdrop blur effects */ enableBlur: boolean /** Blur intensity multiplier (0-1) */ blurIntensity: number /** Enable shadow effects */ enableShadows: boolean /** Shadow complexity (0-1) */ shadowComplexity: number /** Enable parallax effects */ enableParallax: boolean /** Parallax intensity multiplier (0-1) */ parallaxIntensity: number /** Enable spring animations */ enableSpringAnimations: boolean /** Animation duration multiplier (0.5-1.5) */ animationSpeed: number /** Enable typewriter effects */ enableTypewriter: boolean /** Max concurrent animations */ maxConcurrentAnimations: number } interface PerformanceContextType { metrics: PerformanceMetrics settings: QualitySettings /** Force a specific quality level (null = auto) */ forceQuality: (level: QualityLevel | null) => void /** Report that an animation started */ reportAnimationStart: () => void /** Report that an animation ended */ reportAnimationEnd: () => void /** Check if we can start another animation */ canStartAnimation: () => boolean } // ============================================================================= // DEFAULT VALUES // ============================================================================= const DEFAULT_METRICS: PerformanceMetrics = { fps: 60, droppedFrames: 0, deviceMemory: null, hardwareConcurrency: 4, prefersReducedMotion: false, isLowPowerMode: false, qualityLevel: 'high', } const QUALITY_PRESETS: Record = { high: { enableBlur: true, blurIntensity: 1, enableShadows: true, shadowComplexity: 1, enableParallax: true, parallaxIntensity: 1, enableSpringAnimations: true, animationSpeed: 1, enableTypewriter: true, maxConcurrentAnimations: 10, }, medium: { enableBlur: true, blurIntensity: 0.7, enableShadows: true, shadowComplexity: 0.7, enableParallax: true, parallaxIntensity: 0.5, enableSpringAnimations: true, animationSpeed: 0.9, enableTypewriter: true, maxConcurrentAnimations: 6, }, low: { enableBlur: false, blurIntensity: 0, enableShadows: true, shadowComplexity: 0.4, enableParallax: false, parallaxIntensity: 0, enableSpringAnimations: false, animationSpeed: 0.7, enableTypewriter: true, maxConcurrentAnimations: 3, }, minimal: { enableBlur: false, blurIntensity: 0, enableShadows: false, shadowComplexity: 0, enableParallax: false, parallaxIntensity: 0, enableSpringAnimations: false, animationSpeed: 0.5, enableTypewriter: false, maxConcurrentAnimations: 1, }, } // ============================================================================= // CONTEXT // ============================================================================= const PerformanceContext = createContext(null) // ============================================================================= // PROVIDER // ============================================================================= export function PerformanceProvider({ children }: { children: React.ReactNode }) { const [metrics, setMetrics] = useState(DEFAULT_METRICS) const [forcedQuality, setForcedQuality] = useState(null) const [activeAnimations, setActiveAnimations] = useState(0) const frameTimesRef = useRef([]) const lastFrameTimeRef = useRef(performance.now()) const rafIdRef = useRef(null) // Detect device capabilities on mount useEffect(() => { if (typeof window === 'undefined') return const detectCapabilities = () => { // Hardware concurrency const cores = navigator.hardwareConcurrency || 4 // Device memory (Chrome only) const memory = (navigator as any).deviceMemory || null // Reduced motion preference const reducedMotion = window.matchMedia('(prefers-reduced-motion: reduce)').matches // Battery/power save mode (approximation) let lowPower = false if ('getBattery' in navigator) { (navigator as any).getBattery?.().then((battery: any) => { lowPower = battery.level < 0.2 && !battery.charging setMetrics(prev => ({ ...prev, isLowPowerMode: lowPower })) }) } // Determine initial quality level let initialQuality: QualityLevel = 'high' if (reducedMotion) { initialQuality = 'minimal' } else if (cores <= 2 || (memory && memory <= 2)) { initialQuality = 'low' } else if (cores <= 4 || (memory && memory <= 4)) { initialQuality = 'medium' } setMetrics(prev => ({ ...prev, hardwareConcurrency: cores, deviceMemory: memory, prefersReducedMotion: reducedMotion, qualityLevel: initialQuality, })) } detectCapabilities() // Listen for reduced motion changes const mediaQuery = window.matchMedia('(prefers-reduced-motion: reduce)') const handleChange = (e: MediaQueryListEvent) => { setMetrics(prev => ({ ...prev, prefersReducedMotion: e.matches, qualityLevel: e.matches ? 'minimal' : prev.qualityLevel, })) } mediaQuery.addEventListener('change', handleChange) return () => mediaQuery.removeEventListener('change', handleChange) }, []) // FPS monitoring loop useEffect(() => { if (typeof window === 'undefined') return let frameCount = 0 let lastFpsUpdate = performance.now() const measureFrame = (timestamp: number) => { const delta = timestamp - lastFrameTimeRef.current lastFrameTimeRef.current = timestamp // Track frame times (keep last 60) frameTimesRef.current.push(delta) if (frameTimesRef.current.length > 60) { frameTimesRef.current.shift() } frameCount++ // Update FPS every second if (timestamp - lastFpsUpdate >= 1000) { const avgFrameTime = frameTimesRef.current.reduce((a, b) => a + b, 0) / frameTimesRef.current.length const fps = Math.round(1000 / avgFrameTime) // Count dropped frames (frames > 20ms = dropped at 60fps) const dropped = frameTimesRef.current.filter(t => t > 20).length setMetrics(prev => { // Auto-adjust quality based on performance let newQuality = prev.qualityLevel if (!forcedQuality) { if (dropped > 10 && prev.qualityLevel !== 'minimal') { // Downgrade const levels: QualityLevel[] = ['high', 'medium', 'low', 'minimal'] const currentIndex = levels.indexOf(prev.qualityLevel) newQuality = levels[Math.min(currentIndex + 1, levels.length - 1)] } else if (dropped === 0 && fps >= 58 && prev.qualityLevel !== 'high') { // Consider upgrade (only if stable for a while) // This is conservative - we don't want to oscillate } } return { ...prev, fps, droppedFrames: dropped, qualityLevel: forcedQuality || newQuality, } }) frameCount = 0 lastFpsUpdate = timestamp } rafIdRef.current = requestAnimationFrame(measureFrame) } rafIdRef.current = requestAnimationFrame(measureFrame) return () => { if (rafIdRef.current) { cancelAnimationFrame(rafIdRef.current) } } }, [forcedQuality]) // Get current settings based on quality level const settings = QUALITY_PRESETS[forcedQuality || metrics.qualityLevel] // Force quality level const forceQuality = useCallback((level: QualityLevel | null) => { setForcedQuality(level) if (level) { setMetrics(prev => ({ ...prev, qualityLevel: level })) } }, []) // Animation tracking const reportAnimationStart = useCallback(() => { setActiveAnimations(prev => prev + 1) }, []) const reportAnimationEnd = useCallback(() => { setActiveAnimations(prev => Math.max(0, prev - 1)) }, []) const canStartAnimation = useCallback(() => { return activeAnimations < settings.maxConcurrentAnimations }, [activeAnimations, settings.maxConcurrentAnimations]) return ( {children} ) } // ============================================================================= // HOOKS // ============================================================================= export function usePerformance() { const context = useContext(PerformanceContext) if (!context) { // Return defaults if used outside provider return { metrics: DEFAULT_METRICS, settings: QUALITY_PRESETS.high, forceQuality: () => {}, reportAnimationStart: () => {}, reportAnimationEnd: () => {}, canStartAnimation: () => true, } } return context } /** * Hook to get adaptive blur value */ export function useAdaptiveBlur(baseBlur: number): number { const { settings } = usePerformance() if (!settings.enableBlur) return 0 return Math.round(baseBlur * settings.blurIntensity) } /** * Hook to get adaptive shadow */ export function useAdaptiveShadow(baseShadow: string, fallbackShadow: string = 'none'): string { const { settings } = usePerformance() if (!settings.enableShadows) return fallbackShadow // Could also reduce shadow complexity here return baseShadow } /** * Hook for reduced motion check */ export function usePrefersReducedMotion(): boolean { const { metrics } = usePerformance() return metrics.prefersReducedMotion } /** * Hook to get animation duration with performance adjustment */ export function useAdaptiveAnimationDuration(baseDuration: number): number { const { settings } = usePerformance() return Math.round(baseDuration * settings.animationSpeed) }