mytravelrecap
Your travel recap from Google Maps Timeline — 100% client-side, private, and open source.
A single-page static site that turns your Google Takeout timeline into a typography-first story: scroll-fade animations, vector infographics, and an interactive map — all running in your browser. No data is sent to any server.
- Features
- Quick start
- Usage
- Caching (local storage)
- Configuration
- Project structure
- Development
- Deployment
- Privacy & design
- Tech stack
- Contributing
- License
| Typography-first layout | Large, bold stats that tell your travel story as you scroll |
| Scroll animations | Content fades in from background to foreground with IntersectionObserver |
| Vector infographics | Subtle globe and journey-line illustrations |
| Travel stats | Total distance, unique places, countries visited, check-ins |
| Time & records | Moving vs stationary time, personal records |
| Environmental impact | Estimated carbon footprint and tree offset |
| Interactive map | Visualize timeline visits by uploading your JSON |
| Privacy-first | 100% client-side; no server uploads |
The site is 100% static: CSS is committed, so no build is required to run or deploy.
git clone https://github.com/imarinzone/mytravelrecap.git
cd mytravelrecap
npx serveOpen http://localhost:3000 (or the port shown). No npm install or build step needed — open index.html with any static server (or even file:// for basic use).
- Export your timeline (device-specific):
- Android: Settings → Location → Location Services → Timeline → Export Timeline data → choose Google Drive or local folder → Save. Wait for the export notification.
- iOS: Google Maps → profile picture → Settings → Personal Content → Location settings → Export Timeline data → Save to Files. The file is usually named
location-history.json.
- Open the app in your browser.
- Click Choose File in “Upload Your Timeline Data” and select the exported file.
- The map and stats will update with your locations and metrics.
Country names are resolved offline using data/countries.geojson (point-in-polygon, no APIs). The file is committed; to update it, replace it with a world countries GeoJSON and save as data/countries.geojson.
- IndexedDB (non-PII): Country boundaries GeoJSON and world globe GeoJSON are cached in the browser so repeat visits skip re-downloading. Stored under the database
mytravelrecap_geodata. If IndexedDB is unavailable (e.g. private browsing), the app falls back to fetching from the network. - localStorage (small prefs): Theme (light/dark), selected map year, and map style are stored so your choices persist across sessions. No timeline or location data is persisted.
App behaviour is controlled by config.js (loaded before script.js). Edit it to change:
| Key | Default | Description |
|---|---|---|
MARKER_CLUSTER_THRESHOLD |
500 | Use marker clustering above this many map locations |
HEATMAP_THRESHOLD |
500 | Use heatmap layer above this many locations (single canvas) |
PROBABILITY_THRESHOLD |
0 | Min visit probability (0–1) to include when parsing; visits below are excluded (e.g. 0.2 to drop low-confidence places) |
MARKER_BATCH_SIZE |
200 | Markers added per frame when batching (keeps UI responsive) |
SHARE_IMAGE_WIDTH / SHARE_IMAGE_HEIGHT |
1080 × 1920 | Share image dimensions (fixed layout) |
DATA_DEMO_URL |
data/demo.json |
URL for demo data (e.g. “Try demo”) |
GEOJSON_COUNTRIES_URL |
data/countries.geojson |
URL for country boundaries GeoJSON |
GOOGLE_PLACES_API_KEY |
(empty) | Optional. Google Cloud API key with Places API (New) enabled. When set, the "Your Favorite Spots" section fetches and shows place address and photo. The "Open in Google Maps" link works without a key. Restrict the key to your domain and to Places API (New) only. |
Edit config.js and refresh. For production (e.g. Vercel), do not commit the key: set the environment variable GOOGLE_PLACES_API_KEY in your host’s dashboard (e.g. Vercel → Project → Settings → Environment Variables). The build script injects it into config.js at deploy time.
The app uses a central logger (timelineUtils.Logger in timeline-utils.js) for info, warnings, and errors, and registers global handlers for uncaught errors and unhandled promise rejections so they are logged in the console. In production you can open the browser devtools console to inspect failures (e.g. script load errors, map/globe load failures, Places API or share errors). To send logs to an external service, you can replace or extend the Logger implementation in timeline-utils.js or hook into window.onerror / unhandledrejection in your own script.
mytravelrecap/
├── index.html # Single-page app
├── config.js # App config (map thresholds, share image size, data URLs)
├── script.js # UI, map, and DOM logic
├── timeline-utils.js # Parsing, stats, geo helpers
├── geodata-cache.js # IndexedDB cache for GeoJSON (countries, world globe)
├── tailwind.css # Built Tailwind v4 CSS (committed; one-command: npm run build)
├── src/tailwind.css # Tailwind v4 source (@import "tailwindcss", @source, keyframes)
├── vercel.json # Headers (no build step)
├── data/
│ └── countries.geojson
├── components/
├── tests/
└── README.md
npm install
npm test # Jest tests for timeline-utils
npm run build # One command: rebuild tailwind.css (after editing src/tailwind.css or content)
npm run watch:css # Watch Tailwind while developing- Tests: Core logic lives in
timeline-utils.js; tests are intests/. - Tailwind: Tailwind v4 via
@tailwindcss/cli.tailwind.cssis committed. To regenerate after editingsrc/tailwind.css, runnpm run build(one command), then commit. No build runs on deploy. - Localhost: Vercel Speed Insights is disabled on localhost to keep the console clean.
Optimized for Vercel:
- Build:
npm run build(injects env intoconfig.js, then builds Tailwind →tailwind.css). - Headers: Custom
Permissions-Policyinvercel.json(nobrowsing-topics). - Output: Static files from repo root.
- Google Places API (production): In Vercel → Project → Settings → Environment Variables, add
GOOGLE_PLACES_API_KEYwith your key (Production, and optionally Preview). The key is written intoconfig.jsduring the build and is not committed to the repo.
Push to your connected repo; set GOOGLE_PLACES_API_KEY in Vercel if you want Places features in production.
- Client-only: Your timeline file is parsed in the browser. Nothing is uploaded.
- Local storage: Only non-PII is persisted: GeoJSON reference data (IndexedDB) and small preferences like theme and map year (localStorage). Your timeline, segments, and visited locations are never written to disk; they live in memory for the session only.
- Storytelling UI: Large type, vertical scroll, fade-in sections, SVG backgrounds, and dark mode support.
| Category | Tools |
|---|---|
| Markup & styling | HTML5, Tailwind CSS v4 (committed tailwind.css; one-command rebuild: npm run build) |
| Logic | Vanilla JavaScript |
| Maps | Leaflet.js, CartoDB tiles |
| Animations | IntersectionObserver, CSS keyframes |
| Testing | Jest |
Contributions are welcome.
- Open an issue for bugs or ideas.
- Fork the repo, create a branch, and open a pull request.
- Run
npm testbefore submitting.
This project is licensed under the MIT License — see the LICENSE file for details.