A no-build, mobile-first PWA for practising Spanish Permiso B theory questions with on-demand English translations. It is designed for GitHub Pages: there is no server, database, build command, or client-side API key.
Vibe-coded project: DrivePrep was built collaboratively with Codex. Treat it as an open learning project, not as official DGT examination software or legal/road-safety advice. Verify current rules with DGT before taking an exam or making a driving decision.
- 380 bilingual Spanish/English questions are published in
data/questions.json. - They come from the public DGT magazine archive, with per-question attribution and a link to the original source page.
- 64 image-dependent questions from the import are intentionally excluded: DGT magazine permits attributed text reproduction but not reuse of its drawings/photos.
- English is machine-translated through the local Codex adapter and is marked for bilingual review. Historical questions can also become outdated when regulations change.
- Exam simulation: 30 randomized questions, with the result and errors shown only at the end. The current DGT practice-exam format uses 30 questions and a maximum of three errors; regulations and the official format can change, so check the DGT preparation page before relying on it.
- Freestyle: endless randomized questions with immediate correct/incorrect feedback.
- An accessible EN control beside every question and answer.
- Offline caching after the first visit, via a service worker.
data/questions.jsonas the entire published question database. Image paths may be added as animagesarray, such as"images/priority-example.webp".
The question bank is intentionally sourced only from material with a recorded reuse basis. See scripts/SOURCES.md for the source audit.
The PWA must be served over HTTP (opening index.html directly prevents JSON loading and service-worker installation):
uv run python -m http.server 8000
open http://localhost:8000No login, account, API key, or database is needed to use the PWA. Once the first page load completes, it is also available offline through the browser's installed app/offline cache.
- Open
http://localhost:8000and select Exam simulation. - Choose answers through question 30 and confirm that the score and missed answers are shown only on the results page.
- Return home, select Freestyle, and confirm that selecting an answer immediately marks the answer and shows feedback.
- In either mode, tap EN beside a question and beside an answer to reveal its translation.
- On a phone, use the browser's Add to Home Screen option, open the installed app once, then switch the phone offline and reopen it.
The scripts run locally on macOS/Linux and put content through a review gate. The configured DGT magazine importer can collect its numbered archive (up to 2,780 possible question records). Its legal notice permits reproducing the magazine's text with attribution, but excludes images, so the importer does not download images. Never run an unrestricted scraper against third-party quiz sites: their question banks may be copyrighted, behind terms that prohibit extraction, or unsuitable for redistribution.
# Install uv first if needed: https://docs.astral.sh/uv/getting-started/installation/
uv sync
# 1. Fetch only configured sources that are explicitly allowed by robots.txt.
uv run python scripts/scrape.py --max-pages 50 # latest 50 archive issues; omit the cap for the full archive
# 2. Translate a saved import. Either use an API key...
OPENAI_API_KEY=... uv run python scripts/translate.py data/inbox/scraped-YYYY-MM-DD.json
# ...or a locally authenticated Codex CLI (no API key in this project).
uv run python scripts/translate_with_codex.py data/inbox/scraped-YYYY-MM-DD.json --batch-size 50
# 3. Review translations and current-rule accuracy, then publish only items
# that do not require an unlicensed image.
uv run python scripts/curate.py data/inbox/scraped-YYYY-MM-DD-translated.json --exclude-image-dependentscripts/sources.json is the allow-list. The scraper checks robots.txt, has a two-second delay, and writes candidates only; it never overwrites data/questions.json. The default importer is specific to the DGT public magazine markup and saves the official shown answer plus a link back to every original. scripts/SOURCES.md records the audit of other popular providers and the conditions for adding one. Add another source only if its reuse terms explicitly allow republishing the questions and images.
The OpenAI translator only supplies Spanish question/answer text. It returns translations for a bilingual reviewer to approve; it does not determine correct answers. OPENAI_API_KEY is never put in the Pages app or committed.
If the local machine is signed in to the Codex CLI but has no API key, use the resumable Codex alternative instead:
uv run python scripts/translate_with_codex.py data/inbox/scraped-YYYY-MM-DD.json --batch-size 50It checkpoints after every batch to *-translated.json, so rerunning the same command continues where it stopped. The translations still need bilingual review before publication.
{
"id": "source-unique-id",
"category": "SIGNALS",
"question": { "es": "…", "en": "…" },
"answers": [{ "es": "…", "en": "…" }],
"correctIndex": 0,
"images": ["images/example.webp"]
}Keep images under images/ in the repository, provide descriptive imageAlt, and use repository-relative paths.
The curation command can exclude questions that depend on a sign or photo when that image has not been licensed into images/. This is the safe default for DGT magazine imports, whose text is reusable with attribution but whose images are not.
- In the repository’s Settings → Pages, set the source to GitHub Actions.
- The included workflow deploys every push to
main. - The app uses relative URLs, so it works at
https://OWNER.github.io/REPOSITORY/.
After changing app files, increment CACHE in sw.js to prompt installed apps to refresh their cache.