From Marathon to 3D Globe: Engineering a 3-App Marvel Ecosystem
After months of dedication and a full MCU rewatch, I built a side project that’s anything but a weekend hack. It’s a three-application monorepo powered by a single shared dataset, designed to track, visualize, and explore the Marvel Cinematic Universe. The ecosystem includes a PWA viewing tracker, a 3D interactive globe, and a landing hub—all built with modern tools like React 19, Three.js, and TypeScript. Below, I break down the architecture, the tricky problems I solved, and what I’d do differently. Dive in!
What tech stack powers this three-app ecosystem?
The foundation is a pnpm monorepo with React 19, TypeScript 5.9, Vite 7.3, and Tailwind CSS v4. For state management and data syncing, the Marathon app uses Zustand with both localStorage and Supabase for persistence. The 3D globe relies on react-globe.gl and Three.js, while the Hub portal runs on Framer Motion for smooth animations. Localization is handled by i18next supporting English, French, and Spanish. All apps are deployed on Cloudflare Pages for fast global delivery. The entire dataset—83 locations and 160 projects—lives in a single JSON file, prebuilt and synced across all three apps to avoid redundant API calls. For more details, check the data sync section.

How does the Marathon PWA handle viewing tracking?
The Marathon app is a Progressive Web App (PWA) designed to track MCU viewing progress. It offers three view modes: a list, a grid, and a timeline. State is managed with Zustand and persisted to localStorage for offline support, while Supabase enables cross-device sync when online. Users can mark episodes or movies as watched, and the app automatically calculates overall progress. The PWA also includes service workers for caching, so you can start tracking even without a connection. The fuzzy search feature across 83 locations and 160 projects is implemented via a custom algorithm that weights partial matches—especially useful for the globe’s location search.
What interesting problems did you solve with the 3D globe?
The Map app renders a 3D globe using Three.js and react-globe.gl. One major challenge was pin clustering at different zoom levels. When zoomed out, location markers overlap, so I implemented a dynamic clustering algorithm that groups nearby pins and shows a count. As you zoom in, clusters break apart into individual pins. Another issue was WebGL fallback for devices without GPU support. I detect WebGL availability and fall back to a simple 2D canvas rendering of the globe, ensuring the app works on low-end devices. The globe also uses TopoJSON for country boundaries, and pin colors change based on the project phase (e.g., released, upcoming).
How do you sync data across three apps from a single JSON?
Data consistency is critical. All three apps—Marathon, Map, and Hub—share a single JSON dataset containing locations, projects, and metadata. During the build process, a prebuild script validates and transforms the JSON into app-specific formats (e.g., PWA requires progress trackers, globe needs geocoded coordinates). The script runs via pnpm’s recursive commands, generating static JSON files for each app. No runtime API calls are needed after deployment, which keeps load times fast. The monorepo structure with a shared packages/data folder ensures updates propagate everywhere. For future improvements, I’d add a CI pipeline to automate this process further.

How does fuzzy search work across 83 locations and 160 projects?
The search feature needed to handle typos and partial names (e.g., “aveng” should find “Avengers: Endgame”). I built a custom fuzzy matching algorithm that uses Levenshtein distance with weighted scoring. Locations and projects are indexed by name and aliases (e.g., “Stark Tower” also matches “Avengers Tower”). The algorithm ranks results by relevance and returns top matches in under 50ms, even with 243 entries. The search bar appears in both the Marathon and Map apps, with results clicking through to the relevant page. For the globe, a matched location triggers a camera animation to fly to that pin. This was one of the trickier parts because of the different data shapes—location versus project—but a unified index solved it.
What performance optimizations were crucial for this project?
Performance was a priority because three apps run on a single domain. Key optimizations include: code splitting with Vite for each app (so Hub doesn’t load Three.js unnecessarily), lazy loading of the globe component until the user visits the Map app, and tree shaking to remove unused i18next languages. For the PWA, I use localStorage caching of the dataset and progress state, so initial loads are instant even on slow networks. The globe’s pin clustering reduces draw calls, and WebGL fallback ensures older devices still render something usable. Cloudflare Pages’ CDN and edge caching further speed up static assets. Without these, the ecosystem would feel bloated—especially the globe, which can consume significant GPU memory.
What would you do differently if you started over today?
If I rebuilt this from scratch, I’d invest more time in automated testing. Currently, the fuzzy search and data sync rely on manual checks, and a few edge cases slipped through (e.g., a location with no matching project). I’d also use a graph database like Neo4j for the relationship-heavy MCU data instead of a flat JSON—it would make querying connections (e.g., “all characters in a location”) much easier. Finally, I’d add end-to-end tests with Playwright to cover user flows across all three apps, and set up a CI/CD pipeline that automatically rebuilds and deploys when the dataset changes. These improvements would make the ecosystem more maintainable and robust for future updates.
Related Articles
- Mastering Pull Request Performance: 5 Critical Strategies from GitHub's Engineering Team
- 7 Facts Every Developer Should Know About Bun After the Anthropic Acquisition
- Exploring CSS Color Palettes: Alternatives and Tools Beyond Tailwind
- Smart Cache-Busting for JSON and Static Assets Using PHP’s filemtime()
- ES Modules: The Architectural Trade-off That Splits JavaScript Ecosystem
- Native CSS Random Functions Now Live: End of Deterministic Design Era
- Enhancing Astro with MDX: A Q&A Guide
- 10 Key Strategies for Optimizing Diff Performance in GitHub Pull Requests