The runway where all gtfs.zone projects take off from
  • TypeScript 83.3%
  • HTML 13.5%
  • CSS 2.9%
  • Dockerfile 0.3%
Find a file
Repository files (latest commit first)
Filename Latest commit message Latest commit date
Max Katz-Christy cd6ca7896e
All checks were successful
Build and Deploy / build (push) Successful in 34s
Use DEPLOY_USER, DEPLOY_TOKEN and REGISTRY_DOMAIN in the site build
2026-08-18 02:24:35 +02:00
.forgejo/workflows Use DEPLOY_USER, DEPLOY_TOKEN and REGISTRY_DOMAIN in the site build 2026-08-18 02:24:35 +02:00
docs docs: replace the superseded plan docs with one current plan 2026-08-16 14:39:15 +02:00
public feat: replace zola site with vite single page 2026-08-15 03:17:49 +02:00
scripts feat: replace zola site with vite single page 2026-08-15 03:17:49 +02:00
src fix: fix accessibility and better marketing terms 2026-08-16 23:44:50 +02:00
.cz.toml chore(release): bump version 0.5.2 -> 0.5.3 2026-08-16 23:45:09 +02:00
.dockerignore Build and publish a site image instead of copying into a shared volume 2026-08-18 01:44:45 +02:00
.gitignore feat: replace zola site with vite single page 2026-08-15 03:17:49 +02:00
.mcp.json chore: copier update to v0.1.0 2026-04-17 21:56:49 +02:00
.pre-commit-config.yaml build: default pre-commit 2026-03-10 15:03:01 +01:00
CHANGELOG.md chore(release): bump version 0.5.2 -> 0.5.3 2026-08-16 23:45:09 +02:00
CLAUDE.md docs: replace the superseded plan docs with one current plan 2026-08-16 14:39:15 +02:00
CURRENT_PLAN.md docs: replace the superseded plan docs with one current plan 2026-08-16 14:39:15 +02:00
Dockerfile Build and publish a site image instead of copying into a shared volume 2026-08-18 01:44:45 +02:00
index.html fix(theme): opt the page out of dark-mode extensions 2026-08-16 14:39:59 +02:00
LICENSE.txt chore: apply bare-chassis copier template 2026-04-17 00:54:07 +02:00
nginx.conf Build and publish a site image instead of copying into a shared volume 2026-08-18 01:44:45 +02:00
package.json chore(release): bump version 0.5.2 -> 0.5.3 2026-08-16 23:45:09 +02:00
pnpm-lock.yaml feat: replace zola site with vite single page 2026-08-15 03:17:49 +02:00
pnpm-workspace.yaml feat: replace zola site with vite single page 2026-08-15 03:17:49 +02:00
postcss.config.js feat: replace zola site with vite single page 2026-08-15 03:17:49 +02:00
README.md feat: replace zola site with vite single page 2026-08-15 03:17:49 +02:00
tsconfig.json feat: replace zola site with vite single page 2026-08-15 03:17:49 +02:00
vite.config.ts fix: fix accessibility and better marketing terms 2026-08-16 23:44:50 +02:00

landing-zone

The gtfs.zone homepage. Deployed to https://gtfs.zone.

A scroll-driven single page: Vite, TypeScript, Tailwind v4, DaisyUI, and d3 for the scene geometry. No framework.

Commands

pnpm install
pnpm dev         # http://localhost:8080
pnpm build
pnpm typecheck
pnpm bake                          # re-bake public/data/network-night.json from MBTA
pnpm bake ./path/to/feed.zip
pnpm bake <source> --types=0,1     # keep only those GTFS route_types

The committed geometry is MBTA rapid transit (--types=0,1): 16 routes, 86 stations, 25 KB, with the agency's own route colors.

Themes

One page, two themes, switched by the moon toggle in the top right. The control is the same swap swap-rotate markup and the same theme-controller contract used by the editor and the visualizer, and it shares their theme localStorage key.

  • night dark, glowing strokes, continuous vehicles
  • light warm paper, hairlines, grid, snapping vehicles

Both themes draw the same real GTFS geometry, so the toggle changes how the site looks, never what it shows. The differences are exactly two things: the CSS token set (src/styles/tokens-*.css) and the VariantConfig in src/main.ts.

Scenes bake palette values into DOM attributes when they mount, so a theme change tears every scene down and remounts it (stopEngine() in src/engine/scene.ts). Window-level listeners are installed once and survive the swap.

The three products

The page treats the first two as peers, and links both everywhere.

Tool URL Repo
Editor https://edit.gtfs.zone coloring-book
Visualizer https://viz.rt.gtfs.zone test-track
Manager https://manage.rt.gtfs.zone cafe-car

How it fits together

  • src/page.html is the body markup. A small Vite plugin inlines it into index.html.
  • The same plugin expands <!-- @feeds editor --> and <!-- @feeds visualizer --> from src/content/feeds.ts, so the editor chips and the visualizer chips are built from one feed list.
  • It then stamps target="_blank" rel="noopener noreferrer" onto every external anchor and fails the build if one escapes. Nobody has to remember the rule per-anchor.
  • src/content/links.ts is the canonical URL table. src/content/copy.ts holds the prose and imports its hrefs from there.
  • Motion is a pure function of scroll offset. Every scene's render(p) is idempotent and depends only on p. Time-based ambient motion uses p.elapsed and a deterministic seed.
  • One rAF loop drives everything (src/engine/ticker.ts). Offscreen scenes do not render. The background network is the one exception and always renders.

Sections and scenes

Section Scene
Hero hero-map (persistent background)
What is GTFS? none, text only
GTFS Schedule timetable
GTFS Realtime realtime-phone
The editor editor-tools, editor-cta
The visualizer visualizer-inspector
The manager alert-broadcast
Publish publish
Open source none, static SVG glyphs
Get in touch none

Releasing

Deploys are triggered by tags, not by pushes to main. Push to main freely; nothing ships until a tag exists.

cz bump          # updates package.json + CHANGELOG.md, commits, tags vX.Y.Z
git push --follow-tags

cz bump refuses to run off main. The v* tag fires .forgejo/workflows/build.yml, which typechecks, builds, and copies dist/ to /sites/gtfs.zone.

History

docs/ holds the build and revision plans this page was written to, and docs/VERIFICATION.md records what was checked against the deployed products.