Create scroll-driven map stories: as readers move through story cards, the map flies to each location, switches basemaps, and shows annotations (markers, labels, lines, and regions).
Built with MapLibre GL. Licensed under the MIT License by Dan Pacheco.
| Piece | Purpose |
|---|---|
Home (index.html) |
Open the builder, list stories, edit / view / delete |
Builder (Tools/scroll-map-builder.html) |
Author locations, cards, overlays, and map options |
Story viewer (Tools/scroll-map-story.html) |
Published story page (also copied into each story folder on save) |
- Locations — add, reorder, and set camera / basemap per step
- Cards — rich text (Quill) with optional background color and sources
- Annotations — markers, labels, lines, and polygons (draw or import GeoJSON)
- Basemaps — OpenFreeMap Bright, Carto Voyager, Esri satellite (optional place-name overlay)
- Globe or flat — globe is the default for new stories; Mercator also available
- Optional 3D terrain — MapLibre demo tiles (no API key)
- Export — download a standalone ZIP that works offline
- Guest Mode — author locally in the browser (no server); same
localStorage+ ZIP pattern as WebxRide Public Playground
- Story mode — scroll the cards; the map follows each location and its overlays
- Explore mode — free pan/zoom; annotations from all locations show at once
More detail on overlay data: Tools/STORY-OVERLAYS.md.
Requirements: Node.js 18+ recommended.
git clone https://github.com/pachecod/mapsandcards.git
cd mapsandcards
npm install
npm run devVite opens the home page (default http://localhost:5173). Create a story in the builder; saves write under Stories/<slug>/ via the local story API.
Note:
Stories/is local-only and not committed to this repository.
Open Guest Mode from the home page, or go to /Tools/scroll-map-builder.html?guest=1.
| Behavior | Detail |
|---|---|
| Persistence | Browser localStorage key playground-{slug} (same as WebxRide Public Playground) |
| Project shape | { name, framework: "html", files: [index.html, scroll-map-story.json] } |
| New story | Defaults to a blank draft; optionally Start from a bundled template under Starter Templates/ (e.g. Earth) |
| Save | Local only — never calls /__story-api |
| Preview | Blob URL of standalone viewer HTML |
| Share | Export Local Site — client-side JSZip download |
Shared helpers live in Tools/guest-playground.js so embedding into WebxRide’s /play/:templateId path can reuse the same contract.
| Command | What it does |
|---|---|
npm run dev |
Vite + local story API (filesystem) |
npm run build |
Production frontend build |
npm run preview |
Preview the Vite build |
npm start |
Express server (production-style; uses Postgres when configured) |
npm run db:migrate |
Apply Postgres schema |
The included render.yaml targets Render with a Node web service and Postgres.
- Set environment variables from
.env.example:DATABASE_URL/DATABASE_SSL— required for the production story APIAPP_PASSWORD— optional; gates the non-guest builder and story write API (Guest Mode + story viewing stay public)GOOGLE_ANALYTICS_ID— optional GA4 measurement ID (e.g.G-XXXXXXXXXX); leave blank to disable analyticsB2_*— optional Backblaze B2 keys (reserved for future media uploads)PORT— set automatically on most hosts
- Start with
npm run db:migrate && npm start(as inrender.yaml).
Locally, stories live on disk under Stories/. In production, the same /__story-api shape is backed by Postgres.
index.html Home / story list
Tools/
scroll-map-builder.html Authoring UI
scroll-map-story.html Viewer template
guest-playground.js Guest Mode / WebxRide playground helpers
STORY-OVERLAYS.md Overlay schema notes
Starter Templates/ Bundled stories seeded on install (e.g. earth → Earth)
story-api-plugin.js Vite plugin: local story CRUD
server.js Express app for production
routes/ services/ db/ Production API + database
middleware/auth.js Optional password protection
middleware/analytics.js Optional Google Analytics (GOOGLE_ANALYTICS_ID)
Stories/ Your stories (gitignored)
Issues and pull requests are welcome. Please keep story content, .env secrets, and anything under inspiring/ out of commits — they are intentionally excluded from the repo.
MIT — Copyright 2026 Dan Pacheco.