← back to Wallco Ai
wallco.ai · WCAG 2.4.1 skip-to-main link in htmlHeader() — first focusable on every route; #main-content anchor span injected after the nav-panel; visually hidden via translateY until focused
2acf00a2c7a26c88becce019887fec843769ec75 · 2026-05-12 07:01:27 -0700 · SteveStudio2
Files touched
M public/css/site.cssM server.js
Diff
commit 2acf00a2c7a26c88becce019887fec843769ec75
Author: SteveStudio2 <stevestudio2@SteveStacStudio.lan>
Date: Tue May 12 07:01:27 2026 -0700
wallco.ai · WCAG 2.4.1 skip-to-main link in htmlHeader() — first focusable on every route; #main-content anchor span injected after the nav-panel; visually hidden via translateY until focused
---
public/css/site.css | 28 ++++++++++++++++++++++++++++
server.js | 6 ++++--
2 files changed, 32 insertions(+), 2 deletions(-)
diff --git a/public/css/site.css b/public/css/site.css
index 5dd857d..c0d53e9 100644
--- a/public/css/site.css
+++ b/public/css/site.css
@@ -52,6 +52,34 @@ body {
a { color: var(--ink); }
img { max-width: 100%; display: block; }
+/* ── A11Y: SKIP-TO-MAIN LINK (WCAG 2.4.1 bypass blocks) */
+.skip-link {
+ position: fixed;
+ top: 8px;
+ left: 8px;
+ z-index: 9999;
+ background: var(--ink);
+ color: var(--bg);
+ padding: 10px 16px;
+ font: 13px/1 var(--sans);
+ text-decoration: none;
+ border-radius: 4px;
+ transform: translateY(-200%);
+ transition: transform 160ms ease;
+}
+.skip-link:focus,
+.skip-link:focus-visible {
+ transform: translateY(0);
+ outline: 2px solid var(--accent, var(--ink));
+ outline-offset: 2px;
+}
+/* The injected anchor target — scrolled past header on focus jump. */
+#main-content {
+ display: block;
+ scroll-margin-top: 80px;
+ outline: none;
+}
+
/* ── HEADER */
.site-header {
position: fixed;
diff --git a/server.js b/server.js
index 3b97d04..f3fec21 100644
--- a/server.js
+++ b/server.js
@@ -597,7 +597,8 @@ function htmlHeader(active) {
['/spoonflower', 'Spoonflower'],
['/about', 'About'],
];
- return `<header class="site-header">
+ return `<a class="skip-link" href="#main-content">Skip to main content</a>
+<header class="site-header">
<a class="brand" href="/">wallco.ai</a>
<button class="hamburger" id="nav-toggle" aria-controls="nav-panel" aria-expanded="false" aria-label="Open menu">
<span></span><span></span><span></span>
@@ -611,7 +612,8 @@ function htmlHeader(active) {
<svg class="sun-icon" width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><circle cx="12" cy="12" r="5"/><line x1="12" y1="1" x2="12" y2="3"/><line x1="12" y1="21" x2="12" y2="23"/><line x1="4.22" y1="4.22" x2="5.64" y2="5.64"/><line x1="18.36" y1="18.36" x2="19.78" y2="19.78"/><line x1="1" y1="12" x2="3" y2="12"/><line x1="21" y1="12" x2="23" y2="12"/><line x1="4.22" y1="19.78" x2="5.64" y2="18.36"/><line x1="18.36" y1="5.64" x2="19.78" y2="4.22"/></svg>
<svg class="moon-icon" width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M21 12.79A9 9 0 1 1 11.21 3 7 7 0 0 0 21 12.79z"/></svg>
</button>
-</nav>`;
+</nav>
+<span id="main-content" tabindex="-1"></span>`;
}
const FOOTER = `<footer class="site-footer">
← 29adb76 spoonflower — add GET /api/export/spoonflower-batch.csv + 'S
·
back to Wallco Ai
·
wallco.ai · WCAG 2.3.3 prefers-reduced-motion shotgun — neut 450155e →