diff --git a/CLAUDE.md b/CLAUDE.md index bc85c91..0f07257 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -90,8 +90,8 @@ Saves searching. If one of these moves, fix the line here in the same commit. does. - **Destructive actions** ask through `components/ConfirmSheet.tsx`. Never `window.confirm()` or `prompt()`. -- **The Sound menu** (speaker button in the timer's header, only in the deep, with the - tank's volume) is `components/SoundMenu.tsx`. **Keep the picture** (the +- **The Sound menu** (speaker button in the timer's header, only in the deep, click plays the tank, hold opens + its volume) is `components/SoundMenu.tsx`. **Keep the picture** (the timer's picture button, the finish sheet's link, the mark on a sitting in Stats) is `components/SavePicture.tsx`. "This moment" is `lib/wallpaper.ts`; any canvas that should be in it registers a layer diff --git a/components/SoundMenu.tsx b/components/SoundMenu.tsx index b3b799e..2f68071 100644 --- a/components/SoundMenu.tsx +++ b/components/SoundMenu.tsx @@ -25,6 +25,22 @@ export default function SoundMenu({ tank, night, accent }: Props) { const rootRef = useRef(null); const playing = !!tank?.on; + /* A click plays or stops the tank, first time, because that click is the + gesture the browser wants before it will make a sound. The volume is + behind a press and hold (or a right click), so it is there but never + stands between the reader and the sea. */ + const holdRef = useRef<{ timer: number; fired: boolean }>({ timer: 0, fired: false }); + const startHold = () => { + window.clearTimeout(holdRef.current.timer); + holdRef.current.fired = false; + holdRef.current.timer = window.setTimeout(() => { + holdRef.current.fired = true; + setOpen(true); + }, 450); + }; + const endHold = () => window.clearTimeout(holdRef.current.timer); + useEffect(() => () => window.clearTimeout(holdRef.current.timer), []); + useEffect(() => { if (!open) return; const away = (e: MouseEvent | TouchEvent) => { @@ -91,11 +107,35 @@ export default function SoundMenu({ tank, night, accent }: Props) {