diff --git a/src/components/GameActions.vue b/src/components/GameActions.vue index 0c54e4a..96e6fa2 100644 --- a/src/components/GameActions.vue +++ b/src/components/GameActions.vue @@ -19,7 +19,6 @@ function handleNewRandom() { - @@ -44,4 +43,4 @@ function handleNewRandom() { border-color: #fff; color: #fff; } - \ No newline at end of file + diff --git a/src/components/WinModal.vue b/src/components/WinModal.vue index 4cfb5f2..b11877a 100644 --- a/src/components/WinModal.vue +++ b/src/components/WinModal.vue @@ -6,6 +6,8 @@ import { usePuzzleStore } from '@/stores/puzzle'; const store = usePuzzleStore(); const fireworksRef = ref(null); let fireworksInstance = null; +let audioContext = null; +let masterGain = null; const handleClose = () => { store.closeWinModal(); @@ -17,6 +19,53 @@ const handleKeyDown = (e) => { } }; +const playFanfare = async () => { + const AudioCtx = window.AudioContext || window.webkitAudioContext; + if (!AudioCtx) return; + audioContext = new AudioCtx(); + if (audioContext.state === 'suspended') { + try { + await audioContext.resume(); + } catch { + return; + } + } + masterGain = audioContext.createGain(); + masterGain.gain.value = 0.18; + masterGain.connect(audioContext.destination); + const notes = [ + { time: 0.0, dur: 0.18, freqs: [523.25, 659.25, 783.99] }, + { time: 0.2, dur: 0.18, freqs: [587.33, 740.0, 880.0] }, + { time: 0.4, dur: 0.22, freqs: [659.25, 830.61, 987.77] }, + { time: 0.7, dur: 0.35, freqs: [698.46, 880.0, 1046.5] } + ]; + const now = audioContext.currentTime; + notes.forEach(({ time, dur, freqs }) => { + freqs.forEach((freq) => { + const osc = audioContext.createOscillator(); + const gain = audioContext.createGain(); + osc.type = 'triangle'; + osc.frequency.value = freq; + gain.gain.setValueAtTime(0.0001, now + time); + gain.gain.linearRampToValueAtTime(0.8, now + time + 0.02); + gain.gain.exponentialRampToValueAtTime(0.0001, now + time + dur); + osc.connect(gain); + gain.connect(masterGain); + osc.start(now + time); + osc.stop(now + time + dur + 0.05); + }); + }); +}; + +const triggerVibration = () => { + if (!('vibrate' in navigator)) return; + const isCoarse = window.matchMedia?.('(pointer: coarse)')?.matches; + const isTouch = navigator.maxTouchPoints && navigator.maxTouchPoints > 0; + if (isCoarse || isTouch) { + navigator.vibrate([80, 40, 120, 40, 180]); + } +}; + onMounted(() => { if (fireworksRef.value) { fireworksInstance = new Fireworks(fireworksRef.value, { @@ -37,6 +86,8 @@ onMounted(() => { }); fireworksInstance.start(); } + playFanfare(); + triggerVibration(); window.addEventListener('keydown', handleKeyDown); }); @@ -44,6 +95,14 @@ onUnmounted(() => { window.removeEventListener('keydown', handleKeyDown); fireworksInstance?.stop(true); fireworksInstance = null; + if ('vibrate' in navigator) { + navigator.vibrate(0); + } + if (audioContext) { + audioContext.close(); + audioContext = null; + } + masterGain = null; });