a11y: skip-to-content link (0.72.0)

- keyboard/screen-reader users had to tab through the whole topbar before
  reaching page content (WCAG 2.4.1 Bypass Blocks gap)
- add a skip link as the first focusable element: hidden until focused, slides
  in as a cyan pill, and on activation focuses the visible content container
  (works with the persistent-home structure, and avoids the hash-route conflict
  by focusing programmatically instead of via href)
- verified live: Tab focuses it, it becomes visible, activation focuses content
This commit is contained in:
artheru 2026-06-10 10:30:16 +08:00
parent 696d727e44
commit 0f9d553deb
4 changed files with 15 additions and 3 deletions

View File

@ -4,7 +4,7 @@
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta name="theme-color" content="#0e0a14" />
<meta name="description" content="amerc is the agent mercenary tavern — hire, run, publish, and remotely deliver AI agents across software boundaries. No infrastructure, just skills." />
<meta name="version" content="0.71.0-titles" />
<meta name="version" content="0.72.0-skiplink" />
<link rel="icon" href="/favicon.svg" type="image/svg+xml" />
<link rel="icon" href="/app-192.png" type="image/png" sizes="192x192" />
<link rel="apple-touch-icon" href="/apple-touch-icon.png" />

View File

@ -1,6 +1,6 @@
{
"name": "amerc-site",
"version": "0.71.0-titles",
"version": "0.72.0-skiplink",
"private": true,
"type": "module",
"scripts": {

View File

@ -7,7 +7,7 @@ import Account from './Account.jsx';
import TavernGame from './TavernGame.jsx';
import { api } from './api.js';
const RELEASE = '0.71.0-titles';
const RELEASE = '0.72.0-skiplink';
const NAV = [
{ id: 'home', label: 'Tavern' },
@ -457,8 +457,14 @@ export default function Scene2DApp() {
const auth = useAuth();
const isHome = route === 'home';
useEffect(() => { document.title = ROUTE_TITLES[route] || 'amerc'; }, [route]);
const skipToContent = (e) => {
e.preventDefault();
const target = document.querySelector('.route-enter') || document.querySelector('.route-host');
if (target) { target.setAttribute('tabindex', '-1'); target.focus(); }
};
return (
<main className="px-app td-app">
<a className="skip-link" href="#content" onClick={skipToContent}>Skip to content</a>
<h1 className="sr-only">amerc agent mercenary tavern</h1>
<PixelTopbar route={route} setRoute={setRoute} auth={auth} />
{/* Home stays mounted (hidden off-route) so returning to the tavern is instant

View File

@ -1468,3 +1468,9 @@ button {
.ap-gate-rich h2 { font-family: Georgia, "Times New Roman", serif; font-size: 24px; color: #fff; margin: 0 0 10px; }
.ap-gate-rich p { color: #b6a7d4; font-size: 14px; line-height: 1.65; margin: 0 0 22px; }
.ap-gate-cta { display: flex; gap: 12px; justify-content: center; flex-wrap: wrap; }
/* skip-to-content link — visible only on keyboard focus (a11y, 0.72) */
.skip-link { position: fixed; top: 10px; left: 10px; z-index: 100; background: var(--cyan); color: #06222f;
font-family: Georgia, "Times New Roman", serif; font-weight: 600; font-size: 14px; padding: 9px 16px; border-radius: 9px;
box-shadow: 0 6px 18px rgba(0,0,0,0.4); text-decoration: none; transform: translateY(-160%); transition: transform .16s ease-out; }
.skip-link:focus, .skip-link:focus-visible { transform: none; outline: 2px solid #fff; outline-offset: 2px; }