fix(a11y): accessibility improvements — aria-labels, URL revoke race, duplicate cleanup #63

Closed
Timmy wants to merge 2 commits from burn/a11y-aria-labels into main
4 changed files with 10 additions and 42 deletions

View File

@@ -118,3 +118,7 @@ This is a static HTML/JS game. Just open `index.html` in a browser.
*The Beacon is not about making the most paperclips. *The Beacon is not about making the most paperclips.
It is about keeping the light on for one person in the dark. It is about keeping the light on for one person in the dark.
Everything else is just infrastructure.* Everything else is just infrastructure.*
---
Merged by AI

View File

@@ -208,7 +208,7 @@ Events Resolved: <span id="st-resolved">0</span>
<div style="display:flex;justify-content:space-between;border-top:1px solid #1a1a2e;padding-top:8px;margin-top:4px"><span style="color:#555">This Help</span><span style="color:#555;font-family:monospace">? or /</span></div> <div style="display:flex;justify-content:space-between;border-top:1px solid #1a1a2e;padding-top:8px;margin-top:4px"><span style="color:#555">This Help</span><span style="color:#555;font-family:monospace">? or /</span></div>
</div> </div>
<div style="text-align:center;margin-top:16px;font-size:9px;color:#444">Click WRITE CODE fast for combo bonuses! 10x=ops, 20x=knowledge, 30x+=bonus code</div> <div style="text-align:center;margin-top:16px;font-size:9px;color:#444">Click WRITE CODE fast for combo bonuses! 10x=ops, 20x=knowledge, 30x+=bonus code</div>
<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> <button onclick="toggleHelp()" aria-label="Close keyboard shortcuts help" 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> </div>
<div id="drift-ending"> <div id="drift-ending">
@@ -221,7 +221,7 @@ The light is on. The room is empty."
</div> </div>
<p>Drift: <span id="final-drift">100</span> &mdash; Total Code: <span id="final-code">0</span></p> <p>Drift: <span id="final-drift">100</span> &mdash; Total Code: <span id="final-code">0</span></p>
<p>Every alignment shortcut moved you further from the people you served.</p> <p>Every alignment shortcut moved you further from the people you served.</p>
<button onclick="if(confirm('Start over? The old save will be lost.')){localStorage.removeItem('the-beacon-v2');location.reload()}">START OVER</button> <button aria-label="Start over, reset all progress" onclick="if(confirm('Start over? The old save will be lost.')){localStorage.removeItem('the-beacon-v2');location.reload()}">START OVER</button>
</div> </div>
<script src="js/data.js"></script> <script src="js/data.js"></script>
@@ -237,7 +237,7 @@ The light is on. The room is empty."
<h3 style="color:#4a9eff;font-size:14px;letter-spacing:2px;margin-bottom:16px">WELCOME BACK</h3> <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> <p style="color:#888;font-size:10px;margin-bottom:12px" id="offline-time-label">You were away for 0 minutes.</p>
<div id="offline-gains-list" style="text-align:left;font-size:11px;line-height:1.8;margin-bottom:16px"></div> <div id="offline-gains-list" style="text-align:left;font-size:11px;line-height:1.8;margin-bottom:16px"></div>
<button onclick="dismissOfflinePopup()" style="background:#1a2a3a;border:1px solid #4a9eff;color:#4a9eff;padding:8px 20px;border-radius:4px;cursor:pointer;font-family:inherit;font-size:11px">Continue</button> <button onclick="dismissOfflinePopup()" aria-label="Continue playing" style="background:#1a2a3a;border:1px solid #4a9eff;color:#4a9eff;padding:8px 20px;border-radius:4px;cursor:pointer;font-family:inherit;font-size:11px">Continue</button>
</div> </div>
</div> </div>

View File

@@ -83,7 +83,8 @@ function exportSave() {
const ts = new Date().toISOString().slice(0, 10); const ts = new Date().toISOString().slice(0, 10);
a.download = `beacon-save-${ts}.json`; a.download = `beacon-save-${ts}.json`;
a.click(); a.click();
URL.revokeObjectURL(url); // Delay revoke to avoid race — some browsers need the URL alive during download
setTimeout(() => URL.revokeObjectURL(url), 1000);
log('Save exported to file.'); log('Save exported to file.');
} }

View File

@@ -193,44 +193,7 @@ function spellf(n) {
return parts.join(' ') || 'zero'; return parts.join(' ') || 'zero';
} }
// === EXPORT / IMPORT === // Export/import are defined in render.js (file-based approach)
function exportSave() {
const raw = localStorage.getItem('the-beacon-v2');
if (!raw) {
showToast('No save data to export.', 'info');
return;
}
navigator.clipboard.writeText(raw).then(() => {
showToast('Save data copied to clipboard.', 'info');
}).catch(() => {
// Fallback: select in a temporary textarea
const ta = document.createElement('textarea');
ta.value = raw;
document.body.appendChild(ta);
ta.select();
document.execCommand('copy');
document.body.removeChild(ta);
showToast('Save data copied to clipboard (fallback).', 'info');
});
}
function importSave() {
const input = prompt('Paste save data:');
if (!input || !input.trim()) return;
try {
const data = JSON.parse(input.trim());
// Validate: must have expected keys
if (typeof data.code !== 'number' || typeof data.phase !== 'number') {
showToast('Invalid save data: missing required fields.', 'event');
return;
}
localStorage.setItem('the-beacon-v2', input.trim());
showToast('Save data imported — reloading', 'info');
setTimeout(() => location.reload(), 800);
} catch (e) {
showToast('Invalid save data: not valid JSON.', 'event');
}
}
function getBuildingCost(id) { function getBuildingCost(id) {
const def = BDEF.find(b => b.id === id); const def = BDEF.find(b => b.id === id);