diff --git a/docs-site/src/components/CuckooClock.astro b/docs-site/src/components/CuckooClock.astro index 84a6d28..4b5b151 100644 --- a/docs-site/src/components/CuckooClock.astro +++ b/docs-site/src/components/CuckooClock.astro @@ -25,8 +25,8 @@ class="cuckoo__btn" id="cuckoo-clock" type="button" - aria-label="Cuckoo clock — click to strike the hour" - title="Strike the hour" + aria-label="Cuckoo clock. Click for one call; double-click to strike the hour." + title="Click for one call · double-click strikes the hour" > - Strike the hour + + Click · double-click strikes the hour +
@@ -196,7 +198,7 @@ return 0.78; // seconds until the next call may begin }; - clock.addEventListener("click", async () => { + const strike = async (calls) => { if (striking) return; striking = true; @@ -204,18 +206,42 @@ ctx = ctx ?? new (window.AudioContext || window.webkitAudioContext)(); if (ctx.state === "suspended") await ctx.resume(); - const hour = new Date().getHours() % 12 || 12; let t = ctx.currentTime + 0.05; - for (let i = 0; i < hour; i++) t += call(t); + for (let i = 0; i < calls; i++) t += call(t); clock.classList.add("is-striking"); - if (live) live.textContent = `Cuckoo ×${hour}`; + if (live) live.textContent = `Cuckoo ×${calls}`; const total = (t - ctx.currentTime) * 1000; setTimeout(() => { clock.classList.remove("is-striking"); striking = false; }, total); + }; + + // A single click is a greeting: one call. Double-click and it strikes the + // hour properly — which at eleven o'clock is a commitment. + // + // There is no `singleclick` event, and `click` always fires BEFORE `dblclick`. + // So hold the single call for one double-click interval and cancel it if a + // second click lands. 260 ms is comfortably under the platform's threshold + // but longer than a deliberate double-tap. + let pending = null; + + clock.addEventListener("click", () => { + if (pending) return; // this is the 2nd of a pair — let dblclick take it + pending = setTimeout(() => { + pending = null; + strike(1); + }, 260); + }); + + clock.addEventListener("dblclick", () => { + if (pending) { + clearTimeout(pending); + pending = null; + } + strike(new Date().getHours() % 12 || 12); }); } @@ -260,6 +286,7 @@ transition: color 0.2s; } .cuckoo__btn:hover .cuckoo__hint { color: var(--sl-color-accent); } + .cuckoo__hint-sep { opacity: 0.5; margin: 0 0.15em; } .cuckoo__live { min-height: 1.2em;