since3-website/modules/shared/ui/components/sound-effects.tsx
2026-09-25 00:45:33 +02:00

56 lines
1.9 KiB
TypeScript

"use client"
import { useEffect } from "react"
import { play } from "@/lib/sound"
const INTERACTIVE =
"a[href], button:not(:disabled), [role='tab'], summary, input[type='checkbox'], input[type='radio'], label:has(input), [data-sound]"
const HOVER_THROTTLE_MS = 60
/**
* Page-wide sound design via event delegation: a tick when the mouse enters
* a link or button, a click when it is activated. Elements with
* `data-sound="off"` play their own sounds (e.g. the sound toggle). Renders nothing.
*/
export function SoundEffects() {
useEffect(() => {
let lastTarget: Element | null = null
let lastHover = 0
const interactiveTarget = (event: Event) => {
const target = (event.target as Element | null)?.closest?.(INTERACTIVE)
return target && target.getAttribute("data-sound") !== "off" ? target : null
}
const onPointerOver = (event: PointerEvent) => {
// Touch has no hover; a tap would otherwise play hover and click at once
if (event.pointerType !== "mouse") return
const target = interactiveTarget(event)
if (target === lastTarget) return
lastTarget = target
if (!target) return
const now = performance.now()
if (now - lastHover < HOVER_THROTTLE_MS) return
lastHover = now
play("hover")
}
const onClick = (event: MouseEvent) => {
const target = interactiveTarget(event)
// A click on a label is forwarded to its input, which plays the sound
if (!target || target.tagName === "LABEL") return
play("click")
}
document.addEventListener("pointerover", onPointerOver)
// Capture phase, so the sound starts before any navigation or scrolling
document.addEventListener("click", onClick, true)
return () => {
document.removeEventListener("pointerover", onPointerOver)
document.removeEventListener("click", onClick, true)
}
}, [])
return null
}