/** * Leituras do aparelho que decidem o que a página carrega e anima: ponteiro de toque, pouca CPU ou memória, * "economizar dados" e largura de celular. Tudo roda só no navegador (no servidor, devolve o valor mais leve). */ /** Abaixo disto (px) a página usa as versões de celular dos vídeos e capas. */ export const MOBILE_MAX_WIDTH = 767; export const MOBILE_QUERY = `(max-width: ${MOBILE_MAX_WIDTH}px)`; type NavigatorHints = Navigator & { deviceMemory?: number; connection?: { saveData?: boolean }; }; function matches(query: string) { return ( typeof window !== "undefined" && typeof window.matchMedia === "function" && window.matchMedia(query).matches ); } /** Ponteiro principal impreciso (dedo): sem rolagem suavizada nem cena 3D. */ export const isCoarsePointer = () => matches("(pointer: coarse)"); export const prefersReducedMotion = () => matches("(prefers-reduced-motion: reduce)"); /** Tela de celular: abaixo de 768 px. */ export const isMobileViewport = () => matches(MOBILE_QUERY); /** "Economizar dados" ligado: só imagens, nenhum vídeo é baixado sozinho. */ export function isSaveData() { if (typeof navigator === "undefined") return false; return (navigator as NavigatorHints).connection?.saveData === true; } /** * Aparelho que não deve montar WebGL: toque, 4 núcleos ou menos, 4 GB de memória ou menos, economia de dados * ou movimento reduzido. O que não é informado pelo navegador (Safari, Firefox) não conta contra o aparelho. */ export function isLowPowerDevice() { if (typeof navigator === "undefined") return true; const hints = navigator as NavigatorHints; return ( isCoarsePointer() || prefersReducedMotion() || isSaveData() || (typeof hints.hardwareConcurrency === "number" && hints.hardwareConcurrency <= 4) || (typeof hints.deviceMemory === "number" && hints.deviceMemory <= 4) ); }