import { useCallback, useEffect, useRef, useState } from 'react'; const STORAGE_KEY = 'deckhearth:scanner-sound'; function isMobileViewport() { if (typeof window === 'undefined') return false; return window.matchMedia('(max-width: 768px)').matches; } function readPersistedPreference() { if (typeof window === 'undefined') return null; try { const stored = localStorage.getItem(STORAGE_KEY); if (stored === 'true') return true; if (stored === 'false') return false; } catch { // localStorage unavailable (e.g. private browsing on some browsers) } return null; } /** * Scan sound feedback via the Web Audio API. * * - Persists the enabled/disabled preference to localStorage. * - Defaults to ON on mobile, OFF on desktop. * - AudioContext created lazily on first playback (browser autoplay policy). */ export function useScannerSound() { const [enabled, setEnabledState] = useState(() => { const persisted = readPersistedPreference(); return persisted ?? isMobileViewport(); }); const audioCtxRef = useRef(null); const setEnabled = useCallback((value) => { setEnabledState((prev) => { const next = typeof value === 'function' ? value(prev) : value; try { localStorage.setItem(STORAGE_KEY, String(next)); } catch { // ignore } return next; }); }, []); const getAudioContext = useCallback(() => { if (!audioCtxRef.current) { const AudioCtx = window.AudioContext || window.webkitAudioContext; if (!AudioCtx) return null; audioCtxRef.current = new AudioCtx(); } if (audioCtxRef.current.state === 'suspended') { audioCtxRef.current.resume().catch(() => {}); } return audioCtxRef.current; }, []); const playTone = useCallback( (frequency, durationMs) => { if (!enabled) return; const ctx = getAudioContext(); if (!ctx) return; const oscillator = ctx.createOscillator(); const gain = ctx.createGain(); oscillator.type = 'sine'; oscillator.frequency.setValueAtTime(frequency, ctx.currentTime); gain.gain.setValueAtTime(0.3, ctx.currentTime); gain.gain.exponentialRampToValueAtTime( 0.001, ctx.currentTime + durationMs / 1000 ); oscillator.connect(gain); gain.connect(ctx.destination); oscillator.start(ctx.currentTime); oscillator.stop(ctx.currentTime + durationMs / 1000); }, [enabled, getAudioContext] ); const playSuccess = useCallback(() => playTone(880, 50), [playTone]); const playError = useCallback(() => playTone(440, 80), [playTone]); useEffect(() => { return () => { if (audioCtxRef.current) { audioCtxRef.current.close().catch(() => {}); audioCtxRef.current = null; } }; }, []); return { enabled, setEnabled, playSuccess, playError }; }