What this project is
One archive, served through several front doors. Knowing which piece your work touches is usually enough to find the code.
| Piece | What it does | Where |
|---|---|---|
| Interactive map | Time-based visualisation of hundreds of businesses with active / declining / closed states | / · components/MapboxMap.tsx |
| Business directory | Searchable, filterable listings synced with the map | components/StoryList.tsx |
| Memorial plaques | Physical plaque program for former business locations | /plaques |
| Outreach tracker | Admin tool for tracking contact with current property occupants about plaques | /admin/outreach |
| Museum exhibit | Touch-screen kiosk version of the map | /museum-exhibit · /exhibit-vision |
| Classroom workbook | Free education materials for ages 13–18 | /education |
| Frankfurt pilot | Extension of the model beyond Berlin | /frankfurt |
| Collaborate page | Public entry point for new collaborators | /collaborate |
Choose your track
You do not need to be a developer to contribute.
Build the platform
Verify the history
Place the plaques
Cross the languages
src/i18n/.Whatever your track, read the ground rules — they apply to everyone.
Development setup
Ten minutes from a clone to a map running locally.
- Node.js 20+ — the project runs Next.js 16 and React 19.
- pnpm — preferred; npm and yarn also work.
- A Mapbox account — for a free API token.
- Python 3.10+ — only if you are working on the data or scraping scripts.
git clone https://github.com/samfrons/storytimemaps.git
cd storytimemaps
pnpm install
# Environment
cp .env.example .env.local # or create .env.local manually
# Add: NEXT_PUBLIC_MAPBOX_TOKEN=your_token_here
# NEVER commit token values.
pnpm dev # http://localhost:3000Verify your setup
- The map loads at
/without console errors. - The time slider changes business marker states.
- Theme switching is instant. The default theme is
brutal-pop; try?theme=coolin the URL. pnpm run buildcompletes without errors.
Codebase map
The naming is consistent — components in PascalCase, hooks as useX, utilities in camelCase — so grep finds most things faster than a directory tree does.
src/
app/
page.tsx # Main map page (URL param handling — be careful here)
layout.tsx # ThemeProvider config — DO NOT change its settings
globals.css # Theme color definitions (CSS variables)
components/ # All React components (PascalCase)
outreach/ # Outreach tracker UI
admin/outreach/ # Outreach admin page
api/outreach/ # Outreach data API
collaborate/ # Public collaborator page
lib/types/outreach.ts # Outreach data model
i18n/ # Translations (en/de/yi/he)
public/
data/ # All HTTP-served data files (timeline JSON, CSV)
docs/ # Guides (theming, outreach, tasks, this file)Key documents, in reading order:
CLAUDE.md— the rulebook. Non-negotiable design, theme and deployment rules.- Theming & routing — the theme system deep dive.
- Outreach guide — the outreach pipeline and workflow.
- Task tracking — how work is organised and claimed.
- Style guide and performance rules — the specifics.
Contribution workflow
Claim a task
Open a GitHub issue — or comment on an existing one — so that two people never do the same work. The task tracking guide explains where each kind of work is queued.Branch
git checkout -b <type>/<short-description>— for examplefeat/plaque-filter.Develop
Follow the ground rules below and the patterns of the components already in the file you are working near.Run the pre-commit checklist
Everything in the checklist below has to pass before you commit. It is the same list the maintainer checks a pull request against.Commit
Conventional commits:feat:,fix:,perf:,style:,refactor:,docs:.Open a pull request
Describe what changed and which themes and pages you tested it on.
Pre-commit checklist
pnpm run buildpasses with no errors.- No TypeScript errors —
npx tsc --noEmit. - No border-radius anywhere; every colour comes from a CSS variable.
- All themes tested: brutal-pop (the default) plus moody, hot, cold, warm, cool, bauhaus, art-nouveau, archival and hoefe.
- Components memoised; any page using
useSearchParams()wrapped in<Suspense>.
Ground rules
The short version of CLAUDE.md. Where the two disagree, the full file wins.
| Rule | What it means |
|---|---|
| No rounded corners | Sharp rectangular edges everywhere, unless explicitly asked for. |
| No hardcoded colours | Always var(--variable). The only exception is Mapbox layer styling, which needs hex values — take those from the theme functions. Ten themes exist; brutal-pop is the default and doubles as the museum-exhibit kiosk palette. |
| No blue focus outlines | Build custom focus states from border or background changes instead. |
| Do not touch the theme system | Read the theme rules in CLAUDE.md and the theming guide first. |
| Typography | Space Mono for data, labels and technical text; Inter for body copy. |
| Performance | React.memo() for components that receive props, useMemo for expensive work, scroll throttled to 100–150 ms, inputs debounced to 300–500 ms, heavy components dynamically imported. |
| Suspense | Any page touching useSearchParams() — including indirectly, through Sidebar — must be wrapped in a Suspense boundary, or the production build fails. |
| Static files | Everything served over HTTP lives in /public/. Nothing else is served at all. |
Historical sensitivity
This is a memorial project. Every contributor, on every track, agrees to the following.
Getting help
- Questions about rules or architecture: open a GitHub issue with the
questionlabel. - Project coordination and outreach access: contact the maintainer — the repo owner.
- Where things are: grep is your friend. The codebase follows consistent naming throughout.
Welcome aboard — thank you for helping preserve this history.