index.html — Future-Proofing & Modernization Audit

Date: 2026-06-06 · Scope: index.html (homepage) + its shared dependencies (_includes/layout.html head/scripts, firebase.json headers, CDN libraries). Read-only analysis — no code was changed.

Severity key: HIGH = fix soon (security / accessibility / clear perf), MED = worth scheduling, LOW = polish / nice-to-have.


What's already solid (don't change)


HIGH severity

H1 — Viewport blocks pinch-zoom (accessibility fail)

layout.html:16 sets maximum-scale=1.0, user-scalable=no. This disables pinch-to-zoom, which is a WCAG 2.1 SC 1.4.4 (Resize Text) failure and hurts low-vision users. Fix: <meta name="viewport" content="width=device-width, initial-scale=1.0, viewport-fit=cover"> (drop maximum-scale and user-scalable=no).

H2 — No security headers at all

firebase.json only sets Cache-Control and Access-Control-Allow-Origin — there is no CSP, HSTS, X-Content-Type-Options, Referrer-Policy, Permissions-Policy, or frame protection. Fix: add a headers block in firebase.json for **:

H3 — Render-blocking, always-on third-party scripts in <head>

layout.html:122-126 loads canvas-confetti + 4 Firebase compat scripts synchronously in <head> on every page, with no defer/async. Confetti is only used on celebration moments; Firebase isn't needed for first paint. This delays LCP site-wide. Fix: add defer to the Firebase tags; load canvas-confetti lazily (only when confetti actually fires). Longer term, only load Firebase on pages that use auth/Firestore.


MEDIUM severity

M1 — Firebase SDK is the legacy "compat" build, ~2 years old

9.23.0 compat (firebase-*-compat.js) is the backwards-compat shim, not the modern modular API; current is v11+. Compat ships more JS and isn't tree-shakeable. Fix (staged): bump the version now; plan a migration to the modular SDK (import only getAuth/getFirestore/etc.) to cut bundle size meaningfully. Large effort — schedule it, don't rush.

M2 — Hero <video autoplay loop> with no poster

index.html hero video autoplays with 9 <source> entries and 0 poster. No poster = blank/flash before the video paints (hurts LCP/CLS); autoplaying video is heavy on mobile data. Fix: add a poster image; gate autoplay behind prefers-reduced-motion; consider a static hero image on small screens and only load the video on larger viewports.

M3 — user-select: none on the whole <body>

layout.html:159-166 disables text selection site-wide (inputs are re-enabled). This is an anti-pattern: it doesn't actually protect content, but it does block legitimate copy/paste and hurts usability/accessibility. Fix: remove the global rule, or scope it narrowly (e.g. only quiz/exam containers).

M4 — Inline event handlers everywhere

index.html alone has 7 onclick="…" handlers; the pattern is site-wide. These force a permissive CSP ('unsafe-inline') and are harder to maintain/test. Fix: move to addEventListener with data- attributes. This also unblocks a real CSP (H2).

M5 — No CSS/JS minification or bundling

Eleventy passes HTML/CSS/JS through unminified; pages carry large inline <style>/<script> blocks (index.html is 844 lines; layout.html has several scattered <style> patches with !important). More bytes + harder maintenance. Fix: add a build-time minify step (e.g. eleventy-plugin-bundle or a postcss/terser pass) and consolidate the scattered head <style> patches into one place.

M6 — .pre-animate 1.8s transition (known footgun)

layout.html:177 uses a 1.8s opacity/transform transition; CLAUDE.md already documents this stalling and blanking very long pages. Fix: shorten to ~0.4–0.6s, drive with IntersectionObserver, and honor prefers-reduced-motion: reduce.


LOW severity

L1 — No skip-to-content link

0 skip links found; aria-* usage is sparse (2 on the homepage). Keyboard users can't jump past the nav. Fix: add a visually-hidden "Skip to main content" link as the first focusable element targeting #main; add a visible :focus-visible outline style globally.

L2 — Heavy font/icon payloads from CDN

Full Font Awesome all.min.css (6.5.2) + Montserrat in 6 weights (400–900). Most pages use 2–3 weights and a handful of icons. Fix: trim Montserrat to the weights actually used; subset Font Awesome (kit or self-host only the used icons). Self-hosting also removes a third-party dependency.

L3 — Third-party CDN single-point-of-failure / privacy

Firebase (gstatic), Font Awesome (cdnjs), confetti (jsdelivr), fonts (googleapis), Clarity. If a CDN is down or blocked, features break; Google Fonts/Clarity also have privacy implications. Fix: self-host the critical libraries (fonts, Font Awesome, Firebase) for resilience and privacy; keep consent-gating on GA4/Clarity (already done).

L4 — Dead keywords front matter

index.html front matter still defines keywords: but the head no longer outputs a <meta name="keywords"> (Google ignores it anyway). Fix: delete the keywords line from page front matter for cleanliness.

L5 — Global img { pointer-events: none }

layout.html:175 makes all images non-interactive, which can silently break any image that should be clickable. Fix: scope this to decorative images only.


Suggested order of attack

  1. H1 viewport (one line, removes an a11y failure) and L4 (trivial).
  2. H2 security headers (firebase.json headers block — high value, low risk; CSP report-only first).
  3. H3 defer/lazy scripts (add defer, lazy-load confetti) — measurable LCP win, low risk.
  4. M2 hero poster + reduced-motion, M3 remove global user-select, M6 shorten pre-animate.
  5. M5 minify/bundle build step (maintainability + bytes).
  6. M4 de-inline handlers → then a real CSP (closes the loop with H2).
  7. M1 Firebase modular migration (largest effort; schedule deliberately).
  8. L1/L2/L3 polish (skip link, font/icon trimming, self-host libs).

All items are incremental and can ship one at a time without a rewrite. The homepage's fundamentals (SEO, alt text, lazy images, overflow clamp) are already in good shape — this list is about hardening (security/a11y) and trimming legacy weight (Firebase compat, render-blocking scripts, inline handlers).