Track Layout is a fully static GTA San Andreas race-route map builder. Give it a track JSON that only contains checkpoints; it snaps each CP to the game's road graph, routes between them with A*, renders a customizable route map, and exports PNG / SVG.
Pure vibe coding: architecture, code, debugging, docs and samples were iterated by AI in conversation. It works, but review and test it before production use.
Inspired by and built on data from neufox's Node Viewer and neufox's Rec Editor: plain HTML/CSS/JS + Leaflet
CRS.Simple+samap.png, with no build step and no application backend (server.jsis only a local static file server).
- Track JSON import: choose a file or drop it in; tolerant parsing for arrays,
checkpoints/cps/points,[x,y,z],pos/position, and similar shapes. - Automatic road routing: the default Auto mode prefers a full A* route over SA vehicle nodes. If some CPs cannot snap, it still keeps as much of the road network as possible, fills gaps with straight lines, and explains this under Route. You can also force Road / Spline / Straight.
- Closed loops: when Closed loop is enabled, the last point returns to the start and the first point becomes both start and finish.
- CP editing: drag on the map, edit coordinates, move up/down, delete, or add CPs by clicking the map. Show or hide checkpoints in both the map and the export; the sidebar list is collapsible and virtualized for tracks with hundreds of CPs.
- CP display filters: show all markers, start + finish only, start only, finish only, or CP style (all points, including start / finish, rendered as ordinary checkpoints without S/F colours or badges). Marker density can be automatic (about one marker per 600 m, capped at 72), or manual every 2nd / 5th / 10th / 20th. Display thinning never changes routing or CP indices, and the sidebar editing list always keeps every checkpoint.
- Large warning sets: the notes/warnings panel is collapsible too, and both warning lists virtualize their rows so large tracks stay responsive.
- Visual customization: 9 theme presets; solid / along-route / linear / elevation / rainbow route gradients; outline, glow, dashes, direction arrows; CP shape, size, colors, numbering and labels; base-map brightness / contrast / saturation / hue / invert / blur / tint / grid; title and legend.
- Custom theme state: any visual override switches the active theme to Custom while remembering the base preset. Changing it back exactly restores the preset automatically; Restore preset resets visuals to that base without losing title text, visibility toggles, or a custom base map.
- PNG export: 16:9, 4:3, 1:1, 9:16, 4K, transparent background, and full track / current view framing.
- SVG export: route, CPs, title and legend stay vector; the base map is embedded as a raster image, or turn it off for a pure-vector route map.
- Custom base maps: upload an image and set its coordinate bounds for other games or hand-made maps.
- Sample library: the Samples menu has fuzzy search, keyboard navigation and a virtualized scrolling list, so forks can add hundreds of sample tracks without slowing the UI; the sidebar shows the first few plus an "all samples" shortcut.
- Autosave: track, styles and export settings live in this browser's
localStorageand survive reloads. - Stacked toasts: rapid actions show up to five top-center notices at once; extra notices queue and dismiss oldest-first.
- Shortcut reference: press
?or click the keyboard icon to open a grouped keyboard-shortcut dialog. Single-key shortcuts can be disabled there; modifier shortcuts and Escape keep working. - Slider helpers: double-click a range input to restore its current theme/default value; click the numeric readout to type an exact value with min/max validation.
- Safe CP editing: map marker dragging is locked by default to prevent accidental moves; press
Lor use the lock button to unlock, and undo/redo checkpoint edits (drag, coordinate input, add/delete/reorder). - Responsive map: window resizing is debounced (180 ms), then the map re-fits the current track; virtual lists and the scale bar refresh at the same time.
- Route playback: play a direction arrow from start to finish with a 0.1×–4× speed slider (click the
×value to type an exact speed); a floating HUD shows playing/paused/finished status, a draggable progress bar, and stop controls. Optional map following keeps the arrow centered; an additional heading-up mode rotates the map around the playback point so the arrow always points up (dragging or zooming turns both modes off). The animation is map-preview only and never appears in exports. - Collapsible route notes: route warnings can be folded away and use the same virtualized list rendering for long runs.
- Clear local cache: the sidebar footer has a cache reset action that removes this app's
localStorageand Cache Storage data, then reloads. - Responsive workspace: below 980px the sidebar becomes a bottom sheet with peek / half / full snap points. Drag the handle, tap it, use the chevron, or use
↑/↓/Home/Endwhile the handle is focused. On desktop the sidebar can be collapsed from the top bar. - Compact action menu: below 720px the top-bar actions collapse into a hamburger panel containing file, sample, language, theme, help and shortcut controls. It closes on outside click, selection, or
Esc. - Movable help dialogs: drag the title bar of the JSON-format or keyboard-shortcut dialogs to reposition them. Positions are clamped to the viewport, reset when reopened, and double-clicking the title bar recenters.
- Small-screen controls: below 480px the section tabs show icons only. Checkpoint, track-note and route-note headers are full-width collapse toggles, not just the chevron.
- Toast feedback: Ant-Design-style elevated notices with success/error/info icons, soft shadows, top-center stacking and slide-fade motion.
- Title / legend placement: independently anchor both overlays to any of six positions (top/middle/bottom × left/center/right), then fine-tune horizontal and vertical offsets as percentages of the canvas. The same placement is used by the live map, PNG, SVG, mini preview and fullscreen preview.
- Live export preview: the thumbnail in the Export pane re-renders after style, route, or size changes and shows a loading spinner while the next frame is being prepared.
- Export watermark: the bottom-right
GTA: SAN ANDREAS · TRACK LAYOUTcredit can be enabled or disabled in the Export pane. - Fullscreen preview: click the preview image to open a full-viewport lightbox with wheel/trackpad zoom, pinch zoom on touch screens, drag-to-pan, double-click reset, and an optional native Fullscreen API button. Keyboard:
+/-zoom, arrows pan,0reset,Escclose. - Source link: the sidebar footer GitHub icon opens the source repository.
- Internationalization: first visit follows the browser locale (
zh*→ Simplified Chinese, everything else → English); the globe menu in the top bar overrides it. UI, map title/legend, CP list, notices, help and PNG/SVG export text all follow the active language. - Dark / light UI: the sun / moon / monitor menu in the top bar switches Light / Dark / System. System follows
prefers-color-schemelive, and the choice is remembered in this browser. This only affects the app chrome, not the route-map artwork itself.
Requires Node.js 18+. The app is static: no build step and no npm install required.
git clone https://github.com/dockfries/track-layout
cd track-layout
# Option A (recommended): temporary static server via npx
npx --yes serve . -l 8173
# Option B: the zero-dependency static server in this repo
node server.js 8173
# or: npm startOpen http://127.0.0.1:8173/. The first visit automatically loads the Los Santos Loop sample.
npx servedownloadsservetemporarily on first use; it does not write into this repo.npm run serveis equivalent to Option A;npm startis equivalent to Option B.- Both options only serve static files. No track data is uploaded.
- If port 8173 is taken,
servemay choose another port; use the URL printed in the terminal. - Open the app over HTTP. It needs
fetch('data/nodes.json'); double-clickingindex.htmlwill be blocked by the browser'sfile://restrictions.
Tests:
npm test
# or: node tests/run.js- Default language: the first visit reads
navigator.languages. Chinese locales such aszh,zh-CNandzh-Hansget Simplified Chinese; every other locale gets English. - Switch and remember: use the globe menu in the top bar. The choice is stored in
localStorageundertrack-layout:lang. - Coverage: static UI, tabs, help dialog, map title/legend, CP list, route notices, toasts, sample names, theme names, and PNG/SVG title, legend and CP labels all follow the active language.
- Adding strings: UI copy uses semantic English keys in the
MESSAGEStable injs/i18n.js(key: { en, zh }); legacy Chinese text in static HTML is mapped throughSTATIC_TEXT, and rich help HTML lives inRICH. Add entries when you add UI copy. html.i18n-pendinghides the UI until a language is applied, so English users do not see a flash of Chinese text.
- Use the System / Dark / Light picker in the top bar to switch the workbench appearance.
- System follows
prefers-color-schemeand updates live when the OS changes. - Manual choices are stored in
localStorageundertrack-layout:themeand survive reloads; choose System to follow the OS again. - Appearance only affects the workbench UI (sidebar, dialogs, map controls, etc.). The map title, legend, CP markers and export artwork are still controlled by the themes under Style.
The simplest form is an ordered CP array; array order is track order:
{
"name": "Los Santos Loop",
"author": "your name",
"closed": false,
"checkpoints": [
{ "x": 2287.4, "y": -1605.9, "z": 2.6 },
{ "x": 1617.6, "y": -1205.8, "z": 51.8 },
{ "x": 1599.0, "y": -2378.2, "z": 12.4 }
]
}| Field | Required | Description |
|---|---|---|
checkpoints |
yes* | Checkpoint array. Also accepts cps / points / cp / waypoints. |
name |
no | Track name; falls back to the file name. |
author |
no | Author. |
description |
no | Description; kept only in the JSON. |
closed |
no | Whether the track is a closed loop; default false. |
map |
no | Map name; default san-andreas. |
checkpoints[].x / .y |
yes | World coordinates. x is east, y is north. |
checkpoints[].z |
no | Elevation; used for elevation gradients and height-aware snapping. |
checkpoints[].label |
no | Display label. |
checkpoints[].radius |
no | Reserved field, preserved as-is. |
* A top-level array is also accepted: [{ "x": 0, "y": 0 }, ...].
Accepted variants:
[
[100, 200, 10],
{ "pos": [300, 400, 12] },
{ "position": { "x": 500, "y": 600, "z": 8 } },
{ "x": 700, "y": 800, "type": "finish", "label": "Finish" }
]Roles come from order, not type: for an open track the first point is Start and the last is Finish; for a closed loop the first point is Start / Finish. type is parsed, but the final role is always determined by order.
CP numbering: Start / Finish use S, F and S/F badges; ordinary checkpoints use their array index. The sidebar, map markers and route notices all share this numbering.
See track.schema.json for the full JSON Schema and samples/ for examples.
-
The road graph comes from
data/nodes.json(extracted fromNODES0.DAT–NODES63.DAT: 30,587 vehicle nodes and about 63k directed connections). -
Each CP looks for a vehicle node within the snap radius (80 m by default); when
zis available, height difference adds a penalty. Snapping prefers the main road component when every CP can join it, otherwise it stays on the nearest local component so short island routes are not detoured. -
Adjacent CPs are connected with A*. Edge cost = 2D length × average of the two node weights:
Node type Weight Highway 0.8 Normal road 1.0 Parking 1.3 Emergency lane 2.6 Water path 12 -
Auto first tries a full road-network A* route. If some CPs cannot snap or some segments are unreachable, it keeps the reachable road parts, fills gaps with straight lines, and shows partial road plus the specific reasons under Route. If no leg at all can be routed, it uses straight connections. Only when the road graph is unavailable does it fall back to a Catmull–Rom spline.
-
Road mode routes segment by segment, fills failed segments with straight lines, and lists the reasons under Route.
-
Road routes are simplified with Douglas–Peucker and 0–2 Chaikin corner-cutting passes for a cleaner race line; the smoothing slider controls the strength.
-
Closed loops additionally route the last point back to the start.
Limitation: a custom base map only changes the display, not the coordinate system or the road graph. Routing still uses GTA SA vehicle nodes. Use Spline or Straight for air, water or hand-made maps.
- Theme presets: Neon Night, Tactical Radar, Paper Map, Miami Sunset, Blueprint, Mono, Cyberpunk 2077, White Chocolate, Rainbow Flow.
- Route gradients:
- Solid
- Along route: start color → finish color over route length
- Linear: gradient in canvas direction
- Elevation: gradient by CP / route
z - Rainbow: continuous full-spectrum light trail, Tesla style
- Route layer: outline, glow, dashes, direction arrows (spacing / size / color). Automatic arrow spacing targets ~48 arrows with a 140 m minimum, so full-map routes stay readable.
- Checkpoints: show / hide, display range, marker density, CP-style endpoints, uniform start/finish colours, circle / square / diamond, size, fill, border, numbering, labels, halo, start color, finish color.
- Base map: brightness, contrast, saturation, hue, invert, blur, tint strength, grid (8 × 8 on the standard SA bounds), optional grid numbers (0 at the top-left, row-major), background color, visibility; upload a custom image and set
minX/maxX/minY/maxY. Custom images and their bounds are not written tolocalStorage(too large); re-select the image and bounds after reload. - Title & legend: show switches, eyebrow, title, subtitle, text/accent/background colors, background opacity, overall scale, title font size, subtitle font size, legend background.
Can visual styles be saved to JSON? Currently they are autosaved in localStorage only; Save JSON contains track data, not visual styles.
Preview export renders a fresh PNG at the current output size in a dialog; Download PNG and Download SVG use the same renderer.
- PNG: raster output, transparent background, full track / current map view, 16:9 / 4:3 / 1:1 / 9:16 / 4K or custom sizes (up to 4096).
- SVG: route, CPs, arrows, title and legend stay vector; the base map is embedded as a cropped raster with current filters/tint. Turn off Include base map for a pure-vector route map.
- Exports crop to the track bounds with padding; near map edges the viewport is pushed back inside the map where possible to avoid blank strips.
- Title scale, title/subtitle font sizes, theme colors and map filters are re-rendered for export, not screenshotted.
- Very long titles and subtitles are ellipsized to fit the export instead of running past the canvas edge.
| Key | Action |
|---|---|
F |
Fit track |
G |
Show / hide grid |
M |
Show / hide base map |
C |
Show / hide checkpoints |
L |
Lock / unlock map CP dragging |
Ctrl/Cmd + Z |
Undo last checkpoint edit |
Ctrl/Cmd + Shift + Z |
Redo checkpoint edit |
+ / = |
Enter / leave map add-CP mode |
Esc |
Cancel add mode / clear selection / close dialog |
Delete |
Delete the selected checkpoint |
Ctrl/Cmd + O |
Open JSON |
? |
Open the keyboard shortcuts panel |
README.md English documentation
README.zh-CN.md Simplified Chinese documentation
LICENSE MIT license
package.json start / test scripts
.gitignore ignores for local caches, debugging and editor files
index.html page shell
style.css workbench styling
js/track.js JSON parsing, road graph, A*, smoothing, geometry
js/style.js style model, themes, colors, marker SVG
js/i18n.js semantic i18n messages + static-text fallback + language switch
js/mapview.js Leaflet map rendering and CP interaction
js/export.js PNG / SVG renderer
js/app.js app state and UI wiring
data/nodes.json SA vehicle road graph (required, commit it)
assets/samap.png SA base map (required, commit it)
assets/icons/ Lucide / Iconify sprite and license
samples/ sample tracks + index.json (commit them)
docs/preview.webp dark README screenshot
docs/preview-light.webp light README screenshot
vendor/leaflet/ Leaflet 1.9.4 (commit it)
tools/make-samples.js regenerate samples from the road graph
tests/run.js parser / geometry / routing tests
server.js local static server
track.schema.json track JSON Schema
.gitignore only excludes local artifacts such as node_modules/, logs, build caches, .env, editor/OS files, temporary backups, and headless-browser artifacts such as tools/debug/, tools/edge-*/ and tools/*-profile/.
data/nodes.json, assets/samap.png, assets/icons/, docs/preview.webp, docs/preview-light.webp, samples/*.json and vendor/leaflet/ are required runtime or documentation assets. Keep them in the repository; do not delete them or add them to the ignore list.
Sample CPs are real road-graph paths built from hand-picked waypoints (each waypoint plus each segment midpoint), so re-routing cannot cut corners:
npm run samplesPure vibe coding: architecture, code, debugging, docs and samples were iterated by AI in conversation; there is no hand-written line-by-line implementation. Review and test before use.
- neufox's Node Viewer: provided
samap.png,data/nodes.json(extracted fromNODES0.DAT–NODES63.DAT), the LeafletCRS.Simplemap presentation, and the vehicle-node flags / adjacency structure. This project extends those data and ideas with CP snapping, A* routing and route rendering. - neufox's Rec Editor: provided the static, no-build page organization and interaction ideas around path smoothing / resampling / drag editing. The CP editor, map interaction and export flow here reference it.
samap.pnganddata/nodes.jsoncome fromneufox-node-viewer; the road graph was extracted from GTA San AndreasNODES0.DAT–NODES63.DAT.- Map engine: Leaflet, BSD-2-Clause, vendored under
vendor/leaflet/. - UI icons: Lucide, fetched through Iconify and bundled in
assets/icons/sprite.svg. Lucide uses the ISC license; seeassets/icons/LICENSE. - GTA: San Andreas and its map/road data are owned by Rockstar Games. This is an unofficial fan tool and is not affiliated with Rockstar.
Source code, documentation and configuration generated by this project are released under the MIT License; see LICENSE.
MIT covers this repository's source, docs and generated configuration only. The following retain their original rights:
assets/samap.png,data/nodes.json: fromneufox-node-viewer/ GTA: San Andreas; rights belong to Rockstar Games and the original project authors.docs/preview.webp,docs/preview-light.webp: screenshots containing GTA: San Andreas map imagery; not covered by MIT.assets/icons/: Lucide icons, ISC license; seeassets/icons/LICENSE.vendor/leaflet/: Leaflet, BSD-2-Clause; seevendor/leaflet/LICENSE.- GTA coordinate data in sample tracks is subject to the original game-data rights as well.
The GTA map and road data are included for this unofficial fan project with attribution. No separate redistribution license is granted for them; confirm that you have the right to redistribute them before publishing a fork or using them commercially.
Track JSON, dropped images and style settings are processed entirely in the browser. There is no upload endpoint and no analytics. The only external requests are for local data/nodes.json, samples/*.json and base-map files.

