An outdoor QR-code treasure hunt for kids. Players scan QR codes at physical stations, answer Easter-themed questions (in Czech) read aloud by a bunny voice, and follow clues to the next station until they find the treasure.
- Edit
game.jsonwith your questions, answers, and clues - Build the static site → one HTML page per station + a start page
- Print QR codes and hide them around a garden / house / park
- Kids scan a QR code → a question appears on their phone with audio
- Answer correctly → get a clue to the next station
- Repeat until the final station triggers confetti and reveals the treasure 🎁
Each station is a standalone HTML page — no backend, no app install, just a phone camera.
npm install
npm run build # generates static HTML in dist/
npm run generate-qr # generates QR code PNGs in qr-output/Print the QR codes, hide them, and you're ready to go.
To also generate bunny-voice audio (requires Azure Speech credentials):
export AZURE_SPEECH_KEY=<your-key>
export AZURE_SPEECH_REGION=westeurope
npm run generate-audio # generates MP3s in dist/audio/
npm run build # build again to pick up audio files| Field | Description |
|---|---|
title |
Game title shown on every page |
description |
Subtitle on the start page |
baseUrl |
Where the site is hosted (used for QR code URLs) |
firstHint |
Clue revealed on the start page when the player clicks "Začít hledání!" |
stations[] |
Array of stations, each with a question and answers |
stations[].id |
Unique station identifier (used in filenames: station-{id}.html) |
stations[].question |
The question text shown to the player |
stations[].answers[] |
Array of { text, correct } — exactly one should be correct: true |
stations[].nextClue |
Clue pointing to the next station's physical QR code location |
finalMessage |
Shown after the last station is completed (with confetti!) |
The last station does not need a nextClue — finalMessage takes over.
| Command | What it does |
|---|---|
npm run build |
Generate static site in dist/ from templates + game.json |
npm run generate-qr |
Generate QR code PNGs in qr-output/ |
npm run generate-audio |
Generate bunny-voice MP3s in dist/audio/ via Azure TTS |
npm run create-game |
Interactive CLI wizard to create/edit game.json |
npm run preview |
Local preview with Azure SWA CLI |
npm run deploy |
Deploy dist/ to Azure Static Web Apps |
npm run clean |
Delete the dist/ directory |
The QR generator accepts --base-url to override the URL in game.json:
node tools/generate-qr.js --base-url https://easter.example.comnpm run generate-audio uses Azure Cognitive Services Speech SDK to produce MP3 files read by a pitched-up Czech female voice (cs-CZ-VlastaNeural) — the "bunny" effect.
SSML settings: rate 50%, pitch +25%.
| File | Content |
|---|---|
welcome.mp3 |
Game title + description (played on the start page) |
station-{id}.mp3 |
Question read aloud, then each answer option ("Možnost jedna: …", "Možnost dva: …") |
station-{id}-correct.mp3 |
"Správně! Výborně!" + the nextClue text (or finalMessage for the last station) |
| Variable | Description |
|---|---|
AZURE_SPEECH_KEY |
Azure Speech service subscription key |
AZURE_SPEECH_REGION |
Azure region (e.g. westeurope) |
If credentials are missing, the script exits silently with a warning — the site works fine without audio.
- Audio autoplays when a station page loads
- On mobile (where autoplay is blocked), a hint appears: "👆 Klepni na 🔊 pro přehrání"
- A 🔊 replay button is always available
- On correct answer, question audio stops and celebration audio plays
All UI text is in Czech. The design is mobile-first with pastel spring colors:
- Bouncing bunny 🐰 header animation on every page
- Floating eggs 🥚🐣🌷 drifting in the background
- Grass footer gradient at the bottom of the viewport
- Progress dots showing completed / current / remaining stations
- Start page — "Začít hledání!" button triggers a clue-reveal animation showing
firstHint - Station pages — answer buttons with egg icons, shake animation on wrong answer, pop animation on correct
- Final station — triggers a 5-second confetti canvas animation + rainbow-pulse celebration box
- Responsive — landscape layout for answer grid, font scaling on small screens (< 350px)
Correct answers are not stored as plaintext in the generated HTML. The build script SHA-256 hashes the correct answer's index (0, 1, 2, …). At runtime, the player's selected index is hashed via the Web Crypto API and compared to the stored hash — so viewing the page source doesn't reveal the answer.
The site deploys to Azure Static Web Apps. See DEPLOY.md for full instructions.
The workflow (.github/workflows/azure-static-web-apps.yml) runs on every push to main:
npm cinpm run generate-audio(usingAZURE_SPEECH_KEYsecret)npm run build- Deploy
dist/to Azure Static Web Apps
Security hardening:
- Push-to-main only (no PR deploys — prevents fork-based attacks)
- All actions pinned to full commit SHAs (supply chain protection)
permissions: contents: read(least privilege)- Named
productionenvironment for audit trail
| Secret | Purpose |
|---|---|
AZURE_STATIC_WEB_APPS_API_TOKEN |
SWA deployment token |
AZURE_SPEECH_KEY |
Azure Speech TTS key (for audio generation) |
npm run generate-qr reads game.json and creates one PNG per station plus a start-page QR code in qr-output/:
qr-start.png→{baseUrl}/index.htmlqr-station-{id}.png→{baseUrl}/station-{id}.html
Settings: error correction H (high), 400×400 px, 2 px margin.
npm run create-game walks you through building game.json interactively in the terminal (Czech prompts). It can load an existing game.json and add stations or start fresh.
- Frontend: Vanilla HTML, CSS, JavaScript — no frameworks, no bundler
- Build: Node.js scripts (
tools/build.js) using HTML templates with{{placeholder}}substitution - TTS: microsoft-cognitiveservices-speech-sdk for Azure Speech
- QR codes: qrcode npm package
- Hosting: Azure Static Web Apps (free tier)
- CI/CD: GitHub Actions with pinned action SHAs
- Security: SHA-256 answer hashing, least-privilege permissions, no plaintext answers in source
{ "title": "Velikonoční honba za pokladem", "description": "Najdi všechny stanoviště a získej poklad!", "baseUrl": "https://your-site.azurestaticapps.net", "firstHint": "První QR kód najdeš na zahradě u vchodových dveří! 🚪", "stations": [ { "id": "1", "question": "Z čeho se plete pomlázka?", "answers": [ { "text": "Z provázku", "correct": false }, { "text": "Z vrbového proutí", "correct": true }, { "text": "Z drátku", "correct": false } ], "nextClue": "Další QR kód hledej u jabloně! 🌳" } // ... more stations — last station omits nextClue ], "finalMessage": "🎉 Výborně! Našli jste poklad! Běžte do kuchyně! 🎁" }