DEV Community

Cover image for What Did Earth Look Like When You Were Born? I Built It to Find Out
Sushant Rahate
Sushant Rahate

Posted on

What Did Earth Look Like When You Were Born? I Built It to Find Out

DEV Weekend Challenge: Earth Day

🌍 I Built a Climate Time Machine That Makes CO₂ Data Feel Personal

This is a submission for Weekend Challenge: Earth Day Edition


The problem with climate data is that it's abstract. "+1.25°C" doesn't land. "427 ppm CO₂" doesn't hurt. Numbers don't move people — stories do.

So I asked a different question: what if you could see exactly what the planet looked like the year you were born?

That's Earth Time Machine. Enter your birth year. Feel what changed.


What I Built

Earth Time Machine is an interactive climate data experience that makes global warming feel personally relevant by anchoring every metric to your birth year.

The core idea

Instead of showing abstract global averages, it asks: what was CO₂ when you were born? How much Arctic ice existed? How many more people share this planet now? Every number becomes a story about your lifetime.

Features

  • 🌐 Animated 3D globe in the hero — hand-coded in Canvas 2D, no WebGL library. The globe visibly browns and cracks based on the CO₂ rise since your birth year. Born in 1960? Healthy green oceans. 2008? Noticeably warmer.

  • 💨 CO₂ breathing animation — the number counts up from zero to your lifetime rise, then locks and pulses gently like Earth exhaling. No looping. It arrives and stays.

  • 📊 6 planetary vital sign cards — CO₂, temperature, Arctic sea ice, forest cover, population, sea level. Each has a sparkline, animated thermometers, comparison bars, and a "sting" — a one-line gut-punch fact that reveals after you've absorbed the numbers.

  • 🌍 20-country local data — select your country and see local warming vs world average, per-capita CO₂ then vs now, and your country's climate risk tier. India's local warming hits differently than Canada's.

  • 🔮 4 IPCC scenario projections to 2100 — actual path, Paris 1.5°C, moderate action, worst case. Canvas-drawn chart showing where each path leads. Interactive tabs with verdicts.

  • 🌿 Paris Path toggle — overlays what values should be if the Paris Agreement was met. The gap is sobering.

  • 🎮 Generational compare mode — enter two birth years, see the CO₂ absorbed between them, the temperature difference, the ice lost. Perfect for showing a parent vs child what Earth each of them inherited.

  • 🎯 Climate knowledge quiz — 4 randomised questions from a pool of 8. Immediate feedback, score bar, explanations. Keeps people engaged after the data shock.

  • 🔊 Ambient sound — two sine-wave oscillators through the Web Audio API. As you drag the year scrubber, the pitch shifts — deeper in early decades, eerier as CO₂ rises. Subtle but effective.

  • 📥 Download your Earth Identity Card as PNG via html2canvas. Share your personal climate report on social media.

  • 🌐 Live CO₂ clock in the sticky toolbar — ticks upward in real time at the actual rate (2.4 ppm/year). Watch it move.


Demo

🔗 https://earth-time-machine.vercel.app/

Try entering:

  • 1960 — see a relatively healthy planet, then watch how much changed
  • 1990 — the year of the Earth Summit, Rio. CO₂ was already rising fast
  • 2005 — the year Arctic ice hit a then-record low

Drag the year scrubber slowly from 1950 to 2026. Watch the CO₂ pill change. Listen to the drone shift. Find 2007 — the year the Arctic shattered its record.


Code

React + Vite

This template provides a minimal setup to get React working in Vite with HMR and some ESLint rules.

Currently, two official plugins are available:

React Compiler

The React Compiler is not enabled on this template because of its impact on dev & build performances. To add it, see this documentation.

Expanding the ESLint configuration

If you are developing a production application, we recommend using TypeScript with type-aware lint rules enabled. Check out the TS template for information on how to integrate TypeScript and typescript-eslint in your project.




Built with:

  • React 19 + Vite 6
  • Tailwind CSS v4 (single @import "tailwindcss" — no config file)
  • Canvas 2D for the globe and all charts (no chart library)
  • Web Audio API for ambient sound
  • html2canvas for PNG card export

Zero external data dependencies. All climate datasets (CO₂, TEMP, ICE, POP, FOREST, SEA) are bundled from NOAA, NASA GISTEMP, NSIDC, FAO, and UN World Population Prospects.


How I Built It

The globe

The hardest part was making the hero globe look like Earth — not a red blob, not random ellipses.

The breakthrough was switching from position: absolute painted ellipses to actual spherical projection math. Each continent is defined as a series of [lat, lon] pairs. A project(lat, lon, t) function maps them onto the 2D canvas using:

function project(lat, lon, t) {
  const phi    = (lat * Math.PI) / 180;
  const lam    = (lon * Math.PI) / 180 + t * 0.35; // t = spin time
  const cosLat = Math.cos(phi);
  const x3d    = cosLat * Math.sin(lam);
  const y3d    = Math.sin(phi);
  const z3d    = cosLat * Math.cos(lam);
  if (z3d < -0.05) return null; // back-face cull
  return [cx + x3d * r, cy - y3d * r];
}
Enter fullscreen mode Exit fullscreen mode

Back-face culling (z3d < -0.05) means continents on the far side of the globe simply don't draw. The spin is smooth because t increments 0.008 per frame via requestAnimationFrame.

The heat factor maps CO₂ rise since your birth year onto a 0 → 0.58 value that shifts:

  • Ocean colour (vivid blue → murky)
  • Land colour (forest green → reddish-brown)
  • Ice cap size (large → tiny)
  • Atmosphere halo (blue-cyan → dimmer)
let heat = 0;
if (birthYear > 0) {
  const rise = interp(CO2, 2026) - interp(CO2, birthYear);
  heat = Math.min(rise / 90, 0.58);
}
Enter fullscreen mode Exit fullscreen mode

The CO₂ number

The original version looped through seasonal offsets forever, making the number tick up and down endlessly. That felt anxious and wrong.

The fix: count up once with a cubic ease-out, then lock the value and add a pure CSS pulse — no more JS interval touching the text:

const tick = (now) => {
  const p    = Math.min((now - start) / 1800, 1);
  const ease = 1 - Math.pow(1 - p, 3);
  el.textContent = `+${(target * ease).toFixed(1)}`;
  if (p < 1) requestAnimationFrame(tick);
  else {
    el.textContent = `+${target.toFixed(1)}`; // locked
    el.classList.add('breathing');             // CSS pulse only
  }
};
Enter fullscreen mode Exit fullscreen mode
@keyframes co2Breathe {
  0%, 100% { transform: scale(1);      filter: drop-shadow(0 0 0px rgba(192,64,48,0)); }
  50%       { transform: scale(1.028); filter: drop-shadow(0 0 18px rgba(192,64,48,.45)); }
}
Enter fullscreen mode Exit fullscreen mode

Gentle, readable, and the number never changes again.

Sound design

Web Audio API is blocked until a user gesture (browser policy). The fix is audioCtx.resume() which returns a Promise — you must wait for it:

const toggle = () => {
  ctx.resume().then(() => {
    gain.gain.cancelScheduledValues(ctx.currentTime);
    gain.gain.setTargetAtTime(0.6, ctx.currentTime, 0.4);
    updatePitch(CO2[2026]);
  });
};
Enter fullscreen mode Exit fullscreen mode

Two oscillators detuned by 2.5Hz create a warm beating effect. As the year scrubber moves, setTargetAtTime glides the frequency smoothly rather than jumping:

function updatePitch(co2) {
  const t    = Math.max(0, Math.min((co2 - 310) / 120, 1));
  const freq = 45 + t * 30; // 45Hz clean → 75Hz tense
  osc1.frequency.setTargetAtTime(freq, ctx.currentTime, 1.5);
  osc2.frequency.setTargetAtTime(freq * 1.045, ctx.currentTime, 1.5);
}
Enter fullscreen mode Exit fullscreen mode

Architecture

The whole app is structured around a single truth: birthYear. Everything derives from it.

App.jsx
├── birthYear (state) ─────────────────────────────┐
├── Hero           → onReveal(year) sets birthYear  │
├── ShockSection   ← birthYear                      │
├── Toolbar        ← country, toggles               │
├── CountryPanel   ← birthYear + country            │
├── Scrubber       ← birthYear + sliderYear         │
├── ScenarioChart  ← birthYear + scenario           │
├── MetricCards    ← birthYear + country + modes    │
├── CompareSection ← birthYear                      │
├── Quiz           ← birthYear (resets questions)   │
├── Timeline       ← birthYear (filters events)     │
└── ShareCard      ← birthYear + country            │
                   ────────────────────────────────┘
Enter fullscreen mode Exit fullscreen mode

No Redux, no Zustand. Just useState in App.jsx with props passed down. The data layer is pure JS objects — no API calls, no loading states, instant renders.

The data

All datasets are annual means from primary sources, hand-curated and interpolated:

// Linear interpolation across sparse data
export function interp(data, year) {
  if (data[year] !== undefined) return data[year];
  const keys = Object.keys(data).map(Number).sort((a, b) => a - b);
  // find surrounding years and lerp
  const t = (year - lo) / (hi - lo);
  return data[lo] + t * (data[hi] - data[lo]);
}
Enter fullscreen mode Exit fullscreen mode

This means every year from 1950–2026 returns an accurate value even if we only store data every 5 years.

What I'd do with more time

  • SVG world map with country-level temperature choropleth
  • Real Keeling Curve seasonal animation (actual monthly NOAA data)
  • Share to Twitter/X with OpenGraph preview card
  • Offline PWA support — the whole app works without internet anyway

Design Philosophy

The biggest design decision wasn't technical — it was emotional.

Climate data is usually presented as a problem to solve. Numbers, charts, policy. It lands as guilt or numbness.

Earth Time Machine tries a different frame: this happened in your lifetime. You were there. The CO₂ that rose, rose while you were alive. The ice that melted, melted while you watched.

That's not guilt — it's stakes. And stakes make people want to act.

The dark brown/forest green colour palette is intentional. Earth tones. Soil. The feeling of something organic under stress, not cold data on white.

The CO₂ number that arrives and pulses — not loops — is intentional. It's not a process. It's a fact. It stays.


Prize Categories

Not submitting to sponsored prize categories — this project uses no third-party AI APIs, authentication services, or blockchain infrastructure. Just the web platform, open climate data, and a weekend.


Data sources: NOAA Mauna Loa (CO₂), NASA GISTEMP v4 (temperature), NSIDC Sea Ice Index (Arctic ice), FAO FRA 2025 (forest), UN World Population Prospects (population), CSIRO/Church & White 2011 (sea level). All datasets are annual means verified against primary sources.

Built over one weekend for Earth Day 2026. The planet deserved better tooling.

Top comments (0)