A 3D landscape of your own thinking — a portfolio you fly through instead of scroll. Every project is a node sized by how much it mattered; every conversation you ever had with an AI becomes a dot in the cloud around them. Ask it questions, filter by era, watch it narrate itself.
It ships with a synthetic demo so it runs the moment you clone it, and it's built so the code carries no data — you point it at your own, hosted wherever you like.
npm install
npm run devOpen the local URL. You'll see the landscape of a fictional maker, Sam Rivers
— eight projects and 168 conversations of invented data from
public/sample-data/. Nothing to configure.
To build:
npm run build # → dist/
npm run preview # serve the build locallyEverything a forker touches lives in one file: datascape.config.js.
export const config = {
siteName: "Datascape", // wordmark, tab title, breadcrumbs
tagline: "a spatial index of thoughts",
dataBase: import.meta.env.VITE_DATA_BASE || "/sample-data/", // ← your data
surface: "public", // "public" (portfolio) | "observatory" (everything)
author: { name: "", url: "" },
};siteName rebrands the whole site (HUD, the 3D wordmark, breadcrumbs). Counts
like "168 thoughts · 24 months" are read from your data, never hardcoded.
This is the point of the template. The engine imports nothing from a data
folder; instead it fetches a set of JSON files from config.dataBase at boot,
then renders. So the code is yours to publish and the data is yours to keep
anywhere.
dataBase is just a folder (local path or absolute URL) containing:
| file | what it is | required |
|---|---|---|
content.json |
your project list + categories | ✔ |
thoughts.json |
the conversation dots (title, month, cluster, links) | ✔ |
provenance.json |
which conversations seeded which project | ✔ |
corpus.json |
monthly voice metrics, skill lexicon, motifs | ✔ |
evidence.json |
git facts per project (commits, dates, languages) | ✔ |
creed.json |
a centerpiece statement + borrowed quotes | ✔ |
mirrors.json |
self-perception gauges (optional, illustrative) | ✔ |
becoming.json |
aspiration vs trajectory | ✔ |
featured.json |
precomputed answers for the "ask anything" bar | ✔ |
git-history.json |
per-commit cadence | optional |
The shipped public/sample-data/ is a complete, valid example of all of them —
copy it, read the shapes, and replace piece by piece.
A. Hand-authored / scripted (easiest). Regenerate the whole synthetic set any time:
node scripts/make-sample.mjs # writes public/sample-data/Read that script — it's the clearest spec of every file's shape. Edit the persona, projects, and thoughts to your own, or write your own generator.
B. From your real ChatGPT export (the full experience).
The scripts/ pipeline turns an actual export into the data files. It writes to
public/data/ (gitignored — your real corpus never commits):
# 1. Request your data from ChatGPT (Settings → Data controls → Export).
# Unzip conversations-*.json into .data/gpt-export/
# 2. Author your project list: copy public/sample-data/content.json →
# public/data/content.json and edit it to describe your projects.
# 3. Point the repo maps at your local project folders:
# edit the REPOS map at the top of scripts/build-evidence.mjs & git-walk.mjs
node scripts/build-evidence.mjs # git facts → public/data/evidence.json
node scripts/git-walk.mjs # cadence → public/data/git-history.json
node scripts/build-corpus.mjs # the corpus → public/data/{thoughts,provenance,corpus}.jsonThen set dataBase: "/data/" in the config (or upload public/data/ to a host,
below).
Privacy: build-corpus scrubs a list of terms you control
(.data/private-terms.json, gitignored) from every shipped string, and fully
abstracts conversations it judges personal. Nothing but titles and short quotes
ever leaves your machine — full message bodies are never written to the output.
For a live site whose code is public but whose data lives apart, host the JSON
on Cloudflare and point dataBase at it.
# one-time: install wrangler and create an R2 bucket
npm i -g wrangler
wrangler r2 bucket create my-datascape-data
# upload your data folder
node scripts/deploy-data.mjs public/data # or public/sample-data for the demoSet a CORS policy on the bucket so the browser may fetch it — see
docs/cors.json. Then, with the bucket exposed at a public
URL (an R2 custom domain or a Pages/Worker in front):
// datascape.config.js
dataBase: "https://data.your-domain.com/",The app itself deploys as static files (dist/) to Cloudflare Pages, Netlify,
GitHub Pages, anywhere. Data host and app host are independent.
Press / and ask. Three tiers, in order:
- Featured — precomputed answers in
featured.json(instant, free). - Local grammar — a small parser that turns phrases into filters/camera moves.
- Live LLM (optional) — set
liveNavigatorUrlin the config to an endpoint you host (scripts/navigator-server.mjsis a starting point). It never runs for a visitor unless you turn it on — no surprise cloud bills.
src/main.jsx statically imports only the store and config. It calls
loadData(config.dataBase), and only after the data resolves does it
dynamically import the app. Because of that ordering, every module — even
src/data/nodes.js, which builds the node graph at import time — evaluates with
data already present. No async plumbing leaks into the rest of the engine.
main.jsx ──▶ loadData() ──▶ store populated ──▶ import('./App.jsx') ──▶ render
Swap the data source, rebrand in one file, and the whole landscape is someone else's.
Inspired by “topology of thoughts” by poet.engineer. Built on Vite + React + react-three-fiber. Originally the engine behind a personal portfolio; generalized into a template. MIT licensed — make something that's unmistakably yours.
