Skip to content

Commit 80a1123

Browse files
committed
Merge branch 'codex/feedback-polish' into codex/daily-engagement
2 parents ead5f60 + 32dc21b commit 80a1123

3 files changed

Lines changed: 38 additions & 3 deletions

File tree

index.html

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,7 @@
6868
}
6969
@media(hover:none),(pointer:coarse){
7070
#action-button{display:block}
71+
#sound-button{right:max(104px,calc(104px + env(safe-area-inset-right)));bottom:max(28px,calc(28px + env(safe-area-inset-bottom)))}
7172
}
7273
@media(prefers-reduced-motion:reduce){
7374
#title-bar .dot,#hint{animation:none}
@@ -881,6 +882,7 @@ <h2 id="journal-title">SIGNAL LOG</h2>
881882
let clickMarker = null;
882883

883884
canvas.addEventListener('click', (e) => {
885+
feedback.unlock();
884886
canvas.focus({ preventScroll: true });
885887
const rect = canvas.getBoundingClientRect();
886888
const mx = e.clientX - rect.left, my = e.clientY - rect.top;
@@ -934,6 +936,7 @@ <h2 id="journal-title">SIGNAL LOG</h2>
934936

935937
window.addEventListener('keydown', (e) => {
936938
if (!shouldHandleGameKey(e.target)) return;
939+
feedback.unlock();
937940
const k = e.key.toLowerCase();
938941
if (['w','a','s','d','arrowup','arrowdown','arrowleft','arrowright',' ','enter'].includes(k)) e.preventDefault();
939942
keysDown[k] = true;
@@ -1099,6 +1102,7 @@ <h2 id="journal-title">SIGNAL LOG</h2>
10991102
}
11001103

11011104
actionButton.addEventListener('click', () => {
1105+
feedback.unlock();
11021106
canvas.focus({ preventScroll: true });
11031107
performAction();
11041108
});

src/feedback.js

Lines changed: 16 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -40,12 +40,24 @@
4040
let context = null;
4141
let muted = readMuted(storage);
4242

43+
function unlock() {
44+
if (muted || !AudioContextClass) return false;
45+
try {
46+
context ||= new AudioContextClass();
47+
if (context.state === 'suspended') {
48+
const resume = context.resume();
49+
if (resume && typeof resume.catch === 'function') resume.catch(() => {});
50+
}
51+
return true;
52+
} catch {
53+
return false;
54+
}
55+
}
56+
4357
function play(name) {
4458
const cue = CUES[name];
45-
if (muted || !cue || !AudioContextClass) return false;
59+
if (!cue || !unlock()) return false;
4660
try {
47-
context ||= new AudioContextClass();
48-
if (context.state === 'suspended') context.resume();
4961
const start = context.currentTime;
5062
for (const [frequency, offset] of cue) {
5163
const oscillator = context.createOscillator();
@@ -77,6 +89,7 @@
7789
}
7890

7991
return Object.freeze({
92+
unlock,
8093
play,
8194
haptic,
8295
isMuted: () => muted,

tests/feedback.test.cjs

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,3 +29,21 @@ test('feedback is a safe no-op when browser APIs are unavailable', () => {
2929
assert.equal(controller.toggleMuted(), true);
3030
assert.equal(readMuted(storage), true);
3131
});
32+
33+
test('audio unlock resumes a suspended context during input', () => {
34+
let resumeCalls = 0;
35+
class AudioContext {
36+
constructor() {
37+
this.state = 'suspended';
38+
}
39+
40+
resume() {
41+
resumeCalls++;
42+
this.state = 'running';
43+
return Promise.resolve();
44+
}
45+
}
46+
const controller = createFeedbackController({ AudioContext }, memoryStorage());
47+
assert.equal(controller.unlock(), true);
48+
assert.equal(resumeCalls, 1);
49+
});

0 commit comments

Comments
 (0)