mirror of
https://github.com/Since-3/since3-website.git
synced 2026-10-12 20:51:17 +00:00
56 lines
1.9 KiB
TypeScript
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
|
|
}
|