"use client"; import { type RefObject, useEffect, useState } from "react"; import { isSaveData, MOBILE_QUERY } from "@/lib/device"; import type { Device } from "@/lib/project-showcase"; /** * Versão de vídeo e pôster que vale para o aparelho: "mobile" abaixo de 768 px, "desktop" acima. É `null` * até a página montar no navegador (no servidor nenhum arquivo de mídia é escolhido: só o espaço da capa). */ export function useViewportDevice(): Device | null { const [device, setDevice] = useState(null); useEffect(() => { if (typeof window.matchMedia !== "function") { setDevice("desktop"); return; } const query = window.matchMedia(MOBILE_QUERY); const update = () => setDevice(query.matches ? "mobile" : "desktop"); update(); query.addEventListener("change", update); return () => query.removeEventListener("change", update); }, []); return device; } /** Telas em que a vitrine fixa cabe: tablet ou desktop, com altura para o notebook e a legenda (showcase.css usa a mesma conta). */ export const PINNED_QUERY = "(min-width: 768px) and (min-height: 561px)"; /** A vitrine fixa cabe nesta tela? `null` até montar no navegador. */ export function usePinnedViewport(): boolean | null { const [pinned, setPinned] = useState(null); useEffect(() => { if (typeof window.matchMedia !== "function") { setPinned(true); return; } const query = window.matchMedia(PINNED_QUERY); const update = () => setPinned(query.matches); update(); query.addEventListener("change", update); return () => query.removeEventListener("change", update); }, []); return pinned; } /** "Economizar dados" ligado (lido no navegador, depois de montar): só pôsteres, nenhum vídeo baixado. */ export function useSaveData(): boolean { const [saveData, setSaveData] = useState(false); useEffect(() => { setSaveData(isSaveData()); }, []); return saveData; } /** Margem em volta da janela dentro da qual uma seção conta como "perto": uma tela acima e abaixo. */ export const NEAR_MARGIN = "100% 0px"; /** * Verdadeiro quando o elemento chegou a até ~1 tela da janela: só então o pôster dele é pedido. Depois de * perto, continua verdadeiro (o pôster já baixado não some e volta). */ export function useNearViewport(ref: RefObject): boolean { const [near, setNear] = useState(false); useEffect(() => { const element = ref.current; if (!element) return; if (typeof IntersectionObserver === "undefined") { setNear(true); return; } const observer = new IntersectionObserver( (entries) => { if (entries.some((entry) => entry.isIntersecting)) setNear(true); }, { rootMargin: NEAR_MARGIN }, ); observer.observe(element); return () => observer.disconnect(); }, [ref]); return near; }