A local-first web engine for telling stories inside a 3D scan. One Markdown story.md file
drives two switchable presentation modes — no reload, no backend.
The scan is shared infrastructure; the story is the act of authorship.
A 3D scan is expensive to make and cheap to reuse. Treating it as infrastructure rather than a deliverable means one capture of a place can hold many accounts of it — the architect's, the resident's, the journalist's — without any one overwriting the others. Why this exists →
- Page view (Mode B): a scrolling long-form article; the 3D model is one inline element in the scroll. Narrative around the scan.
- Immersive view (Mode A): a full-screen 3D scene where advancing the story flies the camera to each section's bound waypoint and shows its content as an overlay. Narrative inside the scan.
Both modes read the same story file; a toggle switches between them live.
Prerequisites: Node.js 20+ and npm. (With nvm: nvm use 20.)
git clone https://github.com/IntoStorymode/Spatial-Storytelling-Engine
cd Spatial-Storytelling-Engine
npm install
npm run dev # → http://localhost:5173Open http://localhost:5173/ — Home lists the bundled demo stories. Click + New story to open the editor.
Other scripts: npm run build (type-check + production bundle) · npm run preview (serve the
build) · npm run test (Vitest) · npm run publish:site -- <slug> (export one story as a
deploy-anywhere site).
The fastest path is the in-app editor (+ New story on Home):
- Upload your 3D scan — a
.glbmesh or a.sog/.ply/.splat/.ksplat/.spzGaussian splat. No scan yet? Capture one on a phone with Scaniverse or Polycam. - Fill in the details and add sections (text / image / audio / video), uploading any media inline.
- Frame the 3D scene and add waypoints, then point each section at one; hit ▶ Preview to check both modes. (The first section's waypoint is the opening view.)
- Click 💾 Save to gallery, then from Home select stories and ⬇ Export a self-contained website.
Already have an exported story? Click ⬆ Import story on Home to reopen the .zip or folder in
the editor — it comes back upgraded to the current format.
📖 Full walkthrough + the story.md format → Authoring a story.
Preparing a scan or a splat that loads upside down →
Gaussian splats & 3D models. Hosting (and re-importing) the exported
site → Publishing & sharing.
| Guide | What's in it |
|---|---|
| Why this exists | The problem, who it is for, the principles, and how the engine relates to the wider platform idea. |
| Authoring a story | The editor, hand-authoring, and the full story.md format reference. |
| Gaussian splats & 3D models | Supported formats, SuperSplat prep, the orientation fix, and hosting. |
| Publishing & sharing | Export a story as a static site and host it (Netlify, Vercel, and more). |
| Development | Setup, scripts, tests, debug flags, and where things live. |
| Engineering notes | Architecture decisions and hard-won findings — splat formats, frame pacing, renderer choice. |
| Roadmap | What is being worked on next. |
- React + Vite + TypeScript, hash-routed and fully static.
- One unified Three.js viewer for both GLB meshes and Gaussian
splats, with
camera-controlsfor hotspot-to-hotspot animation and Spark for splats (lazy-loaded — only splat stories pay for it). - No backend — story data and assets are plain files under
public/stories/.
Architecture and rationale live in the engineering notes.
In active development and usable today: the full loop — import a scan, author a story with waypoints and media, preview both modes, export a self-contained website — works end to end, and stories have been published live.
Rough edges are honest ones: accessibility is unaudited, the immersive view was designed on desktop and is workable rather than polished on phones, and the VR viewer is a generation behind the main one. See the roadmap.
MIT — see LICENSE. Use it, fork it, build a product on it; no permission needed.
The bundled third-party dependencies keep their own licences, reproduced in
public/THIRD-PARTY-NOTICES.txt — generated from the dependency tree at build time (so it does not
exist on a fresh clone until you run npm run dev or npm run build) and included in every
published site.
Pull requests are not currently accepted — see CONTRIBUTING.md. Issues and forks are welcome.

