# Scored quizzes (Typeform-style) Build a one-question-at-a-time quiz as a **single HTML file**. Score answers in the browser. Do not invent a server or a quiz engine. Optional save uses the portal form endpoint only. Replace `FORM_ENDPOINT` with the portal submit URL from the HTML skill (`https://myformconnect.io/f/YOUR_FORM_UUID` or `https://myformcapture.com/f/YOUR_FORM_UUID`). ## Rules - One self-contained `index.html` (HTML + CSS + vanilla JS). No frameworks. No required CDNs. Google Fonts are optional. - Show **one question at a time** with progress: `Question 2 of 10`. - Multiple choice or true/false. Do not reveal whether the answer was correct until the results screen. - Keep every question in the DOM (hidden), like a multi-step form, so Back/Next still work. - Score only at the end. - **Retry** resets answers and returns to question 1 without reloading a server. - Workplace-safe copy. No stereotypes, slurs, or punching-down jokes. ## Results screen After the last question: 1. Count correct answers from a client-side answer key (in JS, not printed under each question). 2. **Celebrate at 7+ out of 10, or 70%+ on any other length** (4/5, 11/15, 14/20). Show a confetti burst (CSS/JS only — no libraries, no CDNs) and a short celebration headline. Perfect scores can use a bigger burst. 3. Below that threshold: “Better luck next time” (kind, not mocking). No confetti. 4. Actions: - **Show results** — show score and a per-question recap. No network request. Still run confetti if the threshold is met. - **Save and show results** — same recap and the same celebration, plus optional POST to the form backend. - **Retry** — resets and returns to question 1. Example threshold: `const celebrate = score >= Math.ceil(total * 0.7);` which is 7 of 10. Minimal confetti (call once on the results screen when `celebrate` is true): ```javascript function burstConfetti() { const layer = document.createElement("div"); layer.setAttribute("aria-hidden", "true"); layer.style.cssText = "position:fixed;inset:0;pointer-events:none;overflow:hidden;z-index:9999"; document.body.appendChild(layer); const colors = ["#2563eb", "#f59e0b", "#10b981", "#ef4444", "#8b5cf6"]; for (let i = 0; i < 80; i++) { const p = document.createElement("span"); const size = 6 + Math.random() * 8; p.style.cssText = "position:absolute;top:-10px;width:" + size + "px;height:" + (size * 0.6) + "px;background:" + colors[i % colors.length] + ";left:" + Math.random() * 100 + "%;transform:rotate(" + Math.random() * 360 + "deg);animation:quiz-confetti 1.6s ease-out forwards;animation-delay:" + (Math.random() * 0.4) + "s"; layer.appendChild(p); } if (!document.getElementById("quiz-confetti-style")) { const s = document.createElement("style"); s.id = "quiz-confetti-style"; s.textContent = "@keyframes quiz-confetti{to{transform:translateY(110vh) rotate(540deg);opacity:0}}"; document.head.appendChild(s); } setTimeout(function () { layer.remove(); }, 2200); } ``` ## Optional save (MyFormConnect / MyFormCapture) If `YOUR_FORM_UUID` is still the placeholder string, skip the POST and still show results. Otherwise POST with `FormData` (do not set `Content-Type`): - `player_name` (optional text field on the results screen) - `score` (number correct) - `total` (question count) - `answers` (JSON string of `{ question, selected, correct }`) ```javascript const PLACEHOLDER = 'YOUR_FORM_UUID'; const endpoint = 'FORM_ENDPOINT'; // includes the UUID async function saveResults(payload) { if (endpoint.includes(PLACEHOLDER)) return { saved: false }; const body = new FormData(); body.append('player_name', payload.player_name || 'Anonymous'); body.append('score', String(payload.score)); body.append('total', String(payload.total)); body.append('answers', JSON.stringify(payload.answers)); const res = await fetch(endpoint, { method: 'POST', body }); if (!res.ok) throw new Error('Save failed'); return { saved: true }; } ``` Do not POST on every question. Save is one request at the end, and only when the player chooses **Save and show results**. ## Download as a single HTML file Include a **Download quiz (HTML)** control (results screen is fine; a persistent footer control is also OK). It must save the current page as one `.html` file with no server: ```javascript function downloadQuizHtml() { const html = "\n" + document.documentElement.outerHTML; const blob = new Blob([html], { type: "text/html" }); const url = URL.createObjectURL(blob); const a = document.createElement("a"); a.href = url; a.download = "funny-trivia-quiz.html"; a.click(); URL.revokeObjectURL(url); } ``` ## Footer Small footer: **Powered by MyFormConnect** (or MyFormCapture on that portal). The brand name is a link to the portal home URL, including `?ref=trivia-quiz`. Do not use “Generated from a prompt.” ## Related - [Multi-step forms](./multi-step-forms.md) — show/hide steps, submit once - [Field naming](./field-naming.md)