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.
<h1>, sensible <h2>/<h3> hierarchy, unique title + description, canonical, Open Graph + Twitter cards, robots, and EducationalOrganization + WebSite JSON-LD. keywords meta has already been dropped from output (good).alt, 12/13 use loading= (lazy). Nicely done.html and body (recent fix) — good.preconnect to font/CDN origins is in place. <main> landmark present. theme-color, manifest, apple-touch icons present.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).
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 **:
Strict-Transport-Security: max-age=31536000; includeSubDomains; preloadX-Content-Type-Options: nosniffReferrer-Policy: strict-origin-when-cross-originPermissions-Policy: geolocation=(), microphone=(), camera=()X-Frame-Options: SAMEORIGIN (note: the site self-embeds /gear in an iframe, so use SAMEORIGIN, not DENY).'unsafe-inline'. Start with a report-only CSP, then tighten.<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.
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.
<video autoplay loop> with no posterindex.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.
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).
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).
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.
.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.
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.
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.
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).
keywords front matterindex.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.
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.
headers block — high value, low risk; CSP report-only first).defer, lazy-load confetti) — measurable LCP win, low risk.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).