[object Object]

← 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

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 →