/** * quality.js — Detect hardware capability and return a quality tier. * * Tiers: * 'low' — older iPads, phones, low-end GPUs (reduce particles, simpler effects) * 'medium' — mid-range (moderate particle count) * 'high' — desktop, modern iPad Pro (full quality) * * Detection uses a combination of: * - Device pixel ratio (low DPR = likely low-end) * - Logical core count (navigator.hardwareConcurrency) * - Device memory (navigator.deviceMemory, Chrome/Edge only) * - Screen size (small viewport = likely mobile) * - Touch capability (touch + small screen = phone/tablet) * - WebGL renderer string (if available) */ let cachedTier = null; export function getQualityTier() { if (cachedTier) return cachedTier; let score = 0; // Core count: 1-2 = low, 4 = mid, 8+ = high const cores = navigator.hardwareConcurrency || 2; if (cores >= 8) score += 3; else if (cores >= 4) score += 2; else score += 0; // Device memory (Chrome/Edge): < 4GB = low, 4-8 = mid, 8+ = high const mem = navigator.deviceMemory || 4; if (mem >= 8) score += 3; else if (mem >= 4) score += 2; else score += 0; // Screen dimensions (logical pixels) const maxDim = Math.max(window.screen.width, window.screen.height); if (maxDim < 768) score -= 1; // phone else if (maxDim >= 1920) score += 1; // large desktop // DPR: high DPR on small screens = more GPU work const dpr = window.devicePixelRatio || 1; if (dpr > 2 && maxDim < 1024) score -= 1; // retina phone // Touch-only device heuristic const touchOnly = 'ontouchstart' in window && !window.matchMedia('(pointer: fine)').matches; if (touchOnly) score -= 1; // Try reading WebGL renderer for GPU hints try { const canvas = document.createElement('canvas'); const gl = canvas.getContext('webgl') || canvas.getContext('webgl2'); if (gl) { const debugExt = gl.getExtension('WEBGL_debug_renderer_info'); if (debugExt) { const renderer = gl.getParameter(debugExt.UNMASKED_RENDERER_WEBGL).toLowerCase(); // Known low-end GPU strings if (renderer.includes('swiftshader') || renderer.includes('llvmpipe') || renderer.includes('software')) { score -= 3; // software renderer } if (renderer.includes('apple gpu') || renderer.includes('apple m')) { score += 2; // Apple Silicon is good } } gl.getExtension('WEBGL_lose_context')?.loseContext(); } } catch { // Can't probe GPU, use other signals } // Map score to tier if (score <= 1) cachedTier = 'low'; else if (score <= 4) cachedTier = 'medium'; else cachedTier = 'high'; console.info(`[Matrix Quality] Tier: ${cachedTier} (score: ${score}, cores: ${cores}, mem: ${mem}GB, dpr: ${dpr}, touch: ${touchOnly})`); return cachedTier; } /** * Get the recommended pixel ratio cap for the renderer. */ export function getMaxPixelRatio() { const tier = getQualityTier(); if (tier === 'low') return 1; if (tier === 'medium') return 1.5; return 2; }