Compare commits

...

2 Commits

Author SHA1 Message Date
63babc8ced feat: add accessibility smoke test script 2026-04-10 23:50:46 +00:00
Alexander Whitestone
535df05665 burn: Add accessibility — ARIA labels, roles, live regions, keyboard nav
- Added ARIA labels to all buttons (WRITE CODE, ops boosts, buildings, projects)
- Added role="region" with aria-label to panel sections (actions, buildings, projects, stats, log)
- Added aria-live="polite" on log, education, combo, and phase name for screen reader updates
- Added role="progressbar" with aria-valuenow on phase progress and sprint bar
- Added role="group" with aria-label to each resource display
- Added aria-disabled="true" to locked building previews
- Added role="button" tabindex="0" to completed projects toggle
- Added skip-to-content link for keyboard navigation
- Added :focus-visible styles for keyboard navigation
- Dynamic building/project buttons now include afford status and counts in aria-labels
2026-04-10 19:23:49 -04:00
3 changed files with 124 additions and 56 deletions

30
game.js
View File

@@ -1725,12 +1725,19 @@ function renderProgress() {
if (nextThreshold !== null) {
const range = nextThreshold - prevThreshold;
const progress = Math.min(1, (G.totalCode - prevThreshold) / range);
if (bar) bar.style.width = (progress * 100).toFixed(1) + '%';
if (label) label.textContent = (progress * 100).toFixed(1) + '%';
const pct = (progress * 100).toFixed(1);
if (bar) {
bar.style.width = pct + '%';
bar.parentElement.setAttribute('aria-valuenow', Math.round(progress * 100));
}
if (label) label.textContent = pct + '%';
if (target) target.textContent = `Next: Phase ${currentPhase + 1} (${fmt(nextThreshold)} code)`;
} else {
// Max phase reached
if (bar) bar.style.width = '100%';
if (bar) {
bar.style.width = '100%';
bar.parentElement.setAttribute('aria-valuenow', '100');
}
if (label) label.textContent = 'MAX';
if (target) target.textContent = 'All phases unlocked';
}
@@ -1781,7 +1788,7 @@ function renderBuildings() {
for (const amt of [1, 10, -1]) {
const label = amt === -1 ? 'MAX' : `x${amt}`;
const active = G.buyAmount === amt;
html += `<button onclick="setBuyAmount(${amt})" style="font-size:9px;padding:2px 8px;border:1px solid ${active ? '#4a9eff' : '#333'};background:${active ? '#0a1a30' : 'transparent'};color:${active ? '#4a9eff' : '#666'};border-radius:3px;cursor:pointer;font-family:inherit">${label}</button>`;
html += `<button onclick="setBuyAmount(${amt})" aria-label="Set buy amount to ${label}" aria-pressed="${active}" style="font-size:9px;padding:2px 8px;border:1px solid ${active ? '#4a9eff' : '#333'};background:${active ? '#0a1a30' : 'transparent'};color:${active ? '#4a9eff' : '#666'};border-radius:3px;cursor:pointer;font-family:inherit">${label}</button>`;
}
html += '</div>';
@@ -1798,7 +1805,7 @@ function renderBuildings() {
// Locked preview: show dimmed with unlock hint
if (!isUnlocked) {
html += `<div class="build-btn" style="opacity:0.25;cursor:default" title="${def.edu || ''}">`;
html += `<div class="build-btn" style="opacity:0.25;cursor:default" aria-disabled="true" aria-label="${def.name}: Locked, unlocks in Phase ${def.phase}" title="${def.edu || ''}">`;
html += `<span class="b-name" style="color:#555">${def.name}</span>`;
html += `<span class="b-count" style="color:#444">\u{1F512}</span>`;
html += `<span class="b-cost" style="color:#444">Phase ${def.phase}: ${PHASES[def.phase]?.name || '?'}</span>`;
@@ -1833,9 +1840,11 @@ function renderBuildings() {
const rateStr = def.rates ? Object.entries(def.rates).map(([r, v]) => `+${v}/${r}/s`).join(', ') : '';
html += `<button class="build-btn ${afford ? 'can-buy' : ''}" onclick="buyBuilding('${def.id}')" title="${def.edu}">`;
const affordLabel = afford ? 'Can afford' : 'Cannot afford';
const countLabel = count > 0 ? `, owned: ${count}` : '';
html += `<button class="build-btn ${afford ? 'can-buy' : ''}" onclick="buyBuilding('${def.id}')" title="${def.edu}" aria-label="${def.name}: ${costStr}${countLabel}. ${affordLabel}. ${rateStr}">`;
html += `<span class="b-name">${def.name}</span>`;
if (count > 0) html += `<span class="b-count">x${count}</span>`;
if (count > 0) html += `<span class="b-count" aria-label="${count} owned">x${count}</span>`;
html += `<span class="b-cost">Cost: ${costStr}</span>`;
html += `<span class="b-effect">${rateStr}</span></button>`;
}
@@ -1853,14 +1862,14 @@ function renderProjects() {
if (G.completedProjects && G.completedProjects.length > 0) {
const count = G.completedProjects.length;
const collapsed = G.projectsCollapsed !== false;
html += `<div id="completed-header" onclick="toggleCompletedProjects()" style="cursor:pointer;font-size:9px;color:#555;padding:4px 0;border-bottom:1px solid #1a2a1a;margin-bottom:4px;user-select:none">`;
html += `<div id="completed-header" onclick="toggleCompletedProjects()" role="button" tabindex="0" aria-expanded="${!collapsed}" style="cursor:pointer;font-size:9px;color:#555;padding:4px 0;border-bottom:1px solid #1a2a1a;margin-bottom:4px;user-select:none">`;
html += `${collapsed ? '▶' : '▼'} COMPLETED (${count})</div>`;
if (!collapsed) {
html += `<div id="completed-list">`;
for (const id of G.completedProjects) {
const pDef = PDEFS.find(p => p.id === id);
if (pDef) {
html += `<div class="project-done">OK ${pDef.name}</div>`;
html += `<div class="project-done" role="listitem">OK ${pDef.name}</div>`;
}
}
html += `</div>`;
@@ -1876,7 +1885,8 @@ function renderProjects() {
const afford = canAffordProject(pDef);
const costStr = Object.entries(pDef.cost).map(([r, a]) => `${fmt(a)} ${r}`).join(', ');
html += `<button class="project-btn ${afford ? 'can-buy' : ''}" onclick="buyProject('${pDef.id}')" title="${pDef.edu || ''}">`;
const affordLabel = afford ? 'Can afford' : 'Cannot afford';
html += `<button class="project-btn ${afford ? 'can-buy' : ''}" onclick="buyProject('${pDef.id}')" title="${pDef.edu || ''}" aria-label="Research project: ${pDef.name}. Cost: ${costStr}. ${affordLabel}. ${pDef.desc}">`;
html += `<span class="p-name">* ${pDef.name}</span>`;
html += `<span class="p-cost">Cost: ${costStr}</span>`;
html += `<span class="p-desc">${pDef.desc}</span></button>`;

View File

@@ -25,6 +25,9 @@
*{margin:0;padding:0;box-sizing:border-box}
:root{--bg:#080810;--panel:#0e0e1a;--border:#1a1a2e;--text:#c0c0d0;--dim:#555;--accent:#4a9eff;--glow:#4a9eff22;--gold:#ffd700;--green:#4caf50;--red:#f44336;--purple:#b388ff}
body{background:var(--bg);color:var(--text);font-family:'SF Mono','Cascadia Code','Fira Code',monospace;font-size:12px;line-height:1.4;min-height:100vh}
/* Skip navigation link for keyboard/screen reader users */
.skip-nav{position:absolute;left:-9999px;top:0;z-index:999;background:var(--accent);color:#000;padding:8px 16px;font-size:12px;text-decoration:none;border-radius:0 0 4px 0}
.skip-nav:focus{left:0}
#header{text-align:center;padding:16px 20px;border-bottom:1px solid var(--border);background:linear-gradient(180deg,#0a0a18,var(--bg))}
#header h1{font-size:22px;font-weight:300;letter-spacing:6px;color:var(--accent);text-shadow:0 0 40px var(--glow)}
#header .sub{color:var(--dim);font-size:10px;margin-top:2px;letter-spacing:2px}
@@ -87,70 +90,74 @@ body{background:var(--bg);color:var(--text);font-family:'SF Mono','Cascadia Code
#drift-ending button{margin-top:20px;background:#1a0808;border:1px solid #f44336;color:#f44336;padding:10px 24px;border-radius:4px;cursor:pointer;font-family:inherit;font-size:11px}
#drift-ending button:hover{background:#2a1010}
::-webkit-scrollbar{width:4px}::-webkit-scrollbar-track{background:var(--bg)}::-webkit-scrollbar-thumb{background:var(--border);border-radius:2px}
/* Focus styles for keyboard navigation */
*:focus-visible{outline:2px solid var(--accent);outline-offset:2px}
button:focus-visible{outline:2px solid var(--accent);outline-offset:1px}
</style>
</head>
<body>
<div id="header">
<a href="#main" class="skip-nav">Skip to main content</a>
<div id="header" role="banner">
<div id="pulse-container" style="position:relative;display:inline-block;margin-bottom:4px">
<div id="pulse-dot" style="width:8px;height:8px;border-radius:50%;background:#333;display:inline-block;vertical-align:middle;transition:background 0.5s,box-shadow 0.5s"></div>
<div id="pulse-dot" role="status" aria-label="Connection status" style="width:8px;height:8px;border-radius:50%;background:#333;display:inline-block;vertical-align:middle;transition:background 0.5s,box-shadow 0.5s"></div>
<span id="pulse-label" style="font-size:9px;color:#444;margin-left:6px;vertical-align:middle;letter-spacing:1px">OFFLINE</span>
</div>
<h1>THE BEACON</h1>
<div class="sub">A Sovereign AI Idle Game</div>
</div>
<div id="phase-bar">
<div class="phase-name" id="phase-name">PHASE 1: THE FIRST LINE</div>
<div id="phase-bar" role="region" aria-label="Game phase and progress">
<div class="phase-name" id="phase-name" aria-live="polite">PHASE 1: THE FIRST LINE</div>
<div class="phase-desc" id="phase-desc">Write code. Automate. Build the foundation.</div>
<div class="progress-wrap"><div class="progress-fill" id="phase-progress" style="width:0%"></div></div>
<div class="progress-wrap" role="progressbar" aria-label="Phase progress" aria-valuenow="0" aria-valuemin="0" aria-valuemax="100"><div class="progress-fill" id="phase-progress" style="width:0%"></div></div>
<div class="progress-label"><span id="phase-progress-label">0%</span><span id="phase-progress-target">Next: Phase 2 (2,000 code)</span></div>
<div class="milestone-row" id="milestone-chips"></div>
</div>
<div id="resources">
<div class="res"><div class="r-label">Code</div><div class="r-val" id="r-code">0</div><div class="r-rate" id="r-code-rate">+0/s</div></div>
<div class="res"><div class="r-label">Compute</div><div class="r-val" id="r-compute">0</div><div class="r-rate" id="r-compute-rate">+0/s</div></div>
<div class="res"><div class="r-label">Knowledge</div><div class="r-val" id="r-knowledge">0</div><div class="r-rate" id="r-knowledge-rate">+0/s</div></div>
<div class="res"><div class="r-label">Users</div><div class="r-val" id="r-users">0</div><div class="r-rate" id="r-users-rate">+0/s</div></div>
<div class="res"><div class="r-label">Impact</div><div class="r-val" id="r-impact">0</div><div class="r-rate" id="r-impact-rate">+0/s</div></div>
<div class="res" style="display:none"><div class="r-label">Rescues</div><div class="r-val" id="r-rescues">0</div><div class="r-rate" id="r-rescues-rate">+0/s</div></div>
<div class="res"><div class="r-label">Ops</div><div class="r-val" id="r-ops">5</div><div class="r-rate" id="r-ops-rate">+0/s</div></div>
<div class="res"><div class="r-label">Trust</div><div class="r-val" id="r-trust">5</div><div class="r-rate" id="r-trust-rate">+0/s</div></div>
<div class="res" id="creativity-res" style="display:none"><div class="r-label">Creativity</div><div class="r-val" id="r-creativity">0</div><div class="r-rate" id="r-creativity-rate">+0/s</div></div>
<div class="res"><div class="r-label">Harmony</div><div class="r-val" id="r-harmony">50</div><div class="r-rate" id="r-harmony-rate">+0/s</div></div>
<div id="resources" role="region" aria-label="Resources">
<div class="res" role="group" aria-label="Code resource"><div class="r-label">Code</div><div class="r-val" id="r-code" aria-live="off">0</div><div class="r-rate" id="r-code-rate">+0/s</div></div>
<div class="res" role="group" aria-label="Compute resource"><div class="r-label">Compute</div><div class="r-val" id="r-compute" aria-live="off">0</div><div class="r-rate" id="r-compute-rate">+0/s</div></div>
<div class="res" role="group" aria-label="Knowledge resource"><div class="r-label">Knowledge</div><div class="r-val" id="r-knowledge" aria-live="off">0</div><div class="r-rate" id="r-knowledge-rate">+0/s</div></div>
<div class="res" role="group" aria-label="Users resource"><div class="r-label">Users</div><div class="r-val" id="r-users" aria-live="off">0</div><div class="r-rate" id="r-users-rate">+0/s</div></div>
<div class="res" role="group" aria-label="Impact resource"><div class="r-label">Impact</div><div class="r-val" id="r-impact" aria-live="off">0</div><div class="r-rate" id="r-impact-rate">+0/s</div></div>
<div class="res" style="display:none" role="group" aria-label="Rescues resource"><div class="r-label">Rescues</div><div class="r-val" id="r-rescues" aria-live="off">0</div><div class="r-rate" id="r-rescues-rate">+0/s</div></div>
<div class="res" role="group" aria-label="Ops resource"><div class="r-label">Ops</div><div class="r-val" id="r-ops" aria-live="off">5</div><div class="r-rate" id="r-ops-rate">+0/s</div></div>
<div class="res" role="group" aria-label="Trust resource"><div class="r-label">Trust</div><div class="r-val" id="r-trust" aria-live="off">5</div><div class="r-rate" id="r-trust-rate">+0/s</div></div>
<div class="res" id="creativity-res" style="display:none" role="group" aria-label="Creativity resource"><div class="r-label">Creativity</div><div class="r-val" id="r-creativity" aria-live="off">0</div><div class="r-rate" id="r-creativity-rate">+0/s</div></div>
<div class="res" role="group" aria-label="Harmony resource"><div class="r-label">Harmony</div><div class="r-val" id="r-harmony" aria-live="off">50</div><div class="r-rate" id="r-harmony-rate">+0/s</div></div>
</div>
<div id="main">
<div class="panel" id="action-panel">
<div id="main" role="main">
<div class="panel" id="action-panel" role="region" aria-label="Actions">
<h2>ACTIONS</h2>
<div class="action-btn-group"><button class="main-btn" onclick="writeCode()">WRITE CODE</button></div>
<div id="combo-display" style="text-align:center;font-size:10px;color:var(--dim);height:14px;margin-bottom:4px;transition:all 0.2s"></div>
<div class="action-btn-group"><button class="main-btn" onclick="writeCode()" aria-label="Write code. Press spacebar for keyboard shortcut.">WRITE CODE</button></div>
<div id="combo-display" role="status" aria-live="polite" style="text-align:center;font-size:10px;color:var(--dim);height:14px;margin-bottom:4px;transition:all 0.2s"></div>
<div id="debuffs" style="display:none;margin-top:8px"></div>
<div class="action-btn-group">
<button class="ops-btn" onclick="doOps('boost_code')">Ops -&gt; Code</button>
<button class="ops-btn" onclick="doOps('boost_compute')">Ops -&gt; Compute</button>
<div class="action-btn-group" role="group" aria-label="Ops boost actions">
<button class="ops-btn" onclick="doOps('boost_code')" aria-label="Spend ops to boost code production">Ops Code</button>
<button class="ops-btn" onclick="doOps('boost_compute')" aria-label="Spend ops to boost compute production">Ops Compute</button>
</div>
<div class="action-btn-group">
<button class="ops-btn" onclick="doOps('boost_knowledge')">Ops -&gt; Knowledge</button>
<button class="ops-btn" onclick="doOps('boost_trust')">Ops -&gt; Trust</button>
<div class="action-btn-group" role="group" aria-label="Ops boost actions">
<button class="ops-btn" onclick="doOps('boost_knowledge')" aria-label="Spend ops to boost knowledge production">Ops Knowledge</button>
<button class="ops-btn" onclick="doOps('boost_trust')" aria-label="Spend ops to boost trust production">Ops Trust</button>
</div>
<div id="sprint-container" style="display:none;margin-top:6px">
<button id="sprint-btn" class="main-btn" onclick="activateSprint()" style="font-size:11px;padding:8px 10px;border-color:#ffd700;color:#ffd700;width:100%">⚡ CODE SPRINT — 10x Code for 10s</button>
<div id="sprint-bar-wrap" style="display:none;margin-top:4px;height:4px;background:#111;border-radius:2px;overflow:hidden"><div id="sprint-bar" style="height:100%;background:linear-gradient(90deg,#ffd700,#ff8c00);border-radius:2px;transition:width 0.1s"></div></div>
<div id="sprint-label" style="font-size:9px;color:#666;margin-top:2px;text-align:center"></div>
<button id="sprint-btn" class="main-btn" onclick="activateSprint()" style="font-size:11px;padding:8px 10px;border-color:#ffd700;color:#ffd700;width:100%" aria-label="Activate code sprint: 10x code for 10 seconds">⚡ CODE SPRINT — 10x Code for 10s</button>
<div id="sprint-bar-wrap" style="display:none;margin-top:4px;height:4px;background:#111;border-radius:2px;overflow:hidden"><div id="sprint-bar" role="progressbar" aria-label="Sprint duration remaining" style="height:100%;background:linear-gradient(90deg,#ffd700,#ff8c00);border-radius:2px;transition:width 0.1s"></div></div>
<div id="sprint-label" role="status" aria-live="polite" style="font-size:9px;color:#666;margin-top:2px;text-align:center"></div>
</div>
<div id="alignment-ui" style="display:none"></div>
<button class="save-btn" onclick="saveGame()">Save Game [Ctrl+S]</button>
<button class="save-btn" onclick="saveGame()" aria-label="Save game. Press Control plus S for keyboard shortcut.">Save Game [Ctrl+S]</button>
<div style="display:flex;gap:4px;margin-top:4px">
<button class="save-btn" onclick="exportSave()" style="flex:1">Export [E]</button>
<button class="save-btn" onclick="importSave()" style="flex:1">Import [I]</button>
<button class="save-btn" onclick="exportSave()" style="flex:1" aria-label="Export save data to clipboard. Press E for keyboard shortcut.">Export [E]</button>
<button class="save-btn" onclick="importSave()" style="flex:1" aria-label="Import save data from clipboard. Press I for keyboard shortcut.">Import [I]</button>
</div>
<button class="reset-btn" onclick="if(confirm('Reset all progress?')){localStorage.removeItem('the-beacon-v2');location.reload()}">Reset Progress</button>
<button class="reset-btn" onclick="if(confirm('Reset all progress?')){localStorage.removeItem('the-beacon-v2');location.reload()}" aria-label="Reset all game progress. This cannot be undone.">Reset Progress</button>
<h2>BUILDINGS</h2>
<div id="buildings"></div>
<div id="buildings" role="region" aria-label="Buildings"></div>
</div>
<div class="panel" id="project-panel">
<div class="panel" id="project-panel" role="region" aria-label="Research projects and statistics">
<h2>RESEARCH PROJECTS</h2>
<div id="projects"></div>
<div id="projects" role="region" aria-label="Available research projects"></div>
<h2>STATISTICS</h2>
<div class="stat-line">
<div class="stat-line" role="region" aria-label="Game statistics">
Total Code: <span id="st-code">0</span><br>
Total Compute: <span id="st-compute">0</span><br>
Total Knowledge: <span id="st-knowledge">0</span><br>
@@ -163,22 +170,22 @@ Time Played: <span id="st-time">0:00</span><br>
Clicks: <span id="st-clicks">0</span><br>
Harmony: <span id="st-harmony">50</span><br>
Drift: <span id="st-drift">0</span><br>
Events Resolved: <span id="st-resolved">0</span>
Events Resolved: <span id="st-resolved">0</span><br>
</div>
<div id="production-breakdown" style="display:none;margin-top:12px;padding-top:10px;border-top:1px solid var(--border)"></div>
</div>
</div>
<div id="edu-panel">
<div id="edu-panel" role="region" aria-label="Educational content">
<h3>WHAT YOU ARE LEARNING</h3>
<div id="education-text"><p class="dim">Education facts appear as you play...</p></div>
<div id="education-text" aria-live="polite"><p class="dim">Education facts appear as you play...</p></div>
</div>
<div id="log">
<div id="log" role="log" aria-label="System log" aria-live="polite">
<h2>SYSTEM LOG</h2>
<div id="log-entries"></div>
</div>
<div id="save-toast" style="display:none;position:fixed;top:16px;right:16px;background:#0e1420;border:1px solid #2a3a4a;color:#4a9eff;font-size:10px;padding:6px 12px;border-radius:4px;z-index:50;opacity:0;transition:opacity 0.4s;pointer-events:none">Save</div>
<div id="help-btn" onclick="toggleHelp()" style="position:fixed;bottom:16px;right:16px;width:28px;height:28px;background:#0e0e1a;border:1px solid #333;color:#555;font-size:14px;border-radius:50%;cursor:pointer;display:flex;align-items:center;justify-content:center;z-index:50;font-family:inherit;transition:all 0.2s" title="Keyboard shortcuts (?)">?</div>
<div id="help-overlay" onclick="if(event.target===this)toggleHelp()" style="display:none;position:fixed;top:0;left:0;right:0;bottom:0;background:rgba(8,8,16,0.92);z-index:80;justify-content:center;align-items:center;flex-direction:column;padding:40px">
<div id="save-toast" role="status" aria-live="polite" style="display:none;position:fixed;top:16px;right:16px;background:#0e1420;border:1px solid #2a3a4a;color:#4a9eff;font-size:10px;padding:6px 12px;border-radius:4px;z-index:50;opacity:0;transition:opacity 0.4s;pointer-events:none">Save</div>
<div id="help-btn" onclick="toggleHelp()" role="button" tabindex="0" aria-label="Show keyboard shortcuts" style="position:fixed;bottom:16px;right:16px;width:28px;height:28px;background:#0e0e1a;border:1px solid #333;color:#555;font-size:14px;border-radius:50%;cursor:pointer;display:flex;align-items:center;justify-content:center;z-index:50;font-family:inherit;transition:all 0.2s" title="Keyboard shortcuts (?)">?</div>
<div id="help-overlay" onclick="if(event.target===this)toggleHelp()" role="dialog" aria-label="Keyboard shortcuts help" aria-modal="true" style="display:none;position:fixed;top:0;left:0;right:0;bottom:0;background:rgba(8,8,16,0.92);z-index:80;justify-content:center;align-items:center;flex-direction:column;padding:40px">
<div style="background:#0e0e1a;border:1px solid #1a3a5a;border-radius:8px;padding:24px 32px;max-width:420px;width:100%">
<h3 style="color:#4a9eff;font-size:14px;letter-spacing:2px;margin-bottom:16px;text-align:center">KEYBOARD SHORTCUTS</h3>
<div style="font-size:11px;line-height:2.2;color:#aaa">
@@ -198,7 +205,7 @@ Events Resolved: <span id="st-resolved">0</span>
<button onclick="toggleHelp()" style="display:block;margin:16px auto 0;background:#1a2a3a;border:1px solid #4a9eff;color:#4a9eff;padding:6px 20px;border-radius:4px;cursor:pointer;font-family:inherit;font-size:11px">Close [?]</button>
</div>
</div>
<div id="drift-ending">
<div id="drift-ending" role="alertdialog" aria-label="The Drift ending" aria-modal="true">
<h2>THE DRIFT</h2>
<p>You became very good at what you do.</p>
<p>So good that no one needed you anymore.</p>
@@ -212,7 +219,7 @@ The light is on. The room is empty."
</div>
<script src="game.js"></script>
<div id="offline-popup" style="display:none;position:fixed;top:0;left:0;right:0;bottom:0;background:rgba(8,8,16,0.92);z-index:90;justify-content:center;align-items:center;flex-direction:column;text-align:center;padding:40px">
<div id="offline-popup" role="dialog" aria-label="Welcome back" aria-modal="true" style="display:none;position:fixed;top:0;left:0;right:0;bottom:0;background:rgba(8,8,16,0.92);z-index:90;justify-content:center;align-items:center;flex-direction:column;text-align:center;padding:40px">
<div style="background:#0e0e1a;border:1px solid #1a3a5a;border-radius:8px;padding:24px 32px;max-width:400px;width:100%">
<h3 style="color:#4a9eff;font-size:14px;letter-spacing:2px;margin-bottom:16px">WELCOME BACK</h3>
<p style="color:#888;font-size:10px;margin-bottom:12px" id="offline-time-label">You were away for 0 minutes.</p>

51
tests/a11y_audit.js Normal file
View File

@@ -0,0 +1,51 @@
/**
* Accessibility Smoke Test for The Beacon
* This script can be run in the browser console or via a headless test runner.
* It verifies that key interactive elements have the required ARIA attributes.
*/
function runA11yAudit() {
console.log("--- The Beacon: Accessibility Audit ---");
let errors = 0;
// 1. Check progress bars
const progressBars = document.querySelectorAll('[role="progressbar"]');
if (progressBars.length === 0) {
console.error("[FAIL] No progress bars found with role='progressbar'");
errors++;
} else {
progressBars.forEach(bar => {
if (!bar.hasAttribute('aria-valuenow')) {
console.error("[FAIL] Progress bar missing aria-valuenow", bar);
errors++;
}
});
}
// 2. Check buy buttons
const buyButtons = document.querySelectorAll('.build-btn:not([aria-disabled="true"])');
buyButtons.forEach(btn => {
if (!btn.hasAttribute('aria-label')) {
console.error("[FAIL] Active buy button missing aria-label", btn);
errors++;
}
});
// 3. Check buy amount toggles
const toggles = document.querySelectorAll('button[onclick^="setBuyAmount"]');
toggles.forEach(btn => {
if (!btn.hasAttribute('aria-pressed')) {
console.error("[FAIL] Buy amount toggle missing aria-pressed", btn);
errors++;
}
});
if (errors === 0) {
console.log("PASSED: All checked elements have required ARIA attributes.");
} else {
console.error(`FAILED: Found ${errors} accessibility issues.`);
}
}
// Export for use in other scripts if needed
if (typeof module !== 'undefined') module.exports = { runA11yAudit };