← back to Robet Site
overnight loop item (b): hamburger focus-trap + ARIA + verified hero contrast
672fd87763ce1bf957fc3825cac72bbb108671c3 · 2026-06-29 17:37:02 -0700 · Steve
Files touched
M public/app.jsM public/index.html
Diff
commit 672fd87763ce1bf957fc3825cac72bbb108671c3
Author: Steve <steve@designerwallcoverings.com>
Date: Mon Jun 29 17:37:02 2026 -0700
overnight loop item (b): hamburger focus-trap + ARIA + verified hero contrast
---
public/app.js | 27 ++++++++++++++++++++++-----
public/index.html | 2 +-
2 files changed, 23 insertions(+), 6 deletions(-)
diff --git a/public/app.js b/public/app.js
index ea35ff2..fb8f942 100644
--- a/public/app.js
+++ b/public/app.js
@@ -26,13 +26,30 @@
row('Address', c.address) +
row('Hours', c.hours);
- // ── hamburger ──
- const panel = $('#navpanel'), burger = $('#hamburger');
- const open = (v) => { panel.classList.toggle('open', v); burger.setAttribute('aria-expanded', v); };
+ // ── hamburger (with focus trap for a11y) ──
+ const panel = $('#navpanel'), burger = $('#hamburger'), navClose = $('#navclose');
+ const focusables = () => [...panel.querySelectorAll('a, button')].filter((el) => el.offsetParent !== null);
+ const open = (v) => {
+ panel.classList.toggle('open', v);
+ burger.setAttribute('aria-expanded', String(v));
+ panel.setAttribute('aria-hidden', String(!v));
+ if (v) { navClose.focus(); } // move focus into the dialog
+ else { burger.focus(); } // restore focus to the trigger on close
+ };
+ panel.setAttribute('aria-hidden', 'true');
burger.addEventListener('click', () => open(true));
- $('#navclose').addEventListener('click', () => open(false));
+ navClose.addEventListener('click', () => open(false));
panel.querySelectorAll('a').forEach((a) => a.addEventListener('click', () => open(false)));
- document.addEventListener('keydown', (e) => { if (e.key === 'Escape') open(false); });
+ document.addEventListener('keydown', (e) => {
+ if (!panel.classList.contains('open')) return;
+ if (e.key === 'Escape') { open(false); return; }
+ if (e.key === 'Tab') { // cycle focus within the open dialog
+ const f = focusables(); if (!f.length) return;
+ const first = f[0], last = f[f.length - 1];
+ if (e.shiftKey && document.activeElement === first) { e.preventDefault(); last.focus(); }
+ else if (!e.shiftKey && document.activeElement === last) { e.preventDefault(); first.focus(); }
+ }
+ });
// ── Smart Scheduling embed ──
const sched = cfg.scheduling || {};
diff --git a/public/index.html b/public/index.html
index 081985c..5bf707f 100644
--- a/public/index.html
+++ b/public/index.html
@@ -11,7 +11,7 @@
<!-- top bar: logo UL · hamburger UR -->
<header class="topbar">
<a class="logo" id="logo" href="#top">ROBERT</a>
- <button class="hamburger" id="hamburger" aria-label="Open menu" aria-expanded="false">
+ <button class="hamburger" id="hamburger" aria-label="Open menu" aria-expanded="false" aria-controls="navpanel" aria-haspopup="dialog">
<span></span><span></span><span></span>
</button>
</header>
← 56165e8 overnight loop: item (a) cross-browser smoke — chromium clea
·
back to Robet Site
·
loop ledger: item (b) done + deployed 9085abc →