From 72d24ad911359d8ca0ee2a7aab37dba63b6e5751 Mon Sep 17 00:00:00 2001 From: Michael Sydney Moore Date: Thu, 17 Sep 2026 12:49:36 +0100 Subject: [PATCH] Render the concept list once per page Every page shipped the 35-item concept list twice: once in the desktop sidebar and again inside a details drawer in the mobile header, with CSS hiding whichever one did not apply. Both copies were always in the DOM, so crawlers and screen readers saw two navigation landmarks both labelled "Concepts", and every page carried about 60 words of duplicate boilerplate. The sidebar is now the single home for the list. Below the 700px breakpoint it becomes an off-canvas drawer opened by a button in the mobile header, handled by a small nav.js module because the content security policy disallows inline scripts. The drawer closes on Escape, on a backdrop click, and when a link inside it is followed, and Escape returns focus to the button. The drawer and its backdrop sit at z-index 91 and 90. The site footer is z-index 50, so the earlier values left the footer painting over the overlay and swallowing backdrop clicks. --- scripts/build.mjs | 9 +++++++-- src/main.css | 36 +++++++++++++++++++++--------------- src/nav.js | 36 ++++++++++++++++++++++++++++++++++++ 3 files changed, 64 insertions(+), 17 deletions(-) create mode 100644 src/nav.js diff --git a/scripts/build.mjs b/scripts/build.mjs index e215d7e..b1d7bfd 100644 --- a/scripts/build.mjs +++ b/scripts/build.mjs @@ -43,6 +43,8 @@ const stylesheet = new CleanCSS({level:2, rebase:false}).minify(stylesheetSource if (stylesheet.errors.length) throw new Error(stylesheet.errors.join('\n')); const logo = await readFile(new URL('../src/logo.webp', import.meta.url)); const logoPath = `/assets/logo.${createHash('sha256').update(logo).digest('hex').slice(0,12)}.webp`; +const nav = (await minify(await readFile(new URL('../src/nav.js', import.meta.url), 'utf8'), {module:true})).code; +const navPath = `/assets/nav.${createHash('sha256').update(nav).digest('hex').slice(0,12)}.js`; const aiClient = (await minify(await readFile(new URL('../src/ai-client.js', import.meta.url), 'utf8'), {module:true})).code; const aiClientPath = `/assets/ai-client.${createHash('sha256').update(aiClient).digest('hex').slice(0,12)}.js`; const aiPanelSource = (await readFile(new URL('../src/ai-panel.js', import.meta.url), 'utf8')).replace("'./ai-client.js'", JSON.stringify(aiClientPath)); @@ -175,18 +177,20 @@ function document({ title, description, path, content, current, noindex = false + ${current ? `` : ''} -