Discovering New Worlds
Is there another planet out there Earth-like enough to support life?
Every second, the Sun turns about 600 million tons of hydrogen into helium. That's where sunlight comes from.
An Interactive High School Textbook
Will Pollard
November 6, 2026
Earth & Space Science · Interactive Textbook
Hundreds of earthquakes shook the planet in the past 7 days. Every dot on this globe is one of them, live from the U.S. Geological Survey. Why do they line up like that? Let's find out.
lpGlobe = {
const SIZE = 440;
const dpr = Math.min(window.devicePixelRatio || 1, 2);
const reduceMotion = window.matchMedia("(prefers-reduced-motion: reduce)").matches;
const quakes = lpQuakes || [];
const now = Date.now();
const DAY = 864e5;
const wrap = html`<div class="lp-globe-wrap"></div>`;
const canvas = html`<canvas width=${SIZE * dpr} height=${SIZE * dpr} role="img"
aria-label="Spinning globe showing every magnitude 2.5 and larger earthquake from the past 7 days, from the USGS live feed. Most dots line up along plate boundaries, like the Ring of Fire around the Pacific Ocean."></canvas>`;
const info = html`<div class="lp-globe-info" aria-live="polite"></div>`;
const legend = html`<div class="lp-globe-legend" aria-hidden="true">
<span><i style="background:#fde047"></i>Last 24 hours</span>
<span><i style="background:#f97316"></i>Earlier this week</span>
<span>Bigger dot = stronger quake</span></div>`;
const src = html`<p class="lp-source">Live data: <a href="https://earthquake.usgs.gov/earthquakes/map/" target="_blank" rel="noopener">USGS Earthquake Hazards Program</a>. ${reduceMotion ? "Drag" : "Drag to spin"}, or tap a dot.</p>`;
wrap.append(canvas, info, legend, src);
const ctx = canvas.getContext("2d");
ctx.scale(dpr, dpr);
const projection = d3.geoOrthographic().fitExtent([[6, 6], [SIZE - 6, SIZE - 6]], {type: "Sphere"}).precision(0.5);
const path = d3.geoPath(projection, ctx);
const graticule = d3.geoGraticule10();
const R = projection.scale();
const radius = m => 1.6 * Math.pow(1.65, m - 2.5) + 0.6;
const ago = t => {
const h = (now - t) / 36e5;
if (h < 1) return "less than an hour ago";
if (h < 24) return `${Math.round(h)} hour${Math.round(h) === 1 ? "" : "s"} ago`;
const d = Math.round(h / 24);
return `${d} day${d === 1 ? "" : "s"} ago`;
};
// Headline count + default caption
const strongest = quakes[quakes.length - 1];
const countEl = document.getElementById("lp-quake-count");
if (countEl && quakes.length) countEl.textContent = quakes.length.toLocaleString();
const defaultInfo = () => {
if (!lpQuakes) { info.innerHTML = "Live earthquake data couldn't load right now. Check back later!"; return; }
if (!strongest) { info.innerHTML = "No earthquakes in the feed right now."; return; }
info.innerHTML = `Strongest this week: <b>M${strongest.mag.toFixed(1)}</b>, ${strongest.place} (${ago(strongest.time)})`;
};
defaultInfo();
// Start centered on the strongest quake so it's front and center
let rotation = strongest ? [-strongest.lon, -Math.max(-35, Math.min(35, strongest.lat)) * 0.6] : [74, -20];
let selected = null;
let lastInteract = -Infinity;
function draw(t) {
projection.rotate(rotation);
const [cx, cy] = projection.translate();
ctx.clearRect(0, 0, SIZE, SIZE);
// Atmosphere glow
const glow = ctx.createRadialGradient(cx, cy, R * 0.92, cx, cy, R * 1.04);
glow.addColorStop(0, "rgba(56,189,248,0.35)");
glow.addColorStop(1, "rgba(56,189,248,0)");
ctx.fillStyle = glow;
ctx.beginPath(); ctx.arc(cx, cy, R * 1.04, 0, 2 * Math.PI); ctx.fill();
// Ocean
const ocean = ctx.createRadialGradient(cx - R * 0.35, cy - R * 0.35, R * 0.1, cx, cy, R);
ocean.addColorStop(0, "#1e4f8f");
ocean.addColorStop(1, "#0a1f45");
ctx.beginPath(); path({type: "Sphere"}); ctx.fillStyle = ocean; ctx.fill();
ctx.beginPath(); path(graticule); ctx.strokeStyle = "rgba(148,163,184,0.16)"; ctx.lineWidth = 0.6; ctx.stroke();
if (lpLand) {
ctx.beginPath(); path(lpLand);
ctx.fillStyle = "#2f6f5e"; ctx.fill();
ctx.strokeStyle = "rgba(226,232,240,0.35)"; ctx.lineWidth = 0.5; ctx.stroke();
}
// Quakes on the visible side of the globe
const center = [-rotation[0], -rotation[1]];
for (const q of quakes) {
if (d3.geoDistance([q.lon, q.lat], center) > Math.PI / 2 - 0.02) continue;
const [x, y] = projection([q.lon, q.lat]);
const r = radius(q.mag);
const recent = now - q.time < DAY;
if (recent && !reduceMotion) {
const phase = ((t / 1600) + (q.time % 1000) / 1000) % 1;
ctx.beginPath(); ctx.arc(x, y, r + phase * 14, 0, 2 * Math.PI);
ctx.strokeStyle = `rgba(253,224,71,${0.7 * (1 - phase)})`; ctx.lineWidth = 1.5; ctx.stroke();
}
ctx.beginPath(); ctx.arc(x, y, r, 0, 2 * Math.PI);
ctx.fillStyle = recent ? "rgba(253,224,71,0.92)" : "rgba(249,115,22,0.72)";
ctx.fill();
ctx.strokeStyle = "rgba(15,23,42,0.6)"; ctx.lineWidth = 0.6; ctx.stroke();
}
if (selected && d3.geoDistance([selected.lon, selected.lat], center) < Math.PI / 2) {
const [x, y] = projection([selected.lon, selected.lat]);
ctx.beginPath(); ctx.arc(x, y, radius(selected.mag) + 5, 0, 2 * Math.PI);
ctx.strokeStyle = "#ffffff"; ctx.lineWidth = 2; ctx.stroke();
}
// Soft shading on the night edge
const shade = ctx.createRadialGradient(cx - R * 0.3, cy - R * 0.3, R * 0.5, cx, cy, R);
shade.addColorStop(0, "rgba(2,6,23,0)");
shade.addColorStop(1, "rgba(2,6,23,0.45)");
ctx.beginPath(); path({type: "Sphere"}); ctx.fillStyle = shade; ctx.fill();
}
// Drag to spin (mouse + touch)
const toLocal = e => {
const b = canvas.getBoundingClientRect();
return [(e.clientX - b.left) * SIZE / b.width, (e.clientY - b.top) * SIZE / b.height];
};
let dragStart = null, rotStart = null, moved = false;
canvas.addEventListener("pointerdown", e => {
dragStart = toLocal(e); rotStart = rotation.slice(); moved = false;
canvas.setPointerCapture(e.pointerId);
});
canvas.addEventListener("pointermove", e => {
if (!dragStart) return;
const p = toLocal(e);
const dx = p[0] - dragStart[0], dy = p[1] - dragStart[1];
if (Math.hypot(dx, dy) > 4) moved = true;
const k = 90 / R;
rotation = [rotStart[0] + dx * k, Math.max(-70, Math.min(70, rotStart[1] - dy * k))];
lastInteract = performance.now();
if (reduceMotion) draw(0);
});
canvas.addEventListener("pointerup", e => {
const p = toLocal(e);
dragStart = null;
lastInteract = performance.now();
if (moved) return;
// Tap: pick the nearest visible quake
const center = [-rotation[0], -rotation[1]];
let best = null, bestD = 22;
for (const q of quakes) {
if (d3.geoDistance([q.lon, q.lat], center) > Math.PI / 2) continue;
const [x, y] = projection([q.lon, q.lat]);
const d = Math.hypot(x - p[0], y - p[1]) - radius(q.mag);
if (d < bestD) { bestD = d; best = q; }
}
selected = best;
if (best) info.innerHTML = `<b>M${best.mag.toFixed(1)}</b> · ${best.place} · ${ago(best.time)}`;
else defaultInfo();
if (reduceMotion) draw(0);
});
canvas.addEventListener("pointercancel", () => { dragStart = null; });
// Animate only while the globe is on screen
let timer = null, last = null;
const tick = elapsed => {
if (last !== null && !dragStart && performance.now() - lastInteract > 2500) {
rotation[0] += (elapsed - last) * 0.006;
}
last = elapsed;
draw(elapsed);
};
const start = () => { if (!timer) { last = null; timer = d3.timer(tick); } };
const stop = () => { if (timer) { timer.stop(); timer = null; } };
if (reduceMotion) {
draw(0);
} else {
const io = new IntersectionObserver(([e]) => e.isIntersecting ? start() : stop());
io.observe(canvas);
invalidation.then(() => { io.disconnect(); stop(); });
}
return wrap;
}This book follows your class packets, so you can catch up or review on your own.
Each unit starts with a question scientists are still working on. Tap a unit to see the big picture, or jump straight to a chapter.
Is there another planet out there Earth-like enough to support life?
Every second, the Sun turns about 600 million tons of hydrogen into helium. That's where sunlight comes from.
Are we alone in the universe?
4.5 billion years and 5 mass extinctions came before humans showed up. What took so long?
Who is most at risk, and why do people live there anyway?
About 800 million people live within 100 km of an active volcano.
What do scientists see in the data that makes them so sure humans are the cause?
2024 was the warmest year in NASA's record, which goes back to 1880.
Will NYC get more frequent and more intense storms?
In 2012, Hurricane Sandy pushed seawater into NYC's subway tunnels.
How can we meet our needs without wrecking the planet?
Air pollution is linked to about 7 million early deaths worldwide every year (WHO).
Each stripe is one year, from 1880 to 2025. Blue years were cooler than average, red years were warmer. Drag or tap across it.
lpStripes = {
// NASA GISTEMP v4 annual global surface temperature anomaly (°C vs. 1951–1980), 1880–2025.
// Same series as Unit 4's Earth–Sun Dynamics chapter.
const anomalies = [-0.17,-0.09,-0.11,-0.18,-0.28,-0.33,-0.31,-0.36,-0.18,-0.11,-0.36,-0.22,-0.27,-0.31,-0.3,-0.23,-0.11,-0.11,-0.28,-0.18,-0.08,-0.16,-0.28,-0.37,-0.47,-0.26,-0.22,-0.38,-0.42,-0.48,-0.44,-0.44,-0.37,-0.34,-0.15,-0.14,-0.36,-0.46,-0.3,-0.27,-0.27,-0.18,-0.28,-0.26,-0.27,-0.22,-0.11,-0.22,-0.2,-0.36,-0.15,-0.09,-0.16,-0.28,-0.13,-0.2,-0.15,-0.03,0.0,-0.02,0.12,0.18,0.06,0.09,0.2,0.09,-0.07,-0.03,-0.11,-0.11,-0.17,-0.07,0.01,0.08,-0.13,-0.14,-0.19,0.05,0.06,0.03,-0.02,0.06,0.03,0.05,-0.2,-0.11,-0.06,-0.02,-0.08,0.05,0.03,-0.08,0.01,0.16,-0.07,-0.01,-0.1,0.18,0.07,0.16,0.26,0.32,0.14,0.31,0.16,0.12,0.18,0.32,0.39,0.27,0.45,0.41,0.22,0.23,0.31,0.45,0.33,0.47,0.61,0.38,0.39,0.53,0.63,0.62,0.53,0.68,0.64,0.66,0.54,0.66,0.72,0.61,0.65,0.67,0.75,0.89,1.01,0.91,0.85,0.98,1.01,0.85,0.89,1.17,1.28,1.18];
const data = anomalies.map((a, i) => ({year: 1880 + i, a}));
const W = data.length * 6, H = 150;
const color = d3.scaleDiverging([-0.6, 0, 1.2], t => d3.interpolateRdBu(1 - t)).clamp(true);
const wrap = html`<div class="lp-stripes"></div>`;
const svg = d3.create("svg")
.attr("viewBox", `0 0 ${W} ${H}`)
.attr("preserveAspectRatio", "none")
.attr("role", "img")
.attr("aria-label", "Warming stripes: 146 vertical stripes, one per year from 1880 to 2025. Early years are mostly light blue; stripes turn red after about 1980 and are darkest red in the 2020s, showing Earth's average temperature has risen about 1.2 to 1.3 °C.");
svg.selectAll("rect").data(data).join("rect")
.attr("x", (d, i) => i * 6).attr("y", 0)
.attr("width", 6.4).attr("height", H)
.attr("fill", d => color(d.a));
const marker = svg.append("rect")
.attr("y", 0).attr("width", 6).attr("height", H)
.attr("fill", "none").attr("stroke", "#fde047").attr("stroke-width", 3)
.attr("vector-effect", "non-scaling-stroke")
.style("display", "none");
const read = html`<p class="lp-stripes-read" aria-live="polite"></p>`;
const show = d => {
marker.attr("x", (d.year - 1880) * 6).style("display", null);
const sign = d.a > 0 ? "+" : d.a < 0 ? "−" : "±";
const word = d.a > 0 ? "warmer" : d.a < 0 ? "cooler" : "the same as";
read.innerHTML = d.a === 0
? `<b>${d.year}:</b> right at the 1951–1980 average`
: `<b>${d.year}:</b> ${sign}${Math.abs(d.a).toFixed(2)} °C ${word} than the 1951–1980 average`;
};
show(data.find(d => d.year === 2024));
const node = svg.node();
const pick = e => {
const b = node.getBoundingClientRect();
const i = Math.max(0, Math.min(data.length - 1, Math.floor((e.clientX - b.left) / b.width * data.length)));
show(data[i]);
};
node.addEventListener("pointerdown", e => { node.setPointerCapture(e.pointerId); pick(e); });
node.addEventListener("pointermove", e => { if (e.pointerType === "mouse" || e.buttons) pick(e); });
wrap.append(node, read);
return wrap;
}Data: NASA GISTEMP v4, yearly global average surface temperature compared to the 1951–1980 average. You'll dig into this data in Unit 4.
You learn by doing things, not by reading walls of text.
The same datasets scientists use, from NASA, NOAA, and USGS. Nothing made up.
Drag sliders, spin orbits, and steer storms to see what changes and why.
Packet prompts appear right in the chapter, with a "check your thinking" answer to try after.
Get instant feedback that explains why an answer is right or wrong.
Tip: use the menu (left, or ☰ on a phone) to move between chapters, and the 🔍 search to find any topic.
// Live USGS feed: every magnitude 2.5+ earthquake in the past 7 days.
// If it can't load, the globe shows no dots rather than made-up ones.
lpQuakes = d3.json("https://earthquake.usgs.gov/earthquakes/feed/v1.0/summary/2.5_week.geojson")
.then(feed => feed.features
.filter(f => f.geometry && f.properties.mag != null)
.map(f => ({
lon: f.geometry.coordinates[0],
lat: f.geometry.coordinates[1],
mag: f.properties.mag,
place: f.properties.place || "Unknown location",
time: f.properties.time
}))
.sort((a, b) => a.mag - b.mag))
.catch(() => null)This textbook is by Will Pollard and is based on the New Visions for Public Schools Earth & Space Science curriculum. The majority of resources from curriculum.newvisions.org are Open Educational Resources (OER) available under the terms of the:
Creative Commons Attribution-NonCommercial-ShareAlike 4.0 International (CC BY-NC-SA 4.0) License
You are free to:
Under the following terms:
Materials created by New Visions’ partners are subject to other licenses; copyright on those resources is retained by the original author(s). Partners have granted New Visions a license to publish these resources for use by affiliated schools and teachers, and are identified as the original publisher.
Regents Examination Questions: Used with permission from the New York State Education Department. Past Regents Examinations are available at nysedregents.org. The New York State Education Department retains all rights. Reproducing materials for profit or any commercial use is strictly forbidden without express prior written permission of the New York State Education Department. Any reproduction or distribution must expressly credit the New York State Education Department.