Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
46 changes: 43 additions & 3 deletions components/SoundMenu.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,22 @@ export default function SoundMenu({ tank, night, accent }: Props) {
const rootRef = useRef<HTMLDivElement | null>(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) => {
Expand Down Expand Up @@ -91,11 +107,35 @@ export default function SoundMenu({ tank, night, accent }: Props) {
<div ref={rootRef} className="relative">
<button
type="button"
onClick={() => setOpen((v) => !v)}
onClick={() => {
if (holdRef.current.fired) {
holdRef.current.fired = false;
return;
}
if (open) setOpen(false);
else tank.toggle();
}}
onPointerDown={startHold}
onPointerUp={endHold}
onPointerLeave={endHold}
onPointerCancel={endHold}
onContextMenu={(e) => {
e.preventDefault();
endHold();
holdRef.current.fired = true;
setOpen(true);
}}
onKeyDown={(e) => {
if (e.key === 'ArrowDown') {
e.preventDefault();
setOpen(true);
}
}}
aria-expanded={open}
aria-haspopup="true"
aria-label="Sound"
title="Sound"
aria-pressed={playing}
aria-label="Tank sound"
title="Tank sound (hold for volume)"
style={{ color: open || playing ? ink : soft }}
className="relative grid h-10 w-10 place-items-center rounded-[10px] transition-colors"
>
Expand Down
6 changes: 4 additions & 2 deletions readmedesign.md
Original file line number Diff line number Diff line change
Expand Up @@ -1334,8 +1334,10 @@ only ever grows, it never runs backwards, and none of it touches progress.
wherever the gain is, so a tap mid-fade turns around without a step).
- **The Sound menu** (`components/SoundMenu.tsx`) is the one speaker button in
the timer's header, drawn only in the deep, in the soft ink and not the faint one so it can be found
on either paper, with a dot in the course colour while anything plays. It
opens a small paper card under the button: a switch for the tank, and
on either paper, with a dot in the course colour while anything plays. One
click plays or stops the tank (that first click is the gesture the browser
wants, so there is never a second one in the way). Press and hold, or right
click, or ArrowDown, opens a small paper card under the button: a switch for the tank, and
under it a volume line, a pencil rule that fills with ink as it
rises, a round paper knob, and the level as mono digits. The line is
`.pl-volume` in `globals.css`; it takes the card's ink, track and knob as
Expand Down
Loading