a11y: fix Lighthouse-flagged issues — accessibility 93->100 (0.79.0)

ran Lighthouse (local preview): SEO 100, a11y 93->100, best-practices 92
(its one fail was console errors from the backend-less preview — a false
positive; live has 0 console errors). fixed the 3 real a11y flags:
- color-contrast: '.gm-scrollcue' was 4.33:1 (needs 4.5) — lightened the
  'how it works' cue colour and dropped the opacity dimming
- label-in-name: the logo button's aria-label='amerc home' didn't contain its
  visible text — removed it so the accessible name = visible text (+ title=Home)
- skip-link: the href='#content' had no target — gave the content host
  id='content' + tabindex=-1 so it's a valid focusable target
- re-ran Lighthouse: accessibility 100, all three audits pass
This commit is contained in:
artheru 2026-06-10 11:31:00 +08:00
parent dbe3c383e3
commit c5e16205c5
4 changed files with 6 additions and 6 deletions

View File

@ -4,7 +4,7 @@
<meta name="viewport" content="width=device-width, initial-scale=1.0" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta name="theme-color" content="#0e0a14" /> <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="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.78.0-loadscreen" /> <meta name="version" content="0.79.0-a11y2" />
<link rel="icon" href="/favicon.svg" type="image/svg+xml" /> <link rel="icon" href="/favicon.svg" type="image/svg+xml" />
<link rel="icon" href="/app-192.png" type="image/png" sizes="192x192" /> <link rel="icon" href="/app-192.png" type="image/png" sizes="192x192" />
<link rel="apple-touch-icon" href="/apple-touch-icon.png" /> <link rel="apple-touch-icon" href="/apple-touch-icon.png" />

View File

@ -1,6 +1,6 @@
{ {
"name": "amerc-site", "name": "amerc-site",
"version": "0.78.0-loadscreen", "version": "0.79.0-a11y2",
"private": true, "private": true,
"type": "module", "type": "module",
"scripts": { "scripts": {

View File

@ -10,7 +10,7 @@ import { api } from './api.js';
const Menu = ({ size = 20 }) => (<svg width={size} height={size} viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" aria-hidden="true"><line x1="3" y1="6" x2="21" y2="6" /><line x1="3" y1="12" x2="21" y2="12" /><line x1="3" y1="18" x2="21" y2="18" /></svg>); const Menu = ({ size = 20 }) => (<svg width={size} height={size} viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" aria-hidden="true"><line x1="3" y1="6" x2="21" y2="6" /><line x1="3" y1="12" x2="21" y2="12" /><line x1="3" y1="18" x2="21" y2="18" /></svg>);
const X = ({ size = 20 }) => (<svg width={size} height={size} viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" aria-hidden="true"><line x1="18" y1="6" x2="6" y2="18" /><line x1="6" y1="6" x2="18" y2="18" /></svg>); const X = ({ size = 20 }) => (<svg width={size} height={size} viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" aria-hidden="true"><line x1="18" y1="6" x2="6" y2="18" /><line x1="6" y1="6" x2="18" y2="18" /></svg>);
const RELEASE = '0.78.0-loadscreen'; const RELEASE = '0.79.0-a11y2';
const NAV = [ const NAV = [
{ id: 'home', label: 'Tavern' }, { id: 'home', label: 'Tavern' },
@ -68,7 +68,7 @@ function PixelTopbar({ route, setRoute, auth }) {
); );
return ( return (
<header className="px-topbar"> <header className="px-topbar">
<button className="px-logo" onClick={() => go('home')} type="button" aria-label="amerc home"> <button className="px-logo" onClick={() => go('home')} type="button" title="Home">
<span className="px-logo-gem" aria-hidden="true" /><strong>amerc</strong><small>agent mercenary tavern</small> <span className="px-logo-gem" aria-hidden="true" /><strong>amerc</strong><small>agent mercenary tavern</small>
</button> </button>
<button className="px-ver" onClick={() => go('changelog')} type="button" title="What's new — changelog">v{RELEASE}</button> <button className="px-ver" onClick={() => go('changelog')} type="button" title="What's new — changelog">v{RELEASE}</button>
@ -472,7 +472,7 @@ export default function Scene2DApp() {
<PixelTopbar route={route} setRoute={setRoute} auth={auth} /> <PixelTopbar route={route} setRoute={setRoute} auth={auth} />
{/* Home stays mounted (hidden off-route) so returning to the tavern is instant {/* Home stays mounted (hidden off-route) so returning to the tavern is instant
the canvas pauses while hidden, so it costs nothing. */} the canvas pauses while hidden, so it costs nothing. */}
<div className="route-host" style={{ display: isHome ? 'flex' : 'none' }}><Home setRoute={setRoute} auth={auth} /></div> <div id="content" tabIndex={-1} className="route-host" style={{ display: isHome ? 'flex' : 'none' }}><Home setRoute={setRoute} auth={auth} /></div>
{!isHome && <div key={route} className="route-enter"><Scene route={route} setRoute={setRoute} auth={auth} /></div>} {!isHome && <div key={route} className="route-enter"><Scene route={route} setRoute={setRoute} auth={auth} /></div>}
</main> </main>
); );

View File

@ -1148,7 +1148,7 @@ button {
/* 0.30 — landing: scrollable home with "how it works" */ /* 0.30 — landing: scrollable home with "how it works" */
.gm-home { display: block; overflow-y: auto; overflow-x: hidden; padding: 0; scroll-behavior: smooth; } .gm-home { display: block; overflow-y: auto; overflow-x: hidden; padding: 0; scroll-behavior: smooth; }
.gm-hero { min-height: calc(100vh - 58px); display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 10px; padding: 14px 12px; } .gm-hero { min-height: calc(100vh - 58px); display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 10px; padding: 14px 12px; }
.gm-scrollcue { margin-top: 8px; font-family: Georgia, serif; font-size: 12px; color: #9a86b8; cursor: pointer; opacity: 0.8; animation: hf-bob 2.2s ease-in-out infinite; } .gm-scrollcue { margin-top: 8px; font-family: Georgia, serif; font-size: 12px; color: #b8a8d8; cursor: pointer; animation: hf-bob 2.2s ease-in-out infinite; }
.gm-scrollcue:hover { color: #27d8ff; } .gm-scrollcue:hover { color: #27d8ff; }
@keyframes hf-bob { 0%,100% { transform: translateY(0); } 50% { transform: translateY(4px); } } @keyframes hf-bob { 0%,100% { transform: translateY(0); } 50% { transform: translateY(4px); } }
.hf { max-width: 1040px; margin: 0 auto; padding: 50px 22px 30px; font-family: Inter, sans-serif; } .hf { max-width: 1040px; margin: 0 auto; padding: 50px 22px 30px; font-family: Inter, sans-serif; }