A static-site GUI for the SpaceTraders API game. No build tools, no frameworks — vanilla JS with ES modules, PicoCSS v2 for styling, Font Awesome 7 Pro for icons. All auth and game state lives in browser localStorage.
Phase 1 is read-only: view agent info, fleet, contracts, system waypoints, and markets. The code is structured so Phase 2 (action buttons/forms) can be added by filling the *-actions placeholder divs in each view.
| Layer | Technology | Loaded from |
|---|---|---|
| CSS framework | PicoCSS v2 (dark theme) | jsdelivr CDN |
| CSS colors | PicoCSS colors (utility classes) | jsdelivr CDN |
| Icons | Font Awesome 7 Pro | Kit script (kit.fontawesome.com) |
| Visualization | Sigma.js v3 + Graphology | jsdelivr CDN (ES module +esm) |
| JS | Vanilla ES modules (type="module") |
Local files |
| API | SpaceTraders v2 (api.spacetraders.io/v2) |
Bearer token auth |
No package.json, no node_modules, no bundler. Serve with any static HTTP server (ES modules require it — file:// won't work).
index.html # SPA shell: header/nav, main, footer
css/style.css # Minimal custom CSS (PicoCSS handles most)
icons.json # Icon/color mappings (single source of truth)
js/
app.js # Entry: routes, auth guards, boot
api.js # Fetch wrapper: auth, retry, pagination, endpoints
router.js # Hash-based SPA router with :params
state.js # localStorage: token, cached agent, token history
icons.js # Loads icons.json, exports icon() helper
components/
loading.js # withLoading(), showError(), escapeHtml(), etc.
nav.js # Responsive nav bar (dropdown mobile, horizontal desktop)
pagination.js # Reusable prev/next page controls
system-map.js # Sigma.js system waypoint map
views/
login.js # Token login (bearer token + saved agents)
fleet.js # Agent dashboard + paginated ship cards
ship-detail.js # Full ship info: nav, frame, reactor, cargo, modules
contracts.js # Contract list (cards on mobile, table on desktop)
system.js # System map + waypoint browser + market data
scripts/
verify_icons.py # Checks icons.json against API spec enums
.github/workflows/
verify-icons.yml # Weekly CI: detect icon mapping drift
api-docs/ # SpaceTraders OpenAPI spec (models + reference)
pico/ # PicoCSS v2 source (SCSS reference)
Hash-based SPA. Routes registered in app.js via addRoute(pattern, handler).
| Hash | View | Auth |
|---|---|---|
#/login |
login.js |
No |
#/fleet |
fleet.js |
Yes |
#/fleet/:shipSymbol |
ship-detail.js |
Yes |
#/contracts |
contracts.js |
Yes |
#/system |
system.js |
Yes |
#/system/:systemSymbol |
system.js |
Yes |
Auth-guarded routes use guard(viewFn) which checks getToken() and redirects to #/login if missing.
localStorage (token, agent)
↓
app.js guard() checks token
↓
View render(params) called
↓
endpoints.xxx() — api.js injects Bearer token, retries 429s
↓
withLoading() — shows spinner, catches errors, renders HTML
↓
icon(MAP, key) — resolves FA icon + Pico color from icons.json
- Base URL:
https://api.spacetraders.io/v2 - Auto-injects
Authorization: Bearer <token>fromstate.js - Rate limit: retries 429 responses up to 3 times with
retry-afterdelay - All responses are
{ data, meta? }—metahastotal,page,limitfor pagination - Throws
ApiError(status, body)on non-2xx
Every view exports async function render(params):
- Gets
#app-mainviagetMain() - Wraps async work in
withLoading(main, async () => { ... }) - Builds HTML with template literals +
icon()+escapeHtml() - Leaves
<div id="*-actions">placeholders for future Phase 2 action UI - Paginated views accept optional
pageparam, re-invokerender()on page change
icons.json is the single source of truth for 4 categories: shipFrames, factions, waypointTypes, starTypes. Each entry maps to a Font Awesome icon name and a PicoCSS color utility class.
Every category has a _default entry. When icon(MAP, key) encounters an unmapped key, it uses the default and logs console.warn.
icons.js fetches icons.json at startup and exports a ready promise. app.js awaits ready before starting the router.
Uses Sigma.js v3 (WebGL graph renderer) + Graphology (graph data structure), loaded as ES modules from jsdelivr CDN.
- Waypoints → graph nodes at API-provided x,y coordinates, colored/sized by type
- Orbitals → edges connecting moons to parent bodies
- Ships → small offset nodes near their waypoint, colored by nav status
- Hover highlights connected nodes, dims unrelated ones
- Click scrolls to waypoint in the list (or navigates to ship detail)
- Pan/zoom built-in (mouse + touch)
killMap()cleans up WebGL resources on navigation
Mobile-first via PicoCSS defaults. Custom CSS is minimal:
| Breakpoint | Layout changes |
|---|---|
| < 768px | Single-column cards, dropdown nav, .card-list visible |
| >= 768px | Horizontal nav, .responsive-table visible, 2-col grids |
| >= 1024px | .grid-3 for 3-column fleet cards |
Python script compares icons.json keys against enum values in api-docs/models/*.json schemas. Runs weekly (Monday 9am UTC), on relevant file changes, and on manual trigger. Fails if mappings are out of sync with the API spec.
- PicoCSS first: use semantic HTML and PicoCSS classes/patterns before writing custom CSS. Reference
pico/scss/source for available styles. <article>= card: PicoCSS styles<article>with<header>as a card component.aria-busy="true": PicoCSS renders a loading spinner on buttons and containers..outline: PicoCSS class for outline-style buttons (secondary emphasis).<div role="group">: PicoCSS inline button groups.<details class="dropdown">: PicoCSS v2 dropdown pattern (NOTrole="list"which is v1)..overflow-auto: PicoCSS utility for scrollable table wrappers.- Pico color classes:
pico-color-{family}-{shade}for text color,pico-background-{family}-{shade}for backgrounds. Loaded viapico.colors.css. <mark>labels/pills: extends Pico's native<mark>with variant classes for colored pills. Bare<mark>= Pico default highlight. Variants:.ins(green, success),.del(red, danger),.primary(accent),.secondary(neutral/muted). Wrap multiple labels in<span class="label-group">for flex-wrap layout. UsenavStatusLabel(status)fromloading.jsfor ship nav status badges.- Font Awesome: use
/suggest-iconand/add-iconskills for icon lookups, not web search. - No build step: everything must work as plain static files served over HTTP.
escapeHtml(): always use for user-provided or API-returned text rendered as HTML.- Placeholders: every view leaves
<div id="*-actions">containers for Phase 2 expansion.