From 5a9d71b9afab31422922aba99e7f57c0be88c329 Mon Sep 17 00:00:00 2001 From: Aaqibhafeez Khan Date: Sat, 24 Oct 2026 17:12:01 +0530 Subject: [PATCH 1/3] Trap keyboard focus in pause menu --- public/js/game/main.js | 44 ++++++++++++++++++++++++++++++++++++++---- 1 file changed, 40 insertions(+), 4 deletions(-) diff --git a/public/js/game/main.js b/public/js/game/main.js index 7ab9a6b..5faefa0 100644 --- a/public/js/game/main.js +++ b/public/js/game/main.js @@ -8,9 +8,9 @@ // All game modules are imported with the same ?v= suffix: bump it everywhere when any of // them changes, so browsers never mix a cached old module with a new one. // ============================================ -import * as S from './sim.js?v=8'; -import * as R from './render.js?v=8'; -import * as A from './audio.js?v=8'; +import * as S from './sim.js?v=9'; +import * as R from './render.js?v=9'; +import * as A from './audio.js?v=9'; const $ = (id) => document.getElementById(id); @@ -240,10 +240,16 @@ function boot() { // ============================================ // PAUSE // ============================================ + let pauseReturnFocus = null; + function pause() { if (state !== 'playing') { return; } + pauseReturnFocus = document.activeElement; + if (el.pauseOverlay && el.pauseOverlay.contains(pauseReturnFocus)) { + pauseReturnFocus = null; + } state = 'paused'; A.stopMusic(); el.power.classList.remove('is-visible'); @@ -269,7 +275,37 @@ function boot() { queueRelease(); } A.resumeMusic(); - canvas.focus({ preventScroll: true }); + const returnTarget = pauseReturnFocus; + pauseReturnFocus = null; + if (returnTarget && returnTarget.isConnected && !el.pauseOverlay.contains(returnTarget) && typeof returnTarget.focus === 'function') { + returnTarget.focus({ preventScroll: true }); + } else { + canvas.focus({ preventScroll: true }); + } + } + + if (el.pauseOverlay) { + el.pauseOverlay.addEventListener('keydown', (e) => { + if (e.key !== 'Tab') { + return; + } + const focusable = Array.from(el.pauseOverlay.querySelectorAll( + 'button:not([disabled]), input:not([disabled]):not([type="hidden"]), select:not([disabled]), textarea:not([disabled]), a[href], [tabindex]:not([tabindex="-1"])' + )).filter((node) => !node.hasAttribute('hidden') && node.getAttribute('aria-hidden') !== 'true'); + if (focusable.length === 0) { + e.preventDefault(); + return; + } + const first = focusable[0]; + const last = focusable[focusable.length - 1]; + if (e.shiftKey && (document.activeElement === first || !el.pauseOverlay.contains(document.activeElement))) { + e.preventDefault(); + last.focus({ preventScroll: true }); + } else if (!e.shiftKey && (document.activeElement === last || !el.pauseOverlay.contains(document.activeElement))) { + e.preventDefault(); + first.focus({ preventScroll: true }); + } + }); } function quitToTitle() { From 2ba4b4de8dca0f54f7121c6c46f2f38876418d4c Mon Sep 17 00:00:00 2001 From: Aaqibhafeez Khan Date: Sat, 24 Oct 2026 17:12:05 +0530 Subject: [PATCH 2/3] Bump game asset cache version --- templates/game/index.html.twig | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/templates/game/index.html.twig b/templates/game/index.html.twig index 5f6675c..9dfc5bf 100644 --- a/templates/game/index.html.twig +++ b/templates/game/index.html.twig @@ -283,5 +283,5 @@ }|json_encode|raw }}; {# Bump ?v together with the import suffixes inside js/game/*.js #} - + {% endblock %} From 2956fc21fbf142dd2c7d78884cf98937a6a6ae2c Mon Sep 17 00:00:00 2001 From: Aaqibhafeez Khan Date: Sat, 24 Oct 2026 20:21:39 +0530 Subject: [PATCH 3/3] fix: align renderer cache version --- public/js/game/render.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/public/js/game/render.js b/public/js/game/render.js index edabaff..ecd7082 100644 --- a/public/js/game/render.js +++ b/public/js/game/render.js @@ -13,7 +13,7 @@ import { WORLD_W, VIEW_H, DT, GRAVITY, PLAYER_RADIUS, CAMERA_TOP_FRACTION, MAX_CHARGE_TICKS, ZONES, clamp, lerp, metersFromY, zoneVisual, heightMeters, fallLimitY, pickupBob, calcJumpVelocities, chargePower, -} from './sim.js?v=8'; +} from './sim.js?v=9'; export const ART = 2; // logical px per art pixel const WORLD_ART_W = WORLD_W / ART;