Skip to main content

Documentation Index

Fetch the complete documentation index at: https://mintlify.com/ac-unefm/snake-game/llms.txt

Use this file to discover all available pages before exploring further.

Snake Classic Game is designed to be fully operable by keyboard and usable with screen readers. It implements WAI-ARIA 1.2 patterns including live regions, modal dialogs with aria-modal, focus management, and a skip link. Every interactive element meets the WCAG 2.5.5 minimum touch target size of 44×44 px (the help button is explicitly sized to 2.75rem × 2.75rem ≈ 44 px). The very first element in <body> is a visually hidden skip link:
<a href="#game-region" class="skip-link">Saltar al juego</a>
The .skip-link class positions the element off-screen (top: -100%) until it receives focus, at which point it slides into view at the top of the viewport. This allows keyboard and screen-reader users to bypass the header and jump directly to the game canvas region (#game-region) without tabbing through every preceding element.
.skip-link {
  position: absolute;
  top: -100%;
  left: 1rem;
  /* ... */
  transition: top 0.15s;
}
.skip-link:focus { top: 0; outline: 2px solid #fff; outline-offset: 2px; }

ARIA live regions

Dynamic content is surfaced to assistive technologies through dedicated live region elements:
ElementRole / AttributePurpose
#scoreElaria-live="polite", aria-atomic="true"Announces score changes
#levelElaria-live="polite", aria-atomic="true"Announces level changes
#session-barrole="status"Session progress dot indicator
#messagerole="status", aria-live="polite", aria-atomic="true"Game status messages (game over, next attempt prompt)
#game-alertrole="alert", aria-live="assertive", aria-atomic="true"Urgent announcements (level-up, game over, session end)
aria-atomic="true" on score and level elements tells screen readers to announce the entire element content when any part changes, rather than only the changed text node.

announceAlert(txt)

Level-up and game-over events use #game-alert (role="alert", aria-live="assertive") to interrupt the screen reader immediately. However, assistive technologies often ignore a live region update if the new text is identical to the previous content. announceAlert() works around this by clearing the element first, then setting the new text after a 50 ms delay:
function announceAlert(txt) {
  clearTimeout(alertTimer);
  alertEl.textContent = '';
  alertTimer = setTimeout(() => { alertEl.textContent = txt; }, 50);
}
Clearing textContent to '' forces the DOM mutation event to fire even when the same message is announced twice in a row (e.g., two consecutive GAME OVER announcements). The 50 ms gap gives the browser time to process the empty-string mutation before the new text is set. Both #help-overlay and #results-overlay follow the WAI-ARIA dialog pattern:
<div id="help-overlay"
     role="dialog"
     aria-modal="true"
     aria-labelledby="help-title"
     aria-hidden="true">
  <div id="help-panel">
    <h2 id="help-title">Ayuda</h2>
    <!-- ... -->
    <button id="btn-close-help" aria-label="Cerrar ayuda">Cerrar</button>
  </div>
</div>
<div id="results-overlay"
     role="dialog"
     aria-modal="true"
     aria-labelledby="results-title"
     aria-hidden="true">
Key behaviors:
  • aria-hidden="true" when closed — the overlay is present in the DOM but invisible to assistive technologies. The attribute is removed (removeAttribute('aria-hidden')) when the dialog opens and restored (setAttribute('aria-hidden', 'true')) when it closes.
  • aria-labelledby — points to the dialog’s <h2> element, which screen readers announce as the dialog’s accessible name when focus enters it.
  • Focus managementopenHelp() moves focus to btnCloseHelp when the help dialog opens; showSessionResults() moves focus to btnNewSession when the results dialog opens.
  • Focus restorationcloseHelp() returns focus to the canvas (canvas.focus()) so keyboard navigation resumes exactly where the player left off.
  • Focus trap in the help modal — keyboard handling in the keydown listener ignores all game keys while helpOverlay.classList.contains('visible') is true, effectively trapping interaction within the modal until it is dismissed.

Keyboard-only operation

Every interactive element is reachable by Tab in document order. The canvas itself has tabindex="0" so it is a valid focus target and can receive keydown events without requiring a wrapper:
<canvas id="canvas" width="400" height="400"
  role="img" aria-label="Tablero del juego Snake" tabindex="0"></canvas>
Focus indicators use focus-visible to show outlines only during keyboard navigation (not on mouse click):
#canvas:focus, #canvas:focus-visible {
  outline: 2px solid var(--color-primary);
  outline-offset: 4px;
}
Full keyboard shortcut reference:
KeyAction
Move the snake (also starts the game if idle)
Enter / SpaceStart the game or advance to the next attempt
H / ?Open the help dialog
EscapeClose the active modal (help or results)

prefers-reduced-motion

The reducedMotion constant is evaluated once at startup:
const reducedMotion = window.matchMedia('(prefers-reduced-motion: reduce)').matches;
When true:
  • The food pulse animation is replaced with a static radius and fixed shadowBlur.
  • All CSS transition declarations on interactive elements (skip link, session dots, buttons, footer links) are removed via:
@media (prefers-reduced-motion: reduce) {
  .skip-link, .session-dot, #btn-new-session,
  #btn-help, #btn-close-help, .dpad-btn,
  footer a { transition: none; }
}
This ensures the game is comfortable for users who experience motion sickness or vestibular disorders from animated content.

Content Security Policy

The <meta> CSP tag enforces a strict policy:
<meta http-equiv="Content-Security-Policy"
      content="default-src 'none'; style-src 'unsafe-inline'; script-src 'unsafe-inline';" />
default-src 'none' blocks every external resource category — images, fonts, iframes, XHR/fetch requests, and more. Only inline <style> and inline <script> are permitted. This guarantees the game never loads external content, making it safe to serve from any origin (including file://) without risk of third-party resource injection or data exfiltration.

Anti-XSS DOM construction

All dynamic HTML generated at runtime — session results rows, stat grid items, and session labels — is built with document.createElement and textContent assignments. Game-derived data is never written via innerHTML. The only uses of innerHTML in the codebase are two container-clearing assignments (resultsTbody.innerHTML = '' and statsGrid.innerHTML = '') that empty the containers before they are repopulated with safe createElement calls — no untrusted data is involved:
const td = document.createElement('td');
td.textContent = text;   // safe: sets text node, never parsed as HTML
tr.appendChild(td);
const valueEl = document.createElement('span');
valueEl.className = stat.highlight ? 'stat-value highlight' : 'stat-value';
valueEl.textContent = String(stat.value);   // safe: not innerHTML
This pattern completely eliminates the risk of injecting executable markup through score values, level numbers, or any other game data.

Build docs developers (and LLMs) love