A static leaderboard site. It hosts any number of challenges, each split into one or more flavours (categories), with attempts ranked inside each flavour.
There is no server and no database. Results live as JSON files in data/, the
site is prerendered by SvelteKit, and GitHub Pages serves the result. Every
entry arrives as a pull request — review and merge are the moderation step.
SvelteKit 2 · Svelte 5 · Vite · adapter-static. Every route is prerendered to
plain HTML, so the boards are readable with JavaScript disabled; the sorting,
filtering and personal-best controls hydrate on top.
npm install
npm run dev # http://localhost:5173
npm run validate # check the data files
npm test # unit tests
npm run build # prerender into build/
npm run preview # serve the built siteNode 20 or newer.
Challenge — the thing people compete at, with a name, description and a
free-text ruleset. One JSON file per challenge in data/challenges/, named
after its slug.
Flavour — a category within a challenge. Each flavour decides how its board is ranked:
| Field | Meaning |
|---|---|
metric |
duration (parsed to ms) or score (a plain number) |
direction |
asc = lower is better, desc = higher is better |
unit |
shown after score values, e.g. wpm |
Attempt — one result by one participant.
- Ties share a rank and consume the positions below them:
1, 2, 2, 4. - Tied results are ordered by whoever set theirs first.
- By default each participant is collapsed to their single best result (case-insensitive name match), so one person cannot fill the top ten. The board has a toggle to show every recorded attempt instead.
Times are written the way they are displayed:
12.34 → 12.34
1:34.65 → 1:34.65
1:02:03.5 → 1:02:03.50
Drop a new file in data/challenges/. Nothing else needs changing — the route
list is derived from the data directory at build time, so the new pages are
prerendered automatically.
See CONTRIBUTING.md. In short: edit the challenge's JSON file, open a pull request, and CI validates it before a maintainer reviews the proof and merges.
.github/workflows/deploy.yml builds and publishes to GitHub Pages on every
push to main. To enable it once:
Settings → Pages → Build and deployment → Source: GitHub Actions.
The workflow passes BASE_PATH from actions/configure-pages, so the site
works at https://<user>.github.io/<repo>/ without any hardcoded paths.
.github/workflows/validate.yml runs validation, tests and a full build on
every pull request.
data/
site.json title, tagline, repo
challenges/*.json one file per challenge — the entire dataset
src/
lib/format.js value parsing and display
lib/rank.js ranking rules (pure functions)
lib/load.js reads and validates the data files
lib/Board.svelte sortable, filterable board
routes/ pages
scripts/validate.js CI entry point
tests/
{ "slug": "rubiks-cube", // must match the filename "name": "Rubik's Cube Speedsolve", "description": "Solve a scrambled 3x3 cube as quickly as possible.", "rules": "WCA scrambles. Inspection is capped at 15 seconds.", "flavours": [ { "slug": "single", "name": "3x3 Single", "description": "Fastest single solve.", "metric": "duration", "direction": "asc", "unit": "", "attempts": [ { "participant": "Ana", "value": "6.42", // "[h:]m:s.cc" for durations "date": "2026-03-02", // optional, YYYY-MM-DD "proof": "https://…", // optional but strongly encouraged "note": "" // optional } ] } ] }