SSL Quiz
I built SSL Quiz for friends studying for the theoretical exam for a Belgian sport shooter’s licence (“sportschutterslicentie”), which I passed myself years ago. The official prep material is a 196-question PDF with no shuffling and no scoring, so practice usually meant someone reading questions aloud. SSL Quiz replaces that: it’s a single HTML file with the question bank baked in, so it opens from a double-click, works offline, and tracks every attempt locally. I built it over six days in April 2026, so anyone preparing for the same exam can use it.
One file, nothing else. The whole app, with its markup, CSS, JavaScript and all 196 questions, is one .html file that runs straight from file://, no server and no install. A build script inlines everything: it embeds the IBM Plex Sans typeface as base64 @font-face, injects the question bank as a <script type="application/json"> block, and strips every <link> and <script src>. A content-security-policy meta tag (default-src 'none') backs up what the code already does: no fetch, no analytics, no outbound call of any kind. The build enforces its own rules: it greps the source for nine banned constructs (CDN references, innerHTML, eval, and the like) and refuses to emit if the output passes 1 MB. In practice it comes in at 208 KB, comfortably under the 500 KB target.
Question bank, verified twice. The 196 questions come from the official preparation PDF, parsed into a canonical JSON schema (id, text, three options, correctIndex) by an AI assistant, then checked by an author-time validator that rejects anything without exactly three options, a duplicate id, or stray HTML characters. The first pass only spot-checked three questions against the source PDF, which let four transcription errors in the answer key through; a follow-up commit verified all 196 answers against the PDF’s answer-key pages and fixed the four that were wrong before anyone sat the real test on bad data.
Taking a quiz. The setup screen picks how many questions to take and whether feedback shows after each question or only at the end. Question order and each question’s three options are reshuffled with Fisher-Yates every attempt, the quiz only moves forward, and keyboard users can answer with 1/2/3 or A/B/C and advance with Enter. Results show the score plus a per-question review of what was picked against the correct answer.
Resilient by design. Every answer is written to localStorage before the UI advances, so a closed tab or a refresh never loses progress; closing mid-quiz brings up a resume prompt next time. State is schema-versioned and defensively parsed, so a corrupted or outdated blob resets cleanly instead of crashing, and if storage is blocked or full the app keeps working with a quiet inline notice instead of losing the attempt. History is capped at 500 entries. A dark/light theme is read and applied before first paint, so there’s no flash of the wrong theme.
Technologies: HTML5, vanilla JavaScript (ES2020+), CSS custom properties, Node.js build script, IBM Plex Sans, localStorage, Content-Security-Policy, Fisher-Yates shuffling, AI-assisted PDF parsing.
You May Also Like

How This Blog Is Served
The machinery behind the words you're reading: Hugo, an unprivileged nginx, a strict CSP, and a deploy script.

Keeping the Old Addresses Alive
Nobody migrates email in one day. Fetchmail, a receive-only alias domain, and permanent redirects bridge the old world into the new one.

Making Roundcube Not Look Like 2012
Webmail is the face of the platform, so it got a Gmail-shaped skin, real calendar and contacts integration, and some money spent on polish.





