Winter storm Jonas produced strong enough winds and enough snow to cause significant disruptions to society, damage to property, and harm to human life.
Quick Facts:
📏 Up to 40 inches of snow in parts of West Virginia
💨 Wind gusts exceeding 60 mph along the coast
🏙️ New York City received 27.5 inches — near-record snowfall
⚡ Over 300,000 power outages across the Mid-Atlantic
💰 Estimated $3 billion in damages
😢 At least 55 deaths attributed to the storm
29.1.1 🤔 Driving Questions:
What causes the wind associated with a blizzard?
What causes the snow and precipitation during a storm like this?
Could storms like Jonas become more common or more intense?
29.1.2 🌨️ The Winter Storm Paradox: Less Snow Overall, But Fiercer Storms?
The chart above shows NYC’s total seasonal snowfall trending downward over 150+ years. But does that mean blizzards are becoming a thing of the past?
Not exactly. Climate scientists have identified a striking pattern: as average snowfall totals decline, the most intense individual storm events are becoming more powerful. Warmer oceans inject more moisture into storm systems, and disruptions to the polar vortex can still funnel frigid Arctic air deep into the Northeast — creating the perfect recipe for a catastrophic blizzard, even in an otherwise low-snow year.
The two charts below reveal this paradox using the same NYC Central Park data.
Code
// NYC Annual Snowfall Data (1869–2025)// year = start year of the winter season (e.g. 2009 = winter 2009–10)// total = total seasonal snowfall (inches) — note: 2025-26 is a partial season// maxMonth = largest single-month snowfall that season (inches)// Source: NOAA / NWS Central Park stationnycStormData = [ {year:1869,total:27.8,maxMonth:9.6},{year:1870,total:33.1,maxMonth:15.9}, {year:1871,total:14.1,maxMonth:5.1},{year:1872,total:60.3,maxMonth:27.0}, {year:1873,total:36.9,maxMonth:19.0},{year:1874,total:57.8,maxMonth:15.3}, {year:1875,total:18.3,maxMonth:12.5},{year:1876,total:40.4,maxMonth:20.5}, {year:1877,total:8.1,maxMonth:6.1},{year:1878,total:35.7,maxMonth:17.3}, {year:1879,total:22.7,maxMonth:8.3},{year:1880,total:35.5,maxMonth:11.5}, {year:1881,total:31.4,maxMonth:17.5},{year:1882,total:44.0,maxMonth:10.1}, {year:1883,total:43.1,maxMonth:22.5},{year:1884,total:34.2,maxMonth:14.5}, {year:1885,total:20.8,maxMonth:13.5},{year:1886,total:32.9,maxMonth:10.3}, {year:1887,total:45.6,maxMonth:22.3},{year:1888,total:16.5,maxMonth:7.0}, {year:1889,total:24.3,maxMonth:17.0},{year:1890,total:28.8,maxMonth:11.4}, {year:1891,total:25.4,maxMonth:12.3},{year:1892,total:49.7,maxMonth:17.8}, {year:1893,total:36.1,maxMonth:20.5},{year:1894,total:27.0,maxMonth:9.5}, {year:1895,total:46.3,maxMonth:30.5},{year:1896,total:43.6,maxMonth:13.0}, {year:1897,total:21.1,maxMonth:9.0},{year:1898,total:55.9,maxMonth:25.3}, {year:1899,total:13.4,maxMonth:6.5},{year:1900,total:9.1,maxMonth:7.0}, {year:1901,total:30.0,maxMonth:15.8},{year:1902,total:28.7,maxMonth:14.4}, {year:1903,total:32.3,maxMonth:15.6},{year:1904,total:48.1,maxMonth:21.6}, {year:1905,total:20.0,maxMonth:11.5},{year:1906,total:53.2,maxMonth:21.8}, {year:1907,total:33.4,maxMonth:14.6},{year:1908,total:20.3,maxMonth:11.3}, {year:1909,total:27.2,maxMonth:11.1},{year:1910,total:25.2,maxMonth:13.3}, {year:1911,total:29.5,maxMonth:13.0},{year:1912,total:15.3,maxMonth:11.4}, {year:1913,total:40.5,maxMonth:21.5},{year:1914,total:28.8,maxMonth:7.7}, {year:1915,total:50.7,maxMonth:25.5},{year:1916,total:50.7,maxMonth:14.5}, {year:1917,total:34.5,maxMonth:14.1},{year:1918,total:3.8,maxMonth:2.7}, {year:1919,total:47.6,maxMonth:25.3},{year:1920,total:18.6,maxMonth:13.3}, {year:1921,total:27.8,maxMonth:9.4},{year:1922,total:60.4,maxMonth:24.5}, {year:1923,total:27.5,maxMonth:11.9},{year:1924,total:29.6,maxMonth:27.4}, {year:1925,total:32.4,maxMonth:26.3},{year:1926,total:22.3,maxMonth:11.7}, {year:1927,total:14.5,maxMonth:5.7},{year:1928,total:13.8,maxMonth:9.3}, {year:1929,total:13.6,maxMonth:6.3},{year:1930,total:11.6,maxMonth:5.7}, {year:1931,total:5.3,maxMonth:1.8},{year:1932,total:27.0,maxMonth:12.8}, {year:1933,total:52.0,maxMonth:27.9},{year:1934,total:33.8,maxMonth:23.6}, {year:1935,total:33.2,maxMonth:12.1},{year:1936,total:15.6,maxMonth:6.5}, {year:1937,total:15.1,maxMonth:6.5},{year:1938,total:37.3,maxMonth:10.3}, {year:1939,total:25.7,maxMonth:12.0},{year:1940,total:39.0,maxMonth:19.2}, {year:1941,total:11.3,maxMonth:6.4},{year:1942,total:29.5,maxMonth:9.5}, {year:1943,total:23.8,maxMonth:7.7},{year:1944,total:27.1,maxMonth:12.3}, {year:1945,total:31.4,maxMonth:15.6},{year:1946,total:30.6,maxMonth:17.7}, {year:1947,total:63.8,maxMonth:30.2},{year:1948,total:46.6,maxMonth:25.3}, {year:1949,total:13.8,maxMonth:8.5},{year:1950,total:11.6,maxMonth:3.8}, {year:1951,total:19.7,maxMonth:7.4},{year:1952,total:15.1,maxMonth:7.5}, {year:1953,total:15.8,maxMonth:12.7},{year:1954,total:11.5,maxMonth:5.2}, {year:1955,total:33.5,maxMonth:21.1},{year:1956,total:21.9,maxMonth:8.9}, {year:1957,total:44.7,maxMonth:15.9},{year:1958,total:13.0,maxMonth:6.7}, {year:1959,total:39.2,maxMonth:18.5},{year:1960,total:54.7,maxMonth:18.6}, {year:1961,total:18.1,maxMonth:9.6},{year:1962,total:16.3,maxMonth:5.3}, {year:1963,total:44.7,maxMonth:14.1},{year:1964,total:24.4,maxMonth:14.8}, {year:1965,total:21.4,maxMonth:11.6},{year:1966,total:51.5,maxMonth:23.6}, {year:1967,total:19.5,maxMonth:6.1},{year:1968,total:30.2,maxMonth:16.6}, {year:1969,total:25.6,maxMonth:8.4},{year:1970,total:15.5,maxMonth:11.4}, {year:1971,total:22.9,maxMonth:17.8},{year:1972,total:2.8,maxMonth:1.8}, {year:1973,total:23.5,maxMonth:9.4},{year:1974,total:13.1,maxMonth:10.6}, {year:1975,total:17.3,maxMonth:5.6},{year:1976,total:24.5,maxMonth:13.0}, {year:1977,total:50.7,maxMonth:23.0},{year:1978,total:29.4,maxMonth:20.1}, {year:1979,total:12.8,maxMonth:4.6},{year:1980,total:19.4,maxMonth:8.6}, {year:1981,total:24.6,maxMonth:11.8},{year:1982,total:27.2,maxMonth:21.5}, {year:1983,total:25.4,maxMonth:11.9},{year:1984,total:24.1,maxMonth:10.0}, {year:1985,total:13.0,maxMonth:9.9},{year:1986,total:23.1,maxMonth:13.6}, {year:1987,total:19.1,maxMonth:13.9},{year:1988,total:8.1,maxMonth:5.0}, {year:1989,total:13.4,maxMonth:3.1},{year:1990,total:24.9,maxMonth:9.1}, {year:1991,total:12.6,maxMonth:9.4},{year:1992,total:24.5,maxMonth:11.9}, {year:1993,total:53.4,maxMonth:26.4},{year:1994,total:11.8,maxMonth:11.6}, {year:1995,total:75.6,maxMonth:26.1},{year:1996,total:10.0,maxMonth:4.4}, {year:1997,total:5.5,maxMonth:5.0},{year:1998,total:12.7,maxMonth:4.5}, {year:1999,total:16.3,maxMonth:9.5},{year:2000,total:35.0,maxMonth:13.4}, {year:2001,total:3.5,maxMonth:3.5},{year:2002,total:49.3,maxMonth:26.1}, {year:2003,total:42.6,maxMonth:19.8},{year:2004,total:41.0,maxMonth:15.8}, {year:2005,total:40.0,maxMonth:26.9},{year:2006,total:12.4,maxMonth:6.0}, {year:2007,total:11.9,maxMonth:9.0},{year:2008,total:27.6,maxMonth:9.0}, {year:2009,total:51.4,maxMonth:36.9},{year:2010,total:61.9,maxMonth:36.0}, {year:2011,total:7.4,maxMonth:4.3},{year:2012,total:26.1,maxMonth:12.2}, {year:2013,total:57.4,maxMonth:29.0},{year:2014,total:50.3,maxMonth:18.6}, {year:2015,total:32.8,maxMonth:27.9},{year:2016,total:30.2,maxMonth:9.7}, {year:2017,total:40.9,maxMonth:11.6},{year:2018,total:20.5,maxMonth:10.4}, {year:2019,total:4.8,maxMonth:2.5},{year:2020,total:38.6,maxMonth:26.0}, {year:2021,total:17.9,maxMonth:15.3},{year:2022,total:2.3,maxMonth:2.2}, {year:2023,total:7.5,maxMonth:5.2},{year:2024,total:12.9,maxMonth:7.1}, {year:2025,total:44.4,maxMonth:24.9} // 2025–26: partial season (through Feb 2026)]
Code
functionrollingMean(arr, key, w =15) {const h =Math.floor(w /2);return arr.map((d, i) => {const slice = arr.slice(Math.max(0, i - h),Math.min(arr.length, i + h +1));return { year: d.year,v: slice.reduce((a, x) => a + x[key],0) / slice.length }; });}totalRoll =rollingMean(nycStormData,"total")maxRoll =rollingMean(nycStormData,"maxMonth")
Code
Plot.plot({title:"① Total Seasonal Snowfall Is Declining", width,height:270,marginLeft:58,marginRight:22,marginTop:40,x: { label:null,tickFormat:"d",ticks: [1880,1900,1920,1940,1960,1980,2000,2020] },y: { label:"Season total (inches)",domain: [0,88],grid:true },marks: [ Plot.barY(nycStormData, {x:"year",y:"total",fill:"#6da8d4",opacity:0.55,tip:true,title: d =>`${d.year}–${String(d.year+1).slice(-2)}: ${d.total.toFixed(1)}"` }), Plot.line(totalRoll, { x:"year",y:"v",stroke:"#1a3a5c",strokeWidth:3 }), Plot.text([{ year:1935,v:58 }], {x:"year",y:"v",text: ["← 15-yr average (declining)"],fill:"#1a3a5c",fontSize:11,fontWeight:"600",textAnchor:"start" }) ]})
Code
Plot.plot({title:"② Peak Single-Month Storm Intensity — Rising Since the 2000s", width,height:270,marginLeft:58,marginRight:22,marginTop:40,x: { label:"Season start year",tickFormat:"d",ticks: [1880,1900,1920,1940,1960,1980,2000,2020] },y: { label:"Largest monthly snowfall (inches)",domain: [0,42],grid:true },color: {domain: [false,true],range: ["#f0a070","#c0392b"],legend:true,tickFormat: d => d ?"≥ 20\" (major storm)":"< 20\"" },marks: [ Plot.dot(nycStormData, {x:"year",y:"maxMonth",fill: d => d.maxMonth>=20,r: d => d.maxMonth>=20?5:3,opacity:0.8,tip:true,title: d =>`${d.year}–${String(d.year+1).slice(-2)}: peak ${d.maxMonth.toFixed(1)}"` }), Plot.line(maxRoll, { x:"year",y:"v",stroke:"#7b241c",strokeWidth:3 }), Plot.ruleY([20], { stroke:"#c0392b",strokeDasharray:"6,4",opacity:0.45 }), Plot.text([{ year:1873,v:21.8 }], {x:"year",y:"v",text: ["20\" threshold →"],fill:"#c0392b",fontSize:10,fontWeight:"600",textAnchor:"start" }) ]})
● Red dots = seasons where a single month topped 20” (a major storm). Dark lines = 15-year rolling average. Notice: while seasonal totals trend downward, the 15-yr average of peak monthly snowfall increased sharply after 2000 — the 2000s and 2010s logged the highest peak-storm averages of any era. The current 2025–26 season already recorded 24.9” in a single month (Feb 2026).
Code
buildQuiz("nyc-snowfall", [ {q:"Charts ① and ② show opposite trends in NYC snowfall data. What is the 'winter storm paradox' they reveal?",options: ["Both total snowfall and peak storm intensity are declining due to climate change","Seasonal snowfall totals are declining, but peak single-storm intensity has risen — fewer but more extreme events","Winter storms are becoming more frequent and less intense","The data shows no clear trend; year-to-year variability is too high" ],correct:1,explanation:"The paradox: fewer mild-to-moderate snow events lower the seasonal total, while the most extreme individual storms are delivering record single-month totals. This matches climate science predictions — warming reduces weak events but can amplify extreme ones by loading the atmosphere with extra moisture." }, {q:"The 15-year rolling average of peak monthly snowfall rose sharply after 2000. What physical mechanism best explains this?",options: ["Measurement instruments became more accurate after 2000","Warmer Atlantic and Gulf sea-surface temperatures supply more water vapor; when cold outbreaks occur they produce heavier single-storm totals","The Arctic became colder after 2000, sending larger cold air masses south","Urban heat islands in NYC increased snowfall through cloud-seeding" ],correct:1,explanation:"Warmer ocean surfaces evaporate more water vapor (Clausius-Clapeyron: ~7% more per °C). When cold outbreaks occur, they tap this extra atmospheric moisture — resulting in heavier snowfall than the same cold event would have produced in earlier decades." }])
30 Explore: What Causes Wind?
30.1 🔬 Investigation: Pressure & Wind
Wind isn’t random — it has a cause. In this section, you’ll build a model of what creates wind and why it blows in specific directions.
30.2 The Wind Machine: Pressure Differences
Wind is caused by differences in air pressure. Air always moves from areas of high pressure toward areas of low pressure. The greater the pressure difference, the stronger the wind.
But what causes pressure differences? Uneven heating of Earth’s surface.
Did you know? In real weather systems, wind doesn’t flow in a perfectly straight line from high to low pressure. Because the Earth is spinning, the Coriolis Effect causes the wind to curve (to the right in the Northern Hemisphere, and left in the Southern Hemisphere). However, the primary driving force of the wind is always this pressure difference!
30.3 Atmospheric Convection Currents
This interactive simulation demonstrates how thermal differences create pressure zones and drive wind patterns in the troposphere. Adjust the temperatures of the land masses to observe the resulting changes in air density and wind currents.
Observe how temperature differences between the land and ocean drive coastal winds.
2:00 PM
🌙☀️
Midnight6 AMNoon6 PMMidnight
ℹ️
What's happening?
Code
buildQuiz("land-sea-breeze", [ {q:"In the Land & Sea Breeze Simulator at 2 PM, why does surface wind blow FROM sea TO land?",options: ["The sun heats the ocean faster, causing cold ocean air to push toward land","Hot land air rises (low pressure), and cooler ocean air (high pressure) flows inland to replace it","Sea breezes are a night-time phenomenon — during the day wind blows from land to sea","Ocean water evaporates and pulls surface air outward from the coast" ],correct:1,explanation:"Sea breeze: land heats faster than water → air above land warms, expands, and RISES → surface LOW pressure over land. Cooler, denser ocean air (HIGH pressure) flows landward to fill the void. This is the identical pressure-gradient mechanism as all surface wind — uneven heating creates pressure differences, air flows HIGH → LOW." }, {q:"At midnight, the land temperature drops below the ocean temperature. How does the wind direction change and why?",options: ["Wind continues blowing from sea to land — thermal inertia keeps the pattern going","Wind reverses to blow from land to sea — now the ocean is warmer, ocean air rises (low pressure), and cooler land air flows outward","Wind stops completely at night when solar heating ends","The Coriolis Effect reverses the wind direction after sunset" ],correct:1,explanation:"Land radiates heat rapidly at night; the ocean retains heat far longer (high specific heat capacity). Now ocean > land temperature → ocean air rises (LOW pressure over water) → cooler land air flows seaward. This LAND BREEZE is driven by the same HIGH-to-LOW pressure rule — just with the temperature differential flipped from daytime." }])
30.4.1 💡 Key Concept: What Drives Wind
Uneven heating → Temperature differences → Pressure differences → WIND
When the Sun heats Earth’s surface unevenly, some areas become warmer than others.
Warm air is less dense and rises, creating an area of low pressure at the surface.
Cool air is more dense and sinks, creating an area of high pressure at the surface.
Air flows from high pressure → low pressure. This is wind!
The greater the pressure difference (the pressure gradient), the faster the wind.
Code
buildQuiz("wind-pressure", [ {q:"You set the left surface to 98°F (hot desert) and the right to 8°F (frozen tundra). Which direction does the surface wind blow?",options: ["From the hot (left/low-pressure) side toward the cold (right/high-pressure) side","From the cold (right/high-pressure) side toward the hot (left/low-pressure) side","Wind does not form — the temperatures are too extreme","Wind direction is determined solely by Earth's rotation" ],correct:1,explanation:"Hot air rises → creates LOW pressure at the surface. Cold air sinks → creates HIGH pressure. Surface wind always flows FROM high pressure TOWARD low pressure, so from cold → toward hot. The bigger the temperature (pressure) difference, the faster the wind!" }, {q:"What is the pressure gradient force?",options: ["The force of gravity pulling air downward","The force pushing air from areas of HIGH pressure toward areas of LOW pressure","The Coriolis deflection caused by Earth's rotation","The force that lifts warm air at frontal boundaries" ],correct:1,explanation:"The pressure gradient force is the FUNDAMENTAL driver of all wind. It pushes air 'downhill' from high to low pressure. The steeper the gradient (larger pressure difference over a shorter distance), the stronger the wind — just like steeper hills create faster-moving rivers." }, {q:"A blizzard requires sustained 35+ mph winds. What atmospheric feature generates these extreme winds?",options: ["The weight of heavy snowflakes pushing down on the air column","A steep pressure gradient around a deep low-pressure cyclone with tightly packed isobars","Cold temperatures causing air molecules to vibrate and move faster","Friction between falling snow crystals and the atmosphere" ],correct:1,explanation:"Blizzards form within powerful mid-latitude cyclones — very deep low-pressure systems. The enormous pressure difference between the low center and surrounding high pressure creates a steep gradient. This drives extreme winds toward the low center, spinning counter-clockwise due to the Coriolis Effect." }])
31 Explore: Fronts — When Air Masses Collide
31.1 🌪️ What Happens When Air Masses Meet?
When two air masses with different properties meet, they don’t mix easily. The boundary between them is called a front. Fronts are where weather happens!
31.2 Types of Fronts
32 Explain: How Do Blizzards Form?
32.1 🧊 Building a Blizzard Model
Now that you understand wind (from pressure differences) and precipitation (from air mass collisions at fronts), let’s put it all together to explain how blizzards form.
32.2 The Mid-Latitude Cyclone
Blizzards are produced by powerful mid-latitude cyclones — large low-pressure systems that form at the boundary between polar and tropical air masses, typically between 30°N and 60°N latitude.
Code
{// ── helpers ───────────────────────────────────────────────────────────const _lerp = (a,b,t) => a+(b-a)*t;const _bPt = (t,p0,p1,p2) => ({x:(1-t)**2*p0.x+2*(1-t)*t*p1.x+t*t*p2.x,y:(1-t)**2*p0.y+2*(1-t)*t*p1.y+t*t*p2.y });const _bTan = (t,p0,p1,p2) => {const dx=2*(1-t)*(p1.x-p0.x)+2*t*(p2.x-p1.x);const dy=2*(1-t)*(p1.y-p0.y)+2*t*(p2.y-p1.y);const n=Math.sqrt(dx*dx+dy*dy)||1;return {x:dx/n,y:dy/n}; };const _KF = [ {p:0,L:{x:.5,y:.5},T:{x:.5,y:.5},coldEnd:{x:0,y:.5},coldCtrl:{x:.25,y:.5},warmEnd:{x:1,y:.5},warmCtrl:{x:.75,y:.5},occCtrl:{x:.5,y:.5},pressure:1010,desc:"Stationary Front Stage: Cold air to the north and warm air to the south flow parallel to the boundary in opposite directions. There is no active weather development yet."}, {p:20,L:{x:.5,y:.45},T:{x:.5,y:.45},coldEnd:{x:0,y:.6},coldCtrl:{x:.3,y:.6},warmEnd:{x:1,y:.4},warmCtrl:{x:.7,y:.35},occCtrl:{x:.5,y:.45},pressure:1004,desc:"Incipient Wave Stage: A perturbation (kink) forms on the front. A localized low-pressure center develops. Cold air starts pushing south (cold front), and warm air pushes north (warm front)."}, {p:50,L:{x:.55,y:.35},T:{x:.55,y:.35},coldEnd:{x:.1,y:.9},coldCtrl:{x:.35,y:.7},warmEnd:{x:.9,y:.25},warmCtrl:{x:.8,y:.45},occCtrl:{x:.55,y:.35},pressure:992,desc:"Mature Stage (Open Wave): The cyclone is fully developed with well-defined cold and warm fronts. A clear 'warm sector' exists between them. Central pressure drops rapidly, creating strong winds."}, {p:80,L:{x:.6,y:.25},T:{x:.68,y:.45},coldEnd:{x:.25,y:1},coldCtrl:{x:.45,y:.8},warmEnd:{x:1,y:.3},warmCtrl:{x:.9,y:.5},occCtrl:{x:.62,y:.35},pressure:988,desc:"Occluded Stage: The faster-moving cold front catches up to the warm front, lifting the warm air completely off the ground near the center. This forms an occluded front. The storm reaches peak intensity."}, {p:100,L:{x:.65,y:.2},T:{x:.85,y:.6},coldEnd:{x:.4,y:1},coldCtrl:{x:.65,y:.9},warmEnd:{x:1,y:.45},warmCtrl:{x:.95,y:.55},occCtrl:{x:.7,y:.3},pressure:998,desc:"Dissipation Stage: The low-pressure center is entirely surrounded by cold air. Cut off from its warm air energy source (the temperature gradient), the cyclone slowly weakens and spins down."} ];const _getState = (p) => {let kf1=_KF[0], kf2=_KF[_KF.length-1];for(let i=0;i<_KF.length-1;i++) if(p>=_KF[i].p&&p<=_KF[i+1].p){kf1=_KF[i];kf2=_KF[i+1];break;}const t=(p-kf1.p)/((kf2.p-kf1.p)||1);const s={desc:p<90?kf1.desc:_KF[4].desc,pressure:_lerp(kf1.pressure,kf2.pressure,t)};for(const k of['L','T','coldEnd','coldCtrl','warmEnd','warmCtrl','occCtrl']) s[k]={x:_lerp(kf1[k].x,kf2[k].x,t),y:_lerp(kf1[k].y,kf2[k].y,t)};return s; };// ── state ─────────────────────────────────────────────────────────────let prog=0, playing=false, raf=null, lastT=null;const lay={airMasses:true,precipitation:true,fronts:true,winds:true,isobars:true};// ── DOM ───────────────────────────────────────────────────────────────constroot=document.createElement('div');root.style.cssText='width:100%;font-family:system-ui,sans-serif;border:1px solid #e2e8f0;border-radius:12px;overflow:hidden;background:#f8fafc;';// Headerconst hdr =document.createElement('div'); hdr.style.cssText='background:#1e293b;color:white;padding:13px 18px;display:flex;align-items:center;justify-content:space-between;';const hdrLeft =document.createElement('div'); hdrLeft.innerHTML='<div style="font-size:15px;font-weight:700;">🌪️ Mid-Latitude Cyclone</div><div style="font-size:11px;color:#94a3b8;margin-top:2px;">Interactive Cyclogenesis Model</div>';const hdrBtns =document.createElement('div'); hdrBtns.style.cssText='display:flex;align-items:center;gap:8px;';const resetBtn =document.createElement('button'); resetBtn.title='Reset'; resetBtn.textContent='↺'; resetBtn.style.cssText='background:none;border:none;color:#94a3b8;cursor:pointer;font-size:20px;padding:4px 7px;line-height:1;';const playBtn =document.createElement('button'); playBtn.textContent='▶'; playBtn.style.cssText='background:#3b82f6;border:none;color:white;border-radius:50%;width:38px;height:38px;cursor:pointer;font-size:14px;display:flex;align-items:center;justify-content:center;flex-shrink:0;';const endBtn =document.createElement('button'); endBtn.title='Skip to End'; endBtn.textContent='⏭'; endBtn.style.cssText='background:none;border:none;color:#94a3b8;cursor:pointer;font-size:18px;padding:4px 7px;line-height:1;'; hdrBtns.append(resetBtn, playBtn, endBtn); hdr.append(hdrLeft, hdrBtns);root.appendChild(hdr);// Slider rowconst slRow =document.createElement('div'); slRow.style.cssText='background:white;padding:10px 18px;border-bottom:1px solid #e2e8f0;display:flex;align-items:center;gap:10px;';const slLbl1 =document.createElement('span'); slLbl1.textContent='Stationary'; slLbl1.style.cssText='font-size:11px;color:#64748b;white-space:nowrap;';const slider =document.createElement('input'); slider.type='range'; slider.min=0; slider.max=100; slider.step=0.5; slider.value=0; slider.style.cssText='flex:1;accent-color:#3b82f6;height:6px;cursor:pointer;';const slLbl2 =document.createElement('span'); slLbl2.textContent='Dissipating'; slLbl2.style.cssText='font-size:11px;color:#64748b;white-space:nowrap;'; slRow.append(slLbl1, slider, slLbl2);root.appendChild(slRow);// Lower: sidebar + canvasconst lower =document.createElement('div'); lower.style.cssText='display:flex;width:100%;';// Sidebarconst sidebar =document.createElement('div'); sidebar.style.cssText='width:190px;flex-shrink:0;background:white;border-right:1px solid #e2e8f0;padding:12px;display:flex;flex-direction:column;gap:8px;';const layTitle =document.createElement('div'); layTitle.textContent='Display Layers'; layTitle.style.cssText='font-size:10px;font-weight:700;color:#64748b;text-transform:uppercase;letter-spacing:.05em;'; sidebar.appendChild(layTitle);const layDefs=[ {key:'airMasses',label:'Air Masses',color:'#f97316'}, {key:'precipitation',label:'Precipitation',color:'#22c55e'}, {key:'fronts',label:'Fronts',color:'#3b82f6'}, {key:'winds',label:'Wind Vectors',color:'#64748b'}, {key:'isobars',label:'Isobars',color:'#94a3b8'}, ];const layBtns={};functionstyleLB(btn,active){ btn.style.background=active?'#eff6ff':'white'; btn.style.borderColor=active?'#bfdbfe':'#e2e8f0'; btn.style.fontWeight=active?'600':'400'; }for(const ld of layDefs){const btn=document.createElement('button'); btn.style.cssText=`width:100%;text-align:left;padding:6px 9px;border-radius:7px;font-size:12px;cursor:pointer;display:flex;align-items:center;gap:7px;border:1.5px solid #e2e8f0;background:white;color:#334155;`; btn.innerHTML=`<span style="width:9px;height:9px;border-radius:2px;background:${ld.color};flex-shrink:0;"></span>${ld.label}`; layBtns[ld.key]=btn;styleLB(btn,lay[ld.key]); btn.addEventListener('click',()=>{lay[ld.key]=!lay[ld.key];styleLB(btn,lay[ld.key]);}); sidebar.appendChild(btn); }const infoBox =document.createElement('div'); infoBox.style.cssText='background:#eff6ff;border:1px solid #dbeafe;border-radius:8px;padding:10px;font-size:11px;color:#334155;line-height:1.55;margin-top:4px;'; sidebar.appendChild(infoBox); lower.appendChild(sidebar);// Canvasconst canvas =document.createElement('canvas'); canvas.style.cssText='flex:1;display:block;background:#e0f2fe;min-height:460px;'; lower.appendChild(canvas);root.appendChild(lower);// ── draw ──────────────────────────────────────────────────────────────functiondraw(){const cw=canvas.clientWidth, ch=canvas.clientHeight;if(!cw||!ch) return;if(canvas.width!==cw||canvas.height!==ch){canvas.width=cw;canvas.height=ch;}const ctx=canvas.getContext('2d');const s=_getState(prog);const sc=pt=>({x:pt.x*cw,y:pt.y*ch});const Lp=sc(s.L),Tp=sc(s.T),ce=sc(s.coldEnd),cc=sc(s.coldCtrl),we=sc(s.warmEnd),wc=sc(s.warmCtrl),oc=sc(s.occCtrl); ctx.clearRect(0,0,cw,ch);// Air massesif(lay.airMasses){ ctx.fillStyle='#e0f2fe'; ctx.fillRect(0,0,cw,ch); ctx.beginPath(); ctx.moveTo(Tp.x,Tp.y); ctx.quadraticCurveTo(wc.x,wc.y,we.x,we.y);if(we.y<ch) ctx.lineTo(cw,ch); ctx.lineTo(ce.x,ch); ctx.lineTo(ce.x,ce.y); ctx.quadraticCurveTo(cc.x,cc.y,Tp.x,Tp.y); ctx.fillStyle='#ffedd5'; ctx.fill();const lbl=(text,desc,x,y,col)=>{ ctx.fillStyle=col;ctx.font='bold 26px sans-serif';ctx.textAlign='center';ctx.textBaseline='middle'; ctx.fillText(text,x,y);ctx.font='11px sans-serif';ctx.fillText(desc,x,y+21); };if(prog<5){lbl('cP','Continental Polar',cw*.5,ch*.22,'#0284c7');lbl('mT','Maritime Tropical',cw*.5,ch*.75,'#c2410c'); } else {lbl('cP','Continental Polar',Math.max(45,Math.min(ce.x-50,cw*.18)),Math.max(45,Math.min(Lp.y+85,ch*.5)),'#0284c7');lbl('mT','Maritime Tropical',Math.min(cw-65,Tp.x+(we.x-Tp.x)*.4),Math.min(ch-40,Math.max(Tp.y+105,we.y+45)),'#c2410c');lbl('mP','Maritime Polar',Math.min(cw-65,we.x-65),Math.max(40,Tp.y-85),'#0369a1'); } }// Precipitationif(lay.precipitation){const band=(p0,p1,p2,wLim,side,fill)=>{const steps=30,pts=[],offs=[];for(let i=0;i<=steps;i++){const t_=i/steps,pt=_bPt(t_,p0,p1,p2),tn=_bTan(t_,p0,p1,p2); pts.push(pt);const tap=Math.sin(t_*Math.PI),w=wLim*(.3+.7*tap); offs.push({x:pt.x+(-tn.y*side)*w,y:pt.y+(tn.x*side)*w}); } ctx.beginPath(); ctx.moveTo(pts[0].x,pts[0].y);for(let i=1;i<=steps;i++) ctx.lineTo(pts[i].x,pts[i].y);for(let i=steps;i>=0;i--) ctx.lineTo(offs[i].x,offs[i].y); ctx.closePath(); ctx.fillStyle=fill; ctx.fill(); };if(prog<5){band(ce,Lp,we,40,-1,'rgba(74,222,128,.4)'); } else {band(Tp,wc,we,90,-1,'rgba(74,222,128,.5)');band(Tp,cc,ce,30,1,'rgba(22,163,74,.7)');const inten=Math.sin(prog/100*Math.PI);if(prog>10){ctx.beginPath();ctx.ellipse(Lp.x-10,Lp.y-10,60*inten+20,45*inten+20,Math.PI/4,0,Math.PI*2);ctx.fillStyle='rgba(74,222,128,.6)';ctx.fill();}if(prog>55){band(Lp,oc,Tp,60,1,'rgba(74,222,128,.6)');band(Lp,oc,Tp,60,-1,'rgba(74,222,128,.6)');} } }// Isobarsif(lay.isobars){ ctx.strokeStyle='rgba(0,0,0,.15)'; ctx.lineWidth=1;const inten=Math.sin(prog/100*Math.PI), n=3+Math.floor(inten*5);for(let i=1;i<=n;i++){ctx.beginPath();ctx.ellipse(Lp.x,Lp.y,i*40*(1-inten*.2),i*35*(1-inten*.3),Math.PI/8,0,Math.PI*2);ctx.stroke();} }// Frontsif(lay.fronts){const sym=(x,y,ang,type)=>{ ctx.save(); ctx.translate(x,y); ctx.rotate(ang); ctx.beginPath();if(type==='cold'){ctx.moveTo(-8,0);ctx.lineTo(8,0);ctx.lineTo(0,14);ctx.fillStyle='#2563eb';}elseif(type==='warm'){ctx.arc(0,0,8,Math.PI,0);ctx.fillStyle='#dc2626';}elseif(type==='occ-cold'){ctx.moveTo(-8,0);ctx.lineTo(8,0);ctx.lineTo(0,14);ctx.fillStyle='#9333ea';}elseif(type==='occ-warm'){ctx.arc(0,0,8,Math.PI,0);ctx.fillStyle='#9333ea';} ctx.fill(); ctx.restore(); };const fp=(p0,p1,p2,col,sType,spacing,isOcc)=>{ ctx.strokeStyle=col; ctx.lineWidth=3; ctx.beginPath(); ctx.moveTo(p0.x,p0.y); ctx.quadraticCurveTo(p1.x,p1.y,p2.x,p2.y); ctx.stroke();let d=0,tog=false;for(let i=1;i<30;i++){const t_=i/30,pt=_bPt(t_,p0,p1,p2),pp=_bPt((i-1)/30,p0,p1,p2); d+=Math.sqrt((pt.x-pp.x)**2+(pt.y-pp.y)**2);if(d>spacing){const tn=_bTan(t_,p0,p1,p2);let ang=Math.atan2(tn.y,tn.x);if(sType==='cold') ang+=Math.PI/2;elseif(sType==='warm') ang-=Math.PI/2;else ang+=Math.PI/2;let cur=sType;if(isOcc){cur=tog?'occ-cold':'occ-warm';tog=!tog;}sym(pt.x,pt.y,ang,cur); d=0; } } };if(prog<5){ ctx.strokeStyle='#6b7280'; ctx.lineWidth=3; ctx.beginPath(); ctx.moveTo(ce.x,ce.y); ctx.lineTo(we.x,we.y); ctx.stroke(); } else {fp(Tp,cc,ce,'#2563eb','cold',50,false);fp(Tp,wc,we,'#dc2626','warm',50,false);if(prog>55) fp(Lp,oc,Tp,'#9333ea','occ',35,true); }if(prog>5){ ctx.fillStyle='#ef4444'; ctx.font='bold 22px sans-serif'; ctx.textAlign='center'; ctx.textBaseline='middle'; ctx.strokeStyle='rgba(255,255,255,.8)'; ctx.lineWidth=4; ctx.strokeText('L',Lp.x,Lp.y-14); ctx.fillText('L',Lp.x,Lp.y-14); ctx.fillStyle='#1f2937'; ctx.font='11px sans-serif'; ctx.fillText(`${Math.round(s.pressure)} mb`,Lp.x,Lp.y+9); } }// Windsif(lay.winds){ ctx.strokeStyle='rgba(75,85,99,.4)'; ctx.fillStyle='rgba(75,85,99,.6)'; ctx.lineWidth=1.5;const grid=40;for(let x=grid/2;x<cw;x+=grid){for(let y=grid/2;y<ch;y+=grid){const dx=x-Lp.x,dy=y-Lp.y,dist=Math.sqrt(dx*dx+dy*dy);let wAng=0,wSpd=0;if(prog<10){wAng=y<Lp.y?Math.PI:0;wSpd=10;}else{const inten=Math.sin(prog/100*Math.PI); wAng=Math.atan2(dy,dx)-Math.PI/2-.35; wSpd=dist<300?(dist/300)*20*inten+5:5;if(dist<40) wSpd=dist/8; }if(wSpd>2){const al=Math.min(wSpd,25),ex=x+Math.cos(wAng)*al,ey=y+Math.sin(wAng)*al; ctx.beginPath(); ctx.moveTo(x,y); ctx.lineTo(ex,ey); ctx.stroke();const ha=Math.PI/6,hl=4; ctx.beginPath(); ctx.moveTo(ex,ey); ctx.lineTo(ex-hl*Math.cos(wAng-ha),ey-hl*Math.sin(wAng-ha)); ctx.lineTo(ex-hl*Math.cos(wAng+ha),ey-hl*Math.sin(wAng+ha)); ctx.closePath(); ctx.fill(); } } } } infoBox.textContent= s.desc; }// ── animation loop ────────────────────────────────────────────────────functionloop(t){if(playing){if(lastT!=null){ prog=Math.min(100,prog+(t-lastT)*0.005); slider.value=prog;if(prog>=100){playing=false;playBtn.textContent='▶';} } lastT=t; }draw(); raf=requestAnimationFrame(loop); } raf=requestAnimationFrame(loop); invalidation.then(()=>cancelAnimationFrame(raf));// ── events ──────────────────────────────────────────────────────────── slider.oninput=()=>{prog=+slider.value;playing=false;playBtn.textContent='▶';}; playBtn.onclick=()=>{playing=!playing;playBtn.textContent=playing?'⏸':'▶';if(playing)lastT=null;}; resetBtn.onclick=()=>{prog=0;slider.value=0;playing=false;playBtn.textContent='▶';}; endBtn.onclick=()=>{prog=100;slider.value=100;playing=false;playBtn.textContent='▶';};returnroot;}
A blizzard is a severe mid-latitude cyclone that produces:
Heavy snow (reduces visibility to less than ¼ mile)
Strong winds (sustained 35+ mph for 3+ hours)
Cold temperatures (usually below 20°F)
These conditions occur on the cold side of the low-pressure center (north and west of the center in the Northern Hemisphere), where cold, dry polar air wraps around the system and collides with moist air being lifted along the fronts.
Code
buildQuiz("cyclone", [ {q:"Use the Cyclone Life Stage slider. At Stage 3 (Mature Cyclone), the BLIZZARD zone is shown north/northwest of the low center. Why that location specifically?",options: ["East of the center, in the warm sector between the two fronts","North/northwest of the center — where cold polar air wraps around the system, pressure gradient is steepest, and precipitation from lifted moist air is heaviest","Directly at the low center, where pressure is lowest","South of the warm front, far from any cold air" ],correct:1,explanation:"The blizzard zone needs THREE things simultaneously: (1) temperatures below freezing — provided by cold polar air wrapping north/west of the low; (2) extreme winds — provided by the steep pressure gradient near the low center; (3) heavy precipitation — provided by moist air being lifted along the cold front. Only the north/west quadrant has all three!" }, {q:"Why do mid-latitude cyclones in the Northern Hemisphere spin COUNTER-CLOCKWISE?",options: ["Warm air rises and cold air sinks simultaneously, creating opposite rotations","The Coriolis Effect deflects inflowing air to the RIGHT, causing it to spiral counter-clockwise around the low center","Ocean currents beneath force the overlying atmosphere to rotate counter-clockwise","High-pressure systems push air in a counter-clockwise direction toward low-pressure centers" ],correct:1,explanation:"As air converges toward a low-pressure center, Earth's rotation deflects it to the right (Coriolis Effect). This rightward deflection makes air spiral counter-clockwise around the Northern Hemisphere low. In the Southern Hemisphere, the deflection is to the LEFT, so cyclones spin clockwise. This is why all Northern Hemisphere low-pressure systems (including blizzards, hurricanes, and tornadoes) rotate counter-clockwise." }, {q:"What happens to a mid-latitude cyclone after the cold front overtakes the warm front (Stage 4 — Occlusion)?",options: ["The storm intensifies dramatically as both fronts combine their energy","The storm weakens — warm air is completely lifted off the surface, cutting off the temperature contrast that was driving the cyclone","The storm stalls and becomes a long-lived stationary front","Occlusion has no effect on storm intensity" ],correct:1,explanation:"Once the fast-moving cold front catches the warm front, all the warm air is lifted off the surface — creating the occluded front. With no more warm vs. cold temperature contrast at the surface, the pressure gradient weakens, the system fills in, and the storm dissipates. Every mid-latitude cyclone (including every blizzard) eventually dies this way." }])
32.3 Reading Weather Maps
Meteorologists use surface analysis maps to track air masses, fronts, and pressure systems. Understanding these maps is key to predicting blizzards.
32.3.1 🗺️ Weather Map Symbols
Symbol
Meaning
L (red)
Low-pressure center — rising air, clouds, precipitation
H (blue)
High-pressure center — sinking air, clear skies
Blue line with triangles ▲
Cold front (triangles point in direction of movement)
Red line with semicircles ⦿
Warm front (semicircles point in direction of movement)
Alternating blue/red
Stationary front
Purple line with both
Occluded front
Concentric circles
Isobars — lines of equal pressure (closer = stronger wind)
33 Explain: Precipitation — Where Does Snow Come From?
33.1 💧 From Water Vapor to Snowflakes
Understanding precipitation requires connecting energy, water, and air movement. Let’s trace the journey from evaporation to snowfall.
33.2 The Precipitation Process
Code
{const width =750;const height =500;const svg = d3.create("svg").attr("width", width).attr("height", height).attr("viewBox",`0 0 ${width}${height}`); svg.append("rect").attr("width", width).attr("height", height).attr("fill","#e8f4fd").attr("rx",10); svg.append("text").attr("x",375).attr("y",30).attr("text-anchor","middle").attr("font-size",20).attr("font-weight","bold").text("How Precipitation Forms at a Front");// Ground svg.append("rect").attr("x",0).attr("y",420).attr("width",375).attr("height",80).attr("fill","#ff7675").attr("opacity",0.3); svg.append("rect").attr("x",375).attr("y",420).attr("width",375).attr("height",80).attr("fill","#74b9ff").attr("opacity",0.3); svg.append("text").attr("x",190).attr("y",460).attr("text-anchor","middle").attr("font-size",14).attr("font-weight","bold").attr("fill","#d63031").text("Warm Surface (mT air)"); svg.append("text").attr("x",560).attr("y",460).attr("text-anchor","middle").attr("font-size",14).attr("font-weight","bold").attr("fill","#0984e3").text("Cold Surface (cP air)");// Steps with arrowsconst steps = [ {x:120,y:390,num:"①",text:"Warm, moist air holds\nlots of water vapor",color:"#d63031"}, {x:120,y:300,num:"②",text:"Warm air is forced UP\nat the front boundary",color:"#e17055"}, {x:280,y:200,num:"③",text:"Rising air COOLS\n(adiabatic cooling)",color:"#6c5ce7"}, {x:430,y:120,num:"④",text:"Cool air can't hold as\nmuch moisture → CONDENSATION",color:"#0984e3"}, {x:550,y:60,num:"⑤",text:"Water droplets form clouds;\nif cold enough → ICE CRYSTALS",color:"#00b894"}, {x:550,y:300,num:"⑥",text:"Crystals grow heavy\nand FALL as snow ❄️",color:"#2d3436"} ]; steps.forEach(s => { svg.append("circle").attr("cx", s.x-30).attr("cy", s.y).attr("r",18).attr("fill", s.color).attr("opacity",0.2); svg.append("text").attr("x", s.x-30).attr("y", s.y+6).attr("text-anchor","middle").attr("font-size",18).attr("font-weight","bold").attr("fill", s.color).text(s.num);const lines = s.text.split("\n"); lines.forEach((line, i) => { svg.append("text").attr("x", s.x+10).attr("y", s.y-5+ i *16).attr("font-size",13).attr("fill","#2d3436").text(line); }); });// Rising air arrow svg.append("path").attr("d","M 150,380 Q 200,280 350,180 Q 450,100 550,70").attr("stroke","#e17055").attr("stroke-width",3).attr("fill","none").attr("stroke-dasharray","8,4");// Falling snowfor (let i =0; i <8; i++) { svg.append("text").attr("x",520+Math.random() *100).attr("y",200+Math.random() *180).attr("font-size",16).text("❄️"); }// Front line svg.append("line").attr("x1",375).attr("y1",420).attr("x2",375).attr("y2",200).attr("stroke","#6c5ce7").attr("stroke-width",3).attr("stroke-dasharray","5,3"); svg.append("text").attr("x",380).attr("y",195).attr("font-size",12).attr("fill","#6c5ce7").text("FRONT");return svg.node();}
33.2.1 💡 Key Concept: Why Fronts Produce Precipitation
Warm, moist air (often from the Gulf of Mexico) contains lots of water vapor
At a front, this warm air is forced upward over cold air
As air rises, it cools (because atmospheric pressure decreases with altitude)
Cool air holds less water vapor than warm air
Excess moisture condenses into water droplets or freezes into ice crystals
When crystals grow heavy enough, they fall as snow (if temperatures remain below freezing all the way to the ground)
❄️ A single mid-latitude cyclone can lift BILLIONS of tons of moist air, producing enough snow to bury an entire state! ❄️
Code
buildQuiz("precipitation", [ {q:"Looking at the precipitation diagram above: what triggers water vapor to condense into cloud droplets or ice crystals?",options: ["Increased atmospheric pressure as air rises compresses the moisture","Adiabatic cooling — rising air expands (pressure decreases with altitude) and cools, eventually reaching its dew point","Mixing with cold polar air molecules at the frontal surface","Solar radiation heating water droplets already in the cloud" ],correct:1,explanation:"Adiabatic cooling is the key mechanism: as air rises, it moves into lower-pressure regions and EXPANDS. Expansion requires energy (work done against surroundings), so the air cools WITHOUT exchanging heat with its environment. When the rising air cools to its dew point temperature, condensation begins — water droplets or ice crystals form." }, {q:"Why does precipitation fall as SNOW rather than RAIN during a blizzard?",options: ["Snow forms when two ice crystals collide and stick together in a turbulent cloud","Temperatures must remain below freezing from the cloud base all the way to the ground so precipitation stays frozen","Snow only forms from cP continental air masses; rain only forms from mT maritime air","Low atmospheric pressure prevents water droplets from remaining liquid" ],correct:1,explanation:"Whether you get snow or rain depends on the entire atmospheric temperature profile. If a warm layer exists aloft (above freezing), snow melts into rain. For blizzards, the cold polar air mass extends from the cloud level all the way to the surface — temperatures below 32°F throughout — so ice crystals fall as snow and stay frozen until they hit the ground." }, {q:"Why is moist air (carrying lots of water vapor) LESS dense than dry air at the same temperature?",options: ["Water vapor molecules are heavier than nitrogen and oxygen, making moist air denser","Water vapor molecules (H₂O, MW=18) are LIGHTER than nitrogen (N₂, MW=28) and oxygen (O₂, MW=32), so moist air has lower average molecular weight","Water vapor adds extra pressure that spreads air molecules further apart","Humidity has no effect on air density — only temperature matters" ],correct:1,explanation:"This surprises many students! Water vapor (H₂O) has a molecular weight of 18, while N₂ = 28 and O₂ = 32. When water vapor replaces heavier N₂ and O₂ molecules in air, the average molecular weight DECREASES — making moist air LIGHTER than dry air. This is why warm, moist Gulf air rises so readily when it meets colder, drier polar air at a frontal boundary." }])
34 Elaborate: Blizzards & Climate Change
34.1 🌡️ Will Blizzards Get Worse in a Warming World?
This might seem like a contradiction — how can global warming lead to bigger snowstorms? Let’s investigate with data.
34.2 The Paradox: More Warming → More Snow?
It sounds counterintuitive, but a warmer atmosphere can actually fuel more intense winter storms in some regions. Here’s why:
Code
viewof showMechanism = Inputs.radio( ["Warmer = More Moisture","Arctic Amplification","Both Together"], {label:"Explore the mechanism:",value:"Warmer = More Moisture"})
Code
{const width =750;const height =400;const svg = d3.create("svg").attr("width", width).attr("height", height).attr("viewBox",`0 0 ${width}${height}`); svg.append("rect").attr("width", width).attr("height", height).attr("fill","#f8f9fa").attr("rx",10);if (showMechanism ==="Warmer = More Moisture") { svg.append("text").attr("x",375).attr("y",35).attr("text-anchor","middle").attr("font-size",18).attr("font-weight","bold").text("🌡️ Warmer Air Holds More Water Vapor");// Clausius-Clapeyron: ~7% more moisture per 1°C warmingconst tempData = d3.range(-10,35,1).map(t => ({temp: t,moisture:3.5*Math.exp(0.07* (t +10))}));// Simple bar-style chartconst xScale = d3.scaleLinear().domain([-10,34]).range([80,700]);const yScale = d3.scaleLinear().domain([0,30]).range([350,60]);// Axes svg.append("line").attr("x1",80).attr("y1",350).attr("x2",700).attr("y2",350).attr("stroke","#2d3436"); svg.append("line").attr("x1",80).attr("y1",60).attr("x2",80).attr("y2",350).attr("stroke","#2d3436"); svg.append("text").attr("x",390).attr("y",390).attr("text-anchor","middle").attr("font-size",13).text("Temperature (°C)"); svg.append("text").attr("x",30).attr("y",200).attr("text-anchor","middle").attr("font-size",12).attr("transform","rotate(-90, 30, 200)").text("Max Moisture (g/kg)");// Curve svg.append("path").attr("d", d3.line().x(d =>xScale(d.temp)).y(d =>yScale(d.moisture)).curve(d3.curveBasis)(tempData)).attr("stroke","#e74c3c").attr("stroke-width",3).attr("fill","none");// Annotation svg.append("rect").attr("x",400).attr("y",80).attr("width",280).attr("height",90).attr("fill","#fff3e0").attr("rx",8); svg.append("text").attr("x",540).attr("y",105).attr("text-anchor","middle").attr("font-size",13).attr("font-weight","bold").text("Clausius-Clapeyron Relation:"); svg.append("text").attr("x",540).attr("y",125).attr("text-anchor","middle").attr("font-size",12).text("For every 1°C of warming, air can"); svg.append("text").attr("x",540).attr("y",145).attr("text-anchor","middle").attr("font-size",13).attr("font-weight","bold").attr("fill","#e74c3c").text("hold ~7% more water vapor"); svg.append("text").attr("x",540).attr("y",162).attr("text-anchor","middle").attr("font-size",12).text("→ More fuel for storms!"); } elseif (showMechanism ==="Arctic Amplification") { svg.append("text").attr("x",375).attr("y",35).attr("text-anchor","middle").attr("font-size",18).attr("font-weight","bold").text("🧊 Arctic Amplification & the Jet Stream");// Simplified diagram svg.append("rect").attr("x",50).attr("y",60).attr("width",650).attr("height",130).attr("fill","#dfe6e9").attr("rx",8); svg.append("text").attr("x",375).attr("y",90).attr("text-anchor","middle").attr("font-size",14).attr("font-weight","bold").text("BEFORE: Strong temperature gradient → Tight, fast jet stream"); svg.append("path").attr("d","M 80,150 Q 200,130 300,150 Q 400,170 500,150 Q 600,130 680,150").attr("stroke","#0984e3").attr("stroke-width",4).attr("fill","none"); svg.append("text").attr("x",100).attr("y",180).attr("font-size",12).attr("fill","#0984e3").text("Jet stream stays mostly straight → storms track predictably"); svg.append("rect").attr("x",50).attr("y",220).attr("width",650).attr("height",150).attr("fill","#ffeaa7").attr("rx",8); svg.append("text").attr("x",375).attr("y",250).attr("text-anchor","middle").attr("font-size",14).attr("font-weight","bold").text("AFTER: Reduced gradient → Wavy, slow jet stream"); svg.append("path").attr("d","M 80,310 Q 160,260 240,320 Q 320,380 400,300 Q 480,220 560,320 Q 640,370 680,310").attr("stroke","#e74c3c").attr("stroke-width",4).attr("fill","none"); svg.append("text").attr("x",100).attr("y",360).attr("font-size",12).attr("fill","#e74c3c").text("Jet stream develops deep waves → cold air plunges south, storms stall"); } else { svg.append("text").attr("x",375).attr("y",35).attr("text-anchor","middle").attr("font-size",18).attr("font-weight","bold").text("🌨️ Combined Effect: More Intense Blizzards");const boxes = [ {x:50,y:60,w:300,h:80,color:"#e74c3c",text:"🌡️ Warmer atmosphere\nholds ~7% more moisture per °C"}, {x:400,y:60,w:300,h:80,color:"#0984e3",text:"🧊 Arctic warming weakens\njet stream → deeper troughs"}, {x:150,y:180,w:450,h:60,color:"#6c5ce7",text:"When deep cold outbreaks meet extra-moist air..."}, {x:100,y:280,w:550,h:90,color:"#2d3436",text:"⛈️ RESULT: Potentially MORE INTENSE blizzards\neven as average winters get milder\n(fewer but stronger storms)"} ]; boxes.forEach(b => { svg.append("rect").attr("x", b.x).attr("y", b.y).attr("width", b.w).attr("height", b.h).attr("fill", b.color).attr("opacity",0.15).attr("rx",10).attr("stroke", b.color).attr("stroke-width",2);const lines = b.text.split("\n"); lines.forEach((line, i) => { svg.append("text").attr("x", b.x+ b.w/2).attr("y", b.y+25+ i *20).attr("text-anchor","middle").attr("font-size",13).attr("font-weight", i ===0?"bold":"normal").text(line); }); });// Connecting arrows svg.append("line").attr("x1",200).attr("y1",140).attr("x2",300).attr("y2",180).attr("stroke","#636e72").attr("stroke-width",2); svg.append("line").attr("x1",550).attr("y1",140).attr("x2",450).attr("y2",180).attr("stroke","#636e72").attr("stroke-width",2); svg.append("line").attr("x1",375).attr("y1",240).attr("x2",375).attr("y2",280).attr("stroke","#636e72").attr("stroke-width",2); }return svg.node();}
34.3 Snowfall Trends: The Data
Code
snowData = [ {decade:"1960s",extremeEvents:3,avgSnowfall:58}, {decade:"1970s",extremeEvents:4,avgSnowfall:62}, {decade:"1980s",extremeEvents:3,avgSnowfall:55}, {decade:"1990s",extremeEvents:5,avgSnowfall:52}, {decade:"2000s",extremeEvents:6,avgSnowfall:48}, {decade:"2010s",extremeEvents:8,avgSnowfall:45}, {decade:"2020s*",extremeEvents:4,avgSnowfall:42}]Plot.plot({title:"Northeast US: Extreme Snowfall Events vs. Average Annual Snowfall",subtitle:"More extreme events even as average snowfall decreases",width:700,height:350,x: {label:"Decade",padding:0.3},y: {label:"Count / Inches"},color: {legend:true},marks: [ Plot.barY(snowData, {x:"decade",y:"extremeEvents",fill:"#3498db",title: d =>`${d.extremeEvents} extreme events`,tip:true}), Plot.dot(snowData, {x:"decade",y:"avgSnowfall",fill:"#e74c3c",r:8,tip:true}), Plot.line(snowData, {x:"decade",y:"avgSnowfall",stroke:"#e74c3c",strokeWidth:2,tip:true}), Plot.text([{x:"2020s*",y:48}], {x:"x",y:"y",text: d =>"🔴 Avg Annual Snowfall (inches)",dx:-80,fill:"#e74c3c",fontSize:11}), Plot.text([{x:"2020s*",y:7}], {x:"x",y:"y",text: d =>"🔵 Extreme events per decade",dx:-80,fill:"#3498db",fontSize:11}) ]})
Code
buildQuiz("climate-paradox", [ {q:"The NYC snowfall charts show total seasonal snowfall DECLINING over the 20th century. Does this mean blizzards are becoming less dangerous?",options: ["Yes — less total snow means weaker storms across the board","No — the peak single-month snowfall has INCREASED since the 2000s; fewer but more intense extreme events are possible","Yes — warming temperatures prevent any future blizzard conditions","No — the data shows both total and peak snowfall are increasing simultaneously" ],correct:1,explanation:"This is the winter storm paradox! The 15-year rolling average of peak monthly snowfall INCREASED after 2000, even as seasonal totals declined. This means the number of small-to-moderate snowfall events is decreasing, but the most extreme individual storms may be getting more intense. The 2025-26 season already shows a 24.9-inch February — the highest single-month total in decades." }, {q:"The Clausius-Clapeyron relation: for every 1°C of warming, air holds ~7% more water vapor. What does this mean for blizzard intensity?",options: ["Blizzards will disappear because warmer temperatures will keep precipitation above freezing","When cold outbreaks DO occur, the extra moisture in the atmosphere can fuel heavier snowfall than the same cold event would have produced in a cooler era","The 7% increase is negligible at the scale of weather systems","More moisture only affects summer storms — not cold-season precipitation" ],correct:1,explanation:"More atmospheric moisture = more potential snowfall when temperatures are cold enough. Even if severe cold outbreaks become less frequent, when they DO occur (driven by a wavy jet stream), they can tap into an atmosphere loaded with extra water vapor. This extra fuel can produce heavier snowfall than the same cold air mass would have in the 1950s or 1960s." }, {q:"What is 'Arctic amplification' and how does it connect to blizzards in the northeastern US?",options: ["The Arctic growing larger, physically blocking cold air from moving southward","The Arctic warming 2–4× faster than the global average, weakening the polar jet stream so it develops larger meanders that can send Arctic air deep into the US","A natural 60-year climate cycle that has always caused periodic blizzard outbreaks","Arctic amplification only affects sea ice and marine ecosystems with no impact on mid-latitude weather" ],correct:1,explanation:"Arctic amplification reduces the temperature difference between the poles and mid-latitudes. This temperature contrast is what keeps the polar jet stream tight and fast. A weaker, wavier jet stream develops deeper Rossby waves — meanders that can dip Arctic air far south into the US, even into Georgia or Texas. When these cold outbreaks encounter extra-moist air, intense blizzards can result." }])
🌨️ Blizzard Myths vs. Facts
Think you know blizzards? Is each statement a MYTH or a FACT?
Card 1 of 10
Score: 0 / 0
🎉
Quiz complete!
Final score: / 10
35 Evaluate: Putting It All Together
35.1 ✅ Assessment: Build Your Blizzard Model
You now have all the pieces to explain how blizzards form and how they may change with climate change. Let’s check your understanding!
35.1.1 🧠 Comprehensive Unit Quiz
Test your complete understanding of the blizzard formation model from wind to climate change!
Code
buildUnitQuiz("blizzard-unit-quiz","🧠 Comprehensive Unit Quiz", [ {q:"What is the FUNDAMENTAL cause of all surface wind?",options: ["Temperature differences between Earth's surface and the upper atmosphere","Pressure differences created by uneven heating — air flows from HIGH to LOW pressure","Earth's rotation deflecting air molecules sideways (Coriolis Effect)","Evaporation of ocean water creating updrafts near the equator" ],correct:1,explanation:"The pressure gradient force — pushing air from high to low pressure — is the fundamental driver of all wind. Uneven heating creates temperature differences → density differences → pressure differences. The Coriolis Effect then curves the wind, but the pressure gradient is always the primary driver." }, {q:"A cold, dry air mass that forms over central Canada in January is classified as:",options: ["mT — maritime Tropical","cP — continental Polar","mP — maritime Polar","cA — continental Arctic"],correct:1,explanation:"Continental Polar (cP): 'c' = forms over LAND (dry), 'P' = polar latitude (cold). This is the primary cold air mass responsible for most US blizzards. True cA (continental Arctic) forms only over the permanent Arctic ice cap and is even more extreme." }, {q:"In a mature mid-latitude cyclone, where are blizzard conditions (heavy snow + extreme winds) most likely to occur?",options: ["Ahead of the warm front, in the warm and moist sector","North and west of the low center — where cold air, strong winds, and heavy precipitation all overlap","Exactly at the eye of the low-pressure center, where pressure is lowest","South of the cold front, in the retreating warm air mass" ],correct:1,explanation:"The blizzard zone needs all three ingredients at once: (1) sub-freezing temperatures from cold polar air wrapping north/west; (2) extreme winds from the steep pressure gradient near the low; (3) heavy precipitation from moist air being lifted along the cold front. Only the north/west quadrant has all three!" }, {q:"What process causes water vapor to condense into snow at a frontal boundary?",options: ["Compression heating increases air density until condensation is forced","Adiabatic cooling — rising air expands as pressure decreases, cooling until it reaches its dew point","Cold polar air absorbs moisture from warm air molecules at the surface","Solar radiation activates ice nuclei already present in the cloud" ],correct:1,explanation:"Adiabatic cooling: warm moist air is forced UP at the front, moves into lower-pressure regions, EXPANDS (doing work against surroundings), and therefore COOLS without exchanging heat. When it cools to its dew point, water vapor condenses. If temperatures stay below 32°F from cloud to ground, precipitation falls and stays as snow." }, {q:"Climate change and the Clausius-Clapeyron relation (~7% more moisture per °C of warming): what does this mean for blizzard intensity?",options: ["Blizzards will disappear as warmer temperatures prevent any freezing precipitation","When cold outbreaks occur, extra atmospheric moisture can fuel heavier snowfall than the same cold event would have produced in a cooler era","The 7% change is too small to have any measurable effect on storm precipitation","More moisture only makes summer convective storms stronger, not winter cyclones" ],correct:1,explanation:"More atmospheric moisture = more potential snowfall when temperatures are cold enough. Even if severe cold outbreaks become less frequent, when they DO occur (driven by Arctic amplification and a wavy jet stream), they encounter an atmosphere loaded with extra water vapor. This extra fuel can produce historically extreme snowfall — the 'less frequent but more intense' pattern visible in the NYC snowfall data." }, {q:"Arctic amplification (the Arctic warming 2–4× faster than the global average) affects mid-latitude blizzards by:",options: ["Physically blocking cold air from escaping the Arctic region","Reducing the temperature contrast that keeps the polar jet stream tight, causing it to develop deep meanders that send Arctic air far south","Only affecting sea ice and Arctic marine ecosystems with no mid-latitude impact","Permanently locking cold air in the Arctic and reducing blizzard risk in the US" ],correct:1,explanation:"The polar jet stream is maintained by the temperature contrast between cold polar and warm tropical air. Arctic amplification weakens this contrast, slowing and waving the jet stream. Deeper Rossby wave troughs can send Arctic air deep into the southern US. When this frigid air encounters a moisture-laden atmosphere (thanks to warm oceans), catastrophic blizzards can result — exactly the conditions that made Winter Storm Jonas possible." }, {q:"What is the official National Weather Service (NWS) definition of a blizzard?",options: ["Any storm that drops more than 12 inches of snow in 24 hours","Sustained 35+ mph winds AND visibility under ¼ mile due to snow or blowing snow for at least 3 consecutive hours","Temperatures below 10°F combined with any snowfall","A winter storm watch upgraded to a warning by the NWS" ],correct:1,explanation:"Heavy snow alone is NOT a blizzard. You need the trifecta: 35+ mph sustained winds, visibility reduced to under ¼ mile by falling or blowing snow, and both conditions lasting at least 3 continuous hours. A storm can produce 2 feet of snow and still not be a blizzard if wind speeds are low." }, {q:"Due to the Coriolis Effect, surface winds spiral INTO a Northern Hemisphere low-pressure center in which direction?",options: ["Clockwise — same as water draining in a bathtub","Counterclockwise — deflected to the right as it flows inward","Directly inward with no rotation — the Coriolis Effect only applies at high altitude","Outward and clockwise — away from the low-pressure center" ],correct:1,explanation:"In the Northern Hemisphere, the Coriolis Effect deflects moving air to the RIGHT. As air rushes inward toward a low, those rightward deflections produce a counterclockwise spin. This is why all Northern Hemisphere lows (hurricanes, mid-latitude cyclones, blizzard-producing storms) rotate counterclockwise. Southern Hemisphere lows spin clockwise." }, {q:"Along a WARM front (warm air advancing over cold air), what type of precipitation and cloud sequence is typically observed?",options: ["Sudden, violent thunderstorms with large hail along a sharp narrow band","Gradual, widespread precipitation — high cirrus clouds hours in advance, then thickening stratus clouds, then steady rain or snow","Clear skies immediately followed by a brief snow squall","Only freezing rain, because warm air cannot produce snowfall" ],correct:1,explanation:"Warm fronts have a very gentle slope — warm air glides slowly up and over cold air across hundreds of miles. This gradual lifting produces layered stratus clouds that thicken over 12–24 hours before precipitation begins. The sequence is: cirrus → cirrostratus → altostratus → nimbostratus → steady precipitation. Contrast with cold fronts, which are steep and produce short, violent weather." }, {q:"Why do the HEAVIEST snowfalls usually occur at temperatures between 25–32°F rather than at -10°F?",options: ["Snowflakes can only form above 20°F; below that, precipitation falls as sleet","Very cold Arctic air is extremely dry and holds almost no moisture — heavy snow requires air near 32°F combined with a nearby warm moisture source","Wind speeds are always higher at -10°F, which prevents snow from accumulating","At temperatures below 20°F, precipitation freezes before leaving the cloud" ],correct:1,explanation:"The Clausius-Clapeyron equation tells us that colder air holds exponentially LESS water vapor. Air at -10°F is essentially a desert in terms of moisture content. The biggest snowstorms combine two air masses: cold enough to freeze precipitation (25–32°F is 'optimal snow-making temperature') AND close to a warm, moist source like the Gulf of Mexico or Atlantic Ocean. Deep Arctic cold alone never produces historic snowfall totals." }])
36 Summary: Key Takeaways
Concept
Key Idea
Wind
Caused by pressure differences from uneven heating
Air Masses
Large bodies of air with uniform temp & humidity
Fronts
Boundaries between air masses; where weather happens
Mid-Latitude Cyclone
Low-pressure system with warm & cold fronts; produces blizzards
Precipitation
Warm moist air rises at front → cools → condenses → snow/rain
Climate Connection
Warmer air holds more moisture + weakened jet stream = potentially more intense blizzards
Next up: We’ll investigate why storms follow the paths they do — and whether those paths are changing. 🗺️
---title: "Unit 5: Blizzards 5E"subtitle: "How do severe winter storms form, and could they become worse in the future?"author: "Earth & Space Science"format: html: toc: false toc-depth: 3 number-sections: true code-fold: trueexecute: echo: false warning: false---```{=html}<style>@import url('https://fonts.googleapis.com/css2?family=Space+Grotesk:wght@700&family=Inter:wght@400;600;800&display=swap');.engage-box { background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); color: white; padding: 25px; margin: 20px 0; border-radius: 15px; box-shadow: 0 10px 30px rgba(102, 126, 234, 0.3); border: none;}.engage-box h2 { font-family: 'Space Grotesk', sans-serif; font-size: 2em; margin-top: 0; }.explore-box { background: linear-gradient(135deg, #f093fb 0%, #f5576c 100%); color: white; padding: 25px; margin: 20px 0; border-radius: 15px; box-shadow: 0 10px 30px rgba(240, 147, 251, 0.3);}.explain-box { background: linear-gradient(135deg, #4facfe 0%, #00f2fe 100%); color: #1a1a1a; padding: 25px; margin: 20px 0; border-radius: 15px; box-shadow: 0 10px 30px rgba(79, 172, 254, 0.3);}.elaborate-box { background: linear-gradient(135deg, #43e97b 0%, #38f9d7 100%); color: #1a1a1a; padding: 25px; margin: 20px 0; border-radius: 15px; box-shadow: 0 10px 30px rgba(67, 233, 123, 0.3);}.evaluate-box { background: linear-gradient(135deg, #fa709a 0%, #fee140 100%); color: #1a1a1a; padding: 25px; margin: 20px 0; border-radius: 15px; box-shadow: 0 10px 30px rgba(250, 112, 154, 0.3);}.check-understanding { background: linear-gradient(to right, #667eea, #764ba2); color: white; padding: 20px; margin: 20px 0; border-radius: 12px; border-left: 6px solid #fff;}.key-idea { background: linear-gradient(135deg, #ffecd2 0%, #fcb69f 100%); padding: 20px; margin: 20px 0; border-radius: 12px; border-left: 8px solid #ff6b6b; font-size: 1.1em;}.lab-activity { background: linear-gradient(to bottom, #ffeaa7, #fdcb6e); border: 4px solid #e17055; padding: 25px; margin: 25px 0; border-radius: 15px; box-shadow: 0 8px 20px rgba(0,0,0,0.1);}.quiz-section { background: linear-gradient(135deg, #a8edea 0%, #fed6e3 100%); padding: 25px; margin: 25px 0; border-radius: 15px; border: 4px dashed #ff6b6b;}.pe-badge { display: inline-block; background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); color: white; padding: 8px 16px; border-radius: 20px; font-size: 13px; font-weight: 800; margin: 5px; box-shadow: 0 4px 15px rgba(102, 126, 234, 0.4); text-transform: uppercase; letter-spacing: 1px;}.big-question { font-family: 'Space Grotesk', sans-serif; font-size: 2.5em; font-weight: 800; background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); -webkit-background-clip: text; -webkit-text-fill-color: transparent; margin: 30px 0 20px 0; text-align: center; animation: slideIn 0.8s ease-out;}@keyframes slideIn { from { opacity: 0; transform: translateY(-20px); } to { opacity: 1; transform: translateY(0); }}.mind-blown { background: linear-gradient(135deg, #3498db 0%, #2980b9 100%); color: white; padding: 20px; margin: 20px 0; border-radius: 15px; font-size: 1.3em; font-weight: 700; text-align: center; box-shadow: 0 10px 25px rgba(52, 152, 219, 0.4);}.student-task { background: #fff3e0; border-left: 5px solid #ff9800; padding: 20px; margin: 15px 0; border-radius: 0 10px 10px 0;}.weather-map-note { background: linear-gradient(135deg, #dfe6e9 0%, #b2bec3 100%); padding: 20px; margin: 15px 0; border-radius: 12px; border-left: 6px solid #2d3436;}h1 { font-family: 'Space Grotesk', sans-serif !important; font-weight: 800 !important; font-size: 2.8em !important; margin-top: 40px !important;}h2 { font-family: 'Space Grotesk', sans-serif !important; font-weight: 700 !important; color: #667eea !important;}@keyframes quizFlash { 0%, 100% { transform: scale(1); box-shadow: 0 4px 20px rgba(102, 126, 234, 0.4); } 50% { transform: scale(1.05); box-shadow: 0 8px 40px rgba(118, 75, 162, 0.9), 0 0 0 8px rgba(102, 126, 234, 0.22); }}.quiz-jump-btn { display: inline-block; background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); color: white !important; padding: 8px 16px; border-radius: 20px; font-size: 13px; font-weight: 800; margin: 5px; border: none; cursor: pointer; text-decoration: none !important; animation: quizFlash 1.6s ease-in-out infinite; letter-spacing: 1px; text-transform: uppercase; vertical-align: middle; box-shadow: 0 4px 15px rgba(102, 126, 234, 0.4); transition: transform 0.2s, box-shadow 0.2s;}.quiz-jump-btn:hover { animation: none; background: linear-gradient(135deg, #5a6fd8 0%, #6a42a0 100%); box-shadow: 0 8px 30px rgba(102, 126, 234, 0.65); transform: scale(1.07); color: white !important; text-decoration: none !important;}</style>```<span class="pe-badge">HS-ESS2-8</span> <span class="pe-badge">Time: 7–13 Days</span> <a class="quiz-jump-btn" href="#blizzard-myths">🧠 Quiz & Myths ↓</a><div class="big-question">🌨️ Winter Storm Jonas: Anatomy of a Blizzard 🌨️</div># Engage: The Investigative Phenomenon::: {.engage-box}## ❄️ Winter Storm Jonas (January 22–24, 2016)<div style="font-size: 1.3em; font-weight: 700; text-align: center; margin: 20px 0; text-shadow: 2px 2px 4px rgba(0,0,0,0.3);">Winter storm Jonas produced strong enough winds and enough snow to cause significant disruptions to society, damage to property, and harm to human life.</div>**Quick Facts:**- 📏 Up to **40 inches** of snow in parts of West Virginia- 💨 Wind gusts exceeding **60 mph** along the coast- 🏙️ New York City received **27.5 inches** — near-record snowfall- ⚡ Over **300,000** power outages across the Mid-Atlantic- 💰 Estimated **$3 billion** in damages- 😢 At least **55 deaths** attributed to the storm### 🤔 Driving Questions:- What causes the **wind** associated with a blizzard?- What causes the **snow and precipitation** during a storm like this?- Could storms like Jonas become more common or more intense?:::```{=html}<iframe src="nycsnowfall.html" width="100%" height="1200px" style="border:none;"></iframe>``````{=html}<iframe src="nycsnowbymonth.html" width="100%" height="1200px" style="border:none;"></iframe>```::: {.key-idea}### 🌨️ The Winter Storm Paradox: Less Snow Overall, But Fiercer Storms?The chart above shows NYC's total seasonal snowfall **trending downward** over 150+ years. But does that mean blizzards are becoming a thing of the past?Not exactly. Climate scientists have identified a striking pattern: **as average snowfall totals decline, the most intense individual storm events are becoming more powerful.** Warmer oceans inject more moisture into storm systems, and disruptions to the polar vortex can still funnel frigid Arctic air deep into the Northeast — creating the perfect recipe for a catastrophic blizzard, even in an otherwise low-snow year.The two charts below reveal this paradox using the same NYC Central Park data.:::```{ojs}//| echo: false// NYC Annual Snowfall Data (1869–2025)// year = start year of the winter season (e.g. 2009 = winter 2009–10)// total = total seasonal snowfall (inches) — note: 2025-26 is a partial season// maxMonth = largest single-month snowfall that season (inches)// Source: NOAA / NWS Central Park stationnycStormData = [ {year:1869,total:27.8,maxMonth:9.6},{year:1870,total:33.1,maxMonth:15.9}, {year:1871,total:14.1,maxMonth:5.1},{year:1872,total:60.3,maxMonth:27.0}, {year:1873,total:36.9,maxMonth:19.0},{year:1874,total:57.8,maxMonth:15.3}, {year:1875,total:18.3,maxMonth:12.5},{year:1876,total:40.4,maxMonth:20.5}, {year:1877,total:8.1,maxMonth:6.1},{year:1878,total:35.7,maxMonth:17.3}, {year:1879,total:22.7,maxMonth:8.3},{year:1880,total:35.5,maxMonth:11.5}, {year:1881,total:31.4,maxMonth:17.5},{year:1882,total:44.0,maxMonth:10.1}, {year:1883,total:43.1,maxMonth:22.5},{year:1884,total:34.2,maxMonth:14.5}, {year:1885,total:20.8,maxMonth:13.5},{year:1886,total:32.9,maxMonth:10.3}, {year:1887,total:45.6,maxMonth:22.3},{year:1888,total:16.5,maxMonth:7.0}, {year:1889,total:24.3,maxMonth:17.0},{year:1890,total:28.8,maxMonth:11.4}, {year:1891,total:25.4,maxMonth:12.3},{year:1892,total:49.7,maxMonth:17.8}, {year:1893,total:36.1,maxMonth:20.5},{year:1894,total:27.0,maxMonth:9.5}, {year:1895,total:46.3,maxMonth:30.5},{year:1896,total:43.6,maxMonth:13.0}, {year:1897,total:21.1,maxMonth:9.0},{year:1898,total:55.9,maxMonth:25.3}, {year:1899,total:13.4,maxMonth:6.5},{year:1900,total:9.1,maxMonth:7.0}, {year:1901,total:30.0,maxMonth:15.8},{year:1902,total:28.7,maxMonth:14.4}, {year:1903,total:32.3,maxMonth:15.6},{year:1904,total:48.1,maxMonth:21.6}, {year:1905,total:20.0,maxMonth:11.5},{year:1906,total:53.2,maxMonth:21.8}, {year:1907,total:33.4,maxMonth:14.6},{year:1908,total:20.3,maxMonth:11.3}, {year:1909,total:27.2,maxMonth:11.1},{year:1910,total:25.2,maxMonth:13.3}, {year:1911,total:29.5,maxMonth:13.0},{year:1912,total:15.3,maxMonth:11.4}, {year:1913,total:40.5,maxMonth:21.5},{year:1914,total:28.8,maxMonth:7.7}, {year:1915,total:50.7,maxMonth:25.5},{year:1916,total:50.7,maxMonth:14.5}, {year:1917,total:34.5,maxMonth:14.1},{year:1918,total:3.8,maxMonth:2.7}, {year:1919,total:47.6,maxMonth:25.3},{year:1920,total:18.6,maxMonth:13.3}, {year:1921,total:27.8,maxMonth:9.4},{year:1922,total:60.4,maxMonth:24.5}, {year:1923,total:27.5,maxMonth:11.9},{year:1924,total:29.6,maxMonth:27.4}, {year:1925,total:32.4,maxMonth:26.3},{year:1926,total:22.3,maxMonth:11.7}, {year:1927,total:14.5,maxMonth:5.7},{year:1928,total:13.8,maxMonth:9.3}, {year:1929,total:13.6,maxMonth:6.3},{year:1930,total:11.6,maxMonth:5.7}, {year:1931,total:5.3,maxMonth:1.8},{year:1932,total:27.0,maxMonth:12.8}, {year:1933,total:52.0,maxMonth:27.9},{year:1934,total:33.8,maxMonth:23.6}, {year:1935,total:33.2,maxMonth:12.1},{year:1936,total:15.6,maxMonth:6.5}, {year:1937,total:15.1,maxMonth:6.5},{year:1938,total:37.3,maxMonth:10.3}, {year:1939,total:25.7,maxMonth:12.0},{year:1940,total:39.0,maxMonth:19.2}, {year:1941,total:11.3,maxMonth:6.4},{year:1942,total:29.5,maxMonth:9.5}, {year:1943,total:23.8,maxMonth:7.7},{year:1944,total:27.1,maxMonth:12.3}, {year:1945,total:31.4,maxMonth:15.6},{year:1946,total:30.6,maxMonth:17.7}, {year:1947,total:63.8,maxMonth:30.2},{year:1948,total:46.6,maxMonth:25.3}, {year:1949,total:13.8,maxMonth:8.5},{year:1950,total:11.6,maxMonth:3.8}, {year:1951,total:19.7,maxMonth:7.4},{year:1952,total:15.1,maxMonth:7.5}, {year:1953,total:15.8,maxMonth:12.7},{year:1954,total:11.5,maxMonth:5.2}, {year:1955,total:33.5,maxMonth:21.1},{year:1956,total:21.9,maxMonth:8.9}, {year:1957,total:44.7,maxMonth:15.9},{year:1958,total:13.0,maxMonth:6.7}, {year:1959,total:39.2,maxMonth:18.5},{year:1960,total:54.7,maxMonth:18.6}, {year:1961,total:18.1,maxMonth:9.6},{year:1962,total:16.3,maxMonth:5.3}, {year:1963,total:44.7,maxMonth:14.1},{year:1964,total:24.4,maxMonth:14.8}, {year:1965,total:21.4,maxMonth:11.6},{year:1966,total:51.5,maxMonth:23.6}, {year:1967,total:19.5,maxMonth:6.1},{year:1968,total:30.2,maxMonth:16.6}, {year:1969,total:25.6,maxMonth:8.4},{year:1970,total:15.5,maxMonth:11.4}, {year:1971,total:22.9,maxMonth:17.8},{year:1972,total:2.8,maxMonth:1.8}, {year:1973,total:23.5,maxMonth:9.4},{year:1974,total:13.1,maxMonth:10.6}, {year:1975,total:17.3,maxMonth:5.6},{year:1976,total:24.5,maxMonth:13.0}, {year:1977,total:50.7,maxMonth:23.0},{year:1978,total:29.4,maxMonth:20.1}, {year:1979,total:12.8,maxMonth:4.6},{year:1980,total:19.4,maxMonth:8.6}, {year:1981,total:24.6,maxMonth:11.8},{year:1982,total:27.2,maxMonth:21.5}, {year:1983,total:25.4,maxMonth:11.9},{year:1984,total:24.1,maxMonth:10.0}, {year:1985,total:13.0,maxMonth:9.9},{year:1986,total:23.1,maxMonth:13.6}, {year:1987,total:19.1,maxMonth:13.9},{year:1988,total:8.1,maxMonth:5.0}, {year:1989,total:13.4,maxMonth:3.1},{year:1990,total:24.9,maxMonth:9.1}, {year:1991,total:12.6,maxMonth:9.4},{year:1992,total:24.5,maxMonth:11.9}, {year:1993,total:53.4,maxMonth:26.4},{year:1994,total:11.8,maxMonth:11.6}, {year:1995,total:75.6,maxMonth:26.1},{year:1996,total:10.0,maxMonth:4.4}, {year:1997,total:5.5,maxMonth:5.0},{year:1998,total:12.7,maxMonth:4.5}, {year:1999,total:16.3,maxMonth:9.5},{year:2000,total:35.0,maxMonth:13.4}, {year:2001,total:3.5,maxMonth:3.5},{year:2002,total:49.3,maxMonth:26.1}, {year:2003,total:42.6,maxMonth:19.8},{year:2004,total:41.0,maxMonth:15.8}, {year:2005,total:40.0,maxMonth:26.9},{year:2006,total:12.4,maxMonth:6.0}, {year:2007,total:11.9,maxMonth:9.0},{year:2008,total:27.6,maxMonth:9.0}, {year:2009,total:51.4,maxMonth:36.9},{year:2010,total:61.9,maxMonth:36.0}, {year:2011,total:7.4,maxMonth:4.3},{year:2012,total:26.1,maxMonth:12.2}, {year:2013,total:57.4,maxMonth:29.0},{year:2014,total:50.3,maxMonth:18.6}, {year:2015,total:32.8,maxMonth:27.9},{year:2016,total:30.2,maxMonth:9.7}, {year:2017,total:40.9,maxMonth:11.6},{year:2018,total:20.5,maxMonth:10.4}, {year:2019,total:4.8,maxMonth:2.5},{year:2020,total:38.6,maxMonth:26.0}, {year:2021,total:17.9,maxMonth:15.3},{year:2022,total:2.3,maxMonth:2.2}, {year:2023,total:7.5,maxMonth:5.2},{year:2024,total:12.9,maxMonth:7.1}, {year:2025,total:44.4,maxMonth:24.9} // 2025–26: partial season (through Feb 2026)]``````{ojs}//| echo: false// Shared helper — reactive, available to all cells belowfunction rollingMean(arr, key, w = 15) { const h = Math.floor(w / 2); return arr.map((d, i) => { const slice = arr.slice(Math.max(0, i - h), Math.min(arr.length, i + h + 1)); return { year: d.year, v: slice.reduce((a, x) => a + x[key], 0) / slice.length }; });}totalRoll = rollingMean(nycStormData, "total")maxRoll = rollingMean(nycStormData, "maxMonth")``````{ojs}//| echo: falsePlot.plot({ title: "① Total Seasonal Snowfall Is Declining", width, height: 270, marginLeft: 58, marginRight: 22, marginTop: 40, x: { label: null, tickFormat: "d", ticks: [1880, 1900, 1920, 1940, 1960, 1980, 2000, 2020] }, y: { label: "Season total (inches)", domain: [0, 88], grid: true }, marks: [ Plot.barY(nycStormData, { x: "year", y: "total", fill: "#6da8d4", opacity: 0.55, tip: true, title: d => `${d.year}–${String(d.year + 1).slice(-2)}: ${d.total.toFixed(1)}"` }), Plot.line(totalRoll, { x: "year", y: "v", stroke: "#1a3a5c", strokeWidth: 3 }), Plot.text([{ year: 1935, v: 58 }], { x: "year", y: "v", text: ["← 15-yr average (declining)"], fill: "#1a3a5c", fontSize: 11, fontWeight: "600", textAnchor: "start" }) ]})``````{ojs}//| echo: falsePlot.plot({ title: "② Peak Single-Month Storm Intensity — Rising Since the 2000s", width, height: 270, marginLeft: 58, marginRight: 22, marginTop: 40, x: { label: "Season start year", tickFormat: "d", ticks: [1880, 1900, 1920, 1940, 1960, 1980, 2000, 2020] }, y: { label: "Largest monthly snowfall (inches)", domain: [0, 42], grid: true }, color: { domain: [false, true], range: ["#f0a070", "#c0392b"], legend: true, tickFormat: d => d ? "≥ 20\" (major storm)" : "< 20\"" }, marks: [ Plot.dot(nycStormData, { x: "year", y: "maxMonth", fill: d => d.maxMonth >= 20, r: d => d.maxMonth >= 20 ? 5 : 3, opacity: 0.8, tip: true, title: d => `${d.year}–${String(d.year + 1).slice(-2)}: peak ${d.maxMonth.toFixed(1)}"` }), Plot.line(maxRoll, { x: "year", y: "v", stroke: "#7b241c", strokeWidth: 3 }), Plot.ruleY([20], { stroke: "#c0392b", strokeDasharray: "6,4", opacity: 0.45 }), Plot.text([{ year: 1873, v: 21.8 }], { x: "year", y: "v", text: ["20\" threshold →"], fill: "#c0392b", fontSize: 10, fontWeight: "600", textAnchor: "start" }) ]})```<p style="color:#555; font-size:0.85rem; line-height:1.6; margin: 4px 0 24px;"><strong style="color:#c0392b">● Red dots</strong> = seasons where a single month topped 20" (a major storm).<strong>Dark lines</strong> = 15-year rolling average.Notice: while seasonal totals trend <em>downward</em>, the 15-yr average of peak monthly snowfall<em>increased sharply after 2000</em> — the 2000s and 2010s logged the highest peak-storm averagesof any era. The current 2025–26 season already recorded 24.9" in a single month (Feb 2026).</p>```{ojs}//| echo: falsebuildQuiz("nyc-snowfall", [ { q: "Charts ① and ② show opposite trends in NYC snowfall data. What is the 'winter storm paradox' they reveal?", options: [ "Both total snowfall and peak storm intensity are declining due to climate change", "Seasonal snowfall totals are declining, but peak single-storm intensity has risen — fewer but more extreme events", "Winter storms are becoming more frequent and less intense", "The data shows no clear trend; year-to-year variability is too high" ], correct: 1, explanation: "The paradox: fewer mild-to-moderate snow events lower the seasonal total, while the most extreme individual storms are delivering record single-month totals. This matches climate science predictions — warming reduces weak events but can amplify extreme ones by loading the atmosphere with extra moisture." }, { q: "The 15-year rolling average of peak monthly snowfall rose sharply after 2000. What physical mechanism best explains this?", options: [ "Measurement instruments became more accurate after 2000", "Warmer Atlantic and Gulf sea-surface temperatures supply more water vapor; when cold outbreaks occur they produce heavier single-storm totals", "The Arctic became colder after 2000, sending larger cold air masses south", "Urban heat islands in NYC increased snowfall through cloud-seeding" ], correct: 1, explanation: "Warmer ocean surfaces evaporate more water vapor (Clausius-Clapeyron: ~7% more per °C). When cold outbreaks occur, they tap this extra atmospheric moisture — resulting in heavier snowfall than the same cold event would have produced in earlier decades." }])```# Explore: What Causes Wind?::: {.explore-box}## 🔬 Investigation: Pressure & WindWind isn't random — it has a **cause**. In this section, you'll build a model of what creates wind and why it blows in specific directions.:::## The Wind Machine: Pressure DifferencesWind is caused by **differences in air pressure**. Air always moves from areas of high pressure toward areas of low pressure. The greater the pressure difference, the stronger the wind.But what causes pressure differences? **Uneven heating** of Earth's surface.```{ojs}//| echo: falsePlot = require("@observablehq/plot")d3 = require("d3@7")``````{ojs}//| echo: falsefunction buildQuiz(id, questions) { const outer = html`<div style="font-family:'Inter',sans-serif; max-width:720px; margin:18px auto;"></div>`; const toggle = html`<button style="display:inline-flex;align-items:center;gap:8px;padding:10px 22px;border:2px solid #c7d2fe;border-radius:30px;background:linear-gradient(135deg,#eef2ff 0%,#e8f4fd 100%);color:#4338ca;font-weight:700;font-size:0.95em;cursor:pointer;transition:all 0.25s ease;box-shadow:0 2px 8px rgba(67,56,202,0.12);"><span style="font-size:1.15em;">✅</span><span>Check Your Understanding</span><span class="quiz-chevron" style="display:inline-block;transition:transform 0.3s ease;font-size:0.8em;">▶</span></button>`; const body = html`<div style="display:none; margin-top:12px; border:1.5px solid #c7d2fe; border-radius:14px; overflow:hidden; background:white;"></div>`; const closeBtn = html`<div style="padding:12px 18px; background:#eef2ff; cursor:pointer; display:flex; justify-content:space-between; align-items:center;"><span style="font-weight:600; color:#4338ca;">🧠 Knowledge Check</span><span style="color:#6366f1; font-size:0.85em;">▲ Hide</span></div>`; let isOpen = false; toggle.onclick = () => { isOpen = !isOpen; body.style.display = isOpen ? "block" : "none"; toggle.querySelector(".quiz-chevron").style.transform = isOpen ? "rotate(90deg)" : ""; }; closeBtn.onclick = () => { isOpen = false; body.style.display = "none"; toggle.querySelector(".quiz-chevron").style.transform = ""; }; body.appendChild(closeBtn); questions.forEach((q, qi) => { const card = html`<div id="${id}-q${qi}" style="padding:18px 22px; border-top:1px solid #e0e7ff;"></div>`; card.appendChild(html`<div style="font-weight:700; color:#1e1b4b; margin-bottom:12px; line-height:1.5;">${qi+1}. ${q.q}</div>`); const optWrap = html`<div style="display:flex; flex-direction:column; gap:8px;"></div>`; const feedback = html`<div style="margin-top:12px; padding:12px; border-radius:10px; display:none;"></div>`; q.options.forEach((opt, oi) => { const btn = html`<button style="text-align:left; padding:10px 16px; border-radius:10px; border:2px solid #e0e7ff; background:#f5f7ff; cursor:pointer; font-size:0.95em; color:#1e1b4b; transition:all 0.2s;">${opt}</button>`; btn.onmouseenter = () => { if(!btn.disabled) btn.style.background = "#e0e7ff"; }; btn.onmouseleave = () => { if(!btn.disabled && !btn.chosen) btn.style.background = "#f5f7ff"; }; btn.onclick = () => { card.querySelectorAll("button").forEach(b => { b.disabled = true; b.chosen = false; }); btn.chosen = true; const correct = oi === q.correct; btn.style.background = correct ? "#dcfce7" : "#fee2e2"; btn.style.borderColor = correct ? "#22c55e" : "#ef4444"; if (!correct) { card.querySelectorAll("button")[q.correct].style.background = "#dcfce7"; card.querySelectorAll("button")[q.correct].style.borderColor = "#22c55e"; } feedback.style.display = "block"; feedback.style.background = correct ? "#f0fdf4" : "#fff1f2"; feedback.style.borderLeft = `4px solid ${correct ? "#22c55e" : "#ef4444"}`; feedback.innerHTML = `${q.emoji || (correct ? "✅" : "❌")} <strong>${correct ? "Correct!" : "Not quite."}</strong> ${q.explanation}`; }; optWrap.appendChild(btn); }); card.appendChild(optWrap); card.appendChild(feedback); body.appendChild(card); }); outer.appendChild(toggle); outer.appendChild(body); return outer;}``````{ojs}//| echo: falsefunction buildUnitQuiz(id, title, questions) { const outer = html`<div id="${id}" style="font-family:'Inter',sans-serif; max-width:720px; margin:18px auto;"></div>`; const header = html`<div style="background:linear-gradient(135deg,#667eea 0%,#764ba2 100%);color:#fff;border-radius:14px 14px 0 0;padding:18px 24px;"> <div style="font-size:1.1em;font-weight:700;display:flex;align-items:center;gap:8px;"><span style="font-size:1.2em;">📝</span> ${title}</div> <div style="font-size:0.85em;opacity:0.85;margin-top:4px;">Select an answer for each question.</div> </div>`; const body = html`<div style="border:1.5px solid #c7d2fe;border-top:none;border-radius:0 0 14px 14px;overflow:hidden;background:#f8f9ff;padding:16px;"></div>`; questions.forEach((qq, qi) => { const card = document.createElement("div"); card.style.cssText = "background:#fff;border-radius:12px;padding:16px 20px;margin-bottom:14px;box-shadow:0 2px 8px rgba(0,0,0,.08);"; card.innerHTML = `<p style="font-weight:700;margin:0 0 10px;color:#1e1b4b;">${qi+1}. ${qq.q}</p>`; qq.options.forEach((opt, oi) => { const btn = document.createElement("button"); btn.textContent = opt; btn.style.cssText = "display:block;width:100%;text-align:left;padding:10px 14px;margin:6px 0;border:2px solid #e2e8f0;border-radius:8px;background:#fff;color:#1e1b4b;cursor:pointer;font-size:.95em;transition:border-color .2s,background .2s;"; btn.onmouseover = () => { if(!card.dataset.answered){ btn.style.borderColor="#667eea";btn.style.background="#f0f0ff"; }}; btn.onmouseout = () => { if(!card.dataset.answered){ btn.style.borderColor="#e2e8f0";btn.style.background="#fff"; }}; btn.onclick = () => { if(card.dataset.answered) return; card.dataset.answered = "true"; // First give all buttons a neutral "not chosen" style (fully opaque, not disabled) card.querySelectorAll("button").forEach(b => { b.style.cursor="default"; b.onmouseover=null; b.onmouseout=null; b.style.borderColor="#e2e8f0"; b.style.background="#f8fafc"; b.style.color="#475569"; }); if(oi === qq.correct){ btn.style.borderColor="#48bb78";btn.style.background="#f0fff4";btn.style.color="#276749";btn.style.fontWeight="700"; fb.innerHTML = "✅ Correct! " + qq.explanation; fb.style.color="#276749";fb.style.background="#f0fff4"; } else { btn.style.borderColor="#fc8181";btn.style.background="#fff5f5";btn.style.color="#9b2c2c";btn.style.fontWeight="700"; const correctBtn = card.querySelectorAll("button")[qq.correct]; correctBtn.style.borderColor="#48bb78";correctBtn.style.background="#f0fff4";correctBtn.style.color="#276749";correctBtn.style.fontWeight="700"; fb.innerHTML = "❌ " + qq.explanation; fb.style.color="#9b2c2c";fb.style.background="#fff5f5"; } fb.style.padding="10px 14px";fb.style.marginTop="8px";fb.style.borderRadius="8px"; }; card.appendChild(btn); }); const fb = document.createElement("div"); fb.style.cssText = "border-radius:8px;font-size:.9em;transition:all .3s;"; card.appendChild(fb); body.appendChild(card); }); outer.appendChild(header); outer.appendChild(body); return outer;}``````{ojs}//| echo: false// Interactive pressure-wind modelviewof surfaceTemp = Inputs.range([20, 115], {label: "Surface Temperature (°F)", step: 5, value: 62})``````{ojs}//| echo: falseviewof neighborTemp = Inputs.range([20, 115], {label: "Neighboring Region Temp (°F)", step: 5, value: 30})``````{ojs}//| echo: false{ const width = 700; const height = 350; const svg = d3.create("svg").attr("width", width).attr("height", height).attr("viewBox", `0 0 ${width} ${height}`); // Background svg.append("rect").attr("width", width).attr("height", height).attr("fill", "#f0f4ff").attr("rx", 10); // Ground svg.append("rect").attr("x", 0).attr("y", 280).attr("width", width).attr("height", 70).attr("fill", "#8B7355"); // Left region (warmer) const leftColor = d3.interpolateRdBu(1 - (surfaceTemp - 20) / 95); svg.append("rect").attr("x", 0).attr("y", 280).attr("width", 350).attr("height", 70).attr("fill", leftColor).attr("opacity", 0.7); svg.append("text").attr("x", 175).attr("y", 310).attr("text-anchor", "middle").attr("fill", "white").attr("font-weight", "bold").text(`${surfaceTemp}°F`); // Right region (cooler) const rightColor = d3.interpolateRdBu(1 - (neighborTemp - 20) / 95); svg.append("rect").attr("x", 350).attr("y", 280).attr("width", 350).attr("height", 70).attr("fill", rightColor).attr("opacity", 0.7); svg.append("text").attr("x", 525).attr("y", 310).attr("text-anchor", "middle").attr("fill", "white").attr("font-weight", "bold").text(`${neighborTemp}°F`); // Pressure calculation (simplified: warmer = lower pressure at surface) // Convert °F to K for physics-based pressure formula const leftTempK = (surfaceTemp - 32) * 5 / 9 + 273.15; const rightTempK = (neighborTemp - 32) * 5 / 9 + 273.15; const leftPressure = 1013 - (leftTempK - 270) * 0.5; const rightPressure = 1013 - (rightTempK - 270) * 0.5; const pressureDiff = rightPressure - leftPressure; const windSpeed = Math.abs(pressureDiff) * 1.5; // Pressure labels svg.append("text").attr("x", 175).attr("y", 40).attr("text-anchor", "middle").attr("font-size", 16).attr("font-weight", "bold") .text(`P = ${leftPressure.toFixed(0)} mb`); svg.append("text").attr("x", 175).attr("y", 60).attr("text-anchor", "middle").attr("font-size", 12) .text(leftPressure < rightPressure ? "🔴 LOW PRESSURE" : "🔵 HIGH PRESSURE"); svg.append("text").attr("x", 525).attr("y", 40).attr("text-anchor", "middle").attr("font-size", 16).attr("font-weight", "bold") .text(`P = ${rightPressure.toFixed(0)} mb`); svg.append("text").attr("x", 525).attr("y", 60).attr("text-anchor", "middle").attr("font-size", 12) .text(rightPressure < leftPressure ? "🔴 LOW PRESSURE" : "🔵 HIGH PRESSURE"); // Rising/sinking air if (surfaceTemp > neighborTemp) { // Warm air rises on left for (let i = 0; i < 4; i++) { svg.append("line") .attr("x1", 120 + i * 35).attr("y1", 260).attr("x2", 120 + i * 35).attr("y2", 100) .attr("stroke", "#e74c3c").attr("stroke-width", 2).attr("marker-end", "url(#arrowRed)"); } svg.append("text").attr("x", 175).attr("y", 90).attr("text-anchor", "middle").attr("font-size", 11).attr("fill", "#e74c3c").text("↑ Warm air RISES"); // Cold air sinks on right for (let i = 0; i < 4; i++) { svg.append("line") .attr("x1", 470 + i * 35).attr("y1", 100).attr("x2", 470 + i * 35).attr("y2", 260) .attr("stroke", "#3498db").attr("stroke-width", 2).attr("marker-end", "url(#arrowBlue)"); } svg.append("text").attr("x", 525).attr("y", 90).attr("text-anchor", "middle").attr("font-size", 11).attr("fill", "#3498db").text("↓ Cool air SINKS"); } else if (neighborTemp > surfaceTemp) { for (let i = 0; i < 4; i++) { svg.append("line") .attr("x1", 470 + i * 35).attr("y1", 260).attr("x2", 470 + i * 35).attr("y2", 100) .attr("stroke", "#e74c3c").attr("stroke-width", 2); } svg.append("text").attr("x", 525).attr("y", 90).attr("text-anchor", "middle").attr("font-size", 11).attr("fill", "#e74c3c").text("↑ Warm air RISES"); for (let i = 0; i < 4; i++) { svg.append("line") .attr("x1", 120 + i * 35).attr("y1", 100).attr("x2", 120 + i * 35).attr("y2", 260) .attr("stroke", "#3498db").attr("stroke-width", 2); } svg.append("text").attr("x", 175).attr("y", 90).attr("text-anchor", "middle").attr("font-size", 11).attr("fill", "#3498db").text("↓ Cool air SINKS"); } // Wind arrow at surface if (Math.abs(pressureDiff) > 1) { const windDir = pressureDiff > 0 ? 1 : -1; const arrowX1 = windDir > 0 ? 450 : 250; const arrowX2 = windDir > 0 ? 250 : 450; svg.append("line") .attr("x1", arrowX1).attr("y1", 270).attr("x2", arrowX2).attr("y2", 270) .attr("stroke", "#2ecc71").attr("stroke-width", Math.min(windSpeed / 5, 8)) .attr("marker-end", "url(#arrowGreen)"); svg.append("text").attr("x", 350).attr("y", 255).attr("text-anchor", "middle") .attr("font-size", 14).attr("font-weight", "bold").attr("fill", "#2ecc71") .text(`WIND ${windDir > 0 ? "←" : "→"} ${(windSpeed * 0.621).toFixed(0)} mph (HIGH → LOW pressure)`); } else { svg.append("text").attr("x", 350).attr("y", 260).attr("text-anchor", "middle") .attr("font-size", 14).attr("fill", "#999").text("No significant wind (pressures nearly equal)"); } // Defs for arrowheads const defs = svg.append("defs"); defs.append("marker").attr("id", "arrowRed").attr("viewBox", "0 0 10 10").attr("refX", 5).attr("refY", 5) .attr("markerWidth", 6).attr("markerHeight", 6).attr("orient", "auto-start-reverse") .append("path").attr("d", "M 0 0 L 10 5 L 0 10 z").attr("fill", "#e74c3c"); defs.append("marker").attr("id", "arrowBlue").attr("viewBox", "0 0 10 10").attr("refX", 5).attr("refY", 5) .attr("markerWidth", 6).attr("markerHeight", 6).attr("orient", "auto-start-reverse") .append("path").attr("d", "M 0 0 L 10 5 L 0 10 z").attr("fill", "#3498db"); defs.append("marker").attr("id", "arrowGreen").attr("viewBox", "0 0 10 10").attr("refX", 5).attr("refY", 5) .attr("markerWidth", 6).attr("markerHeight", 6).attr("orient", "auto") .append("path").attr("d", "M 0 0 L 10 5 L 0 10 z").attr("fill", "#2ecc71"); return svg.node();}``````{ojs}//| echo: falseviewof pressureA = Inputs.range([950, 1050], { value: 1013, step: 1, label: "Area A (Western) mb" })``````{ojs}//| echo: falseviewof pressureB = Inputs.range([950, 1050], { value: 1013, step: 1, label: "Area B (Eastern) mb" })``````{ojs}//| echo: falsehtml`<div style="background: #1e293b; color: white; padding: 20px; border-radius: 12px; display: flex; justify-content: space-around; font-family: system-ui, sans-serif; box-shadow: 0 4px 6px rgba(0,0,0,0.1); margin-bottom: 20px;"> <div style="text-align: center;"> <div style="font-size: 0.85em; color: #94a3b8; text-transform: uppercase; font-weight: bold;">Pressure Diff</div> <div style="font-size: 1.5em; font-weight: normal; font-family: monospace;">${Math.abs(pressureA - pressureB)} mb</div> </div> <div style="text-align: center;"> <div style="font-size: 0.85em; color: #94a3b8; text-transform: uppercase; font-weight: bold;">Wind Speed</div> <div style="font-size: 1.5em; font-weight: normal; color: #facc15; font-family: monospace;">${(Math.abs(pressureA - pressureB) * 0.932).toFixed(1)} mph</div> </div> <div style="text-align: center;"> <div style="font-size: 0.85em; color: #94a3b8; text-transform: uppercase; font-weight: bold;">Direction</div> <div style="font-size: 1.5em; font-weight: normal; font-family: monospace;"> ${pressureA > pressureB ? 'A → B' : pressureA < pressureB ? 'B → A' : 'Calm'} </div> </div></div>```````{ojs}//| echo: falsewindChart = { const width = 800; const height = 400; const canvas = DOM.canvas(width, height); canvas.style.width = "100%"; canvas.style.maxWidth = "800px"; canvas.style.borderRadius = "12px"; canvas.style.boxShadow = "0 10px 15px -3px rgba(0, 0, 0, 0.1)"; const ctx = canvas.getContext("2d"); const getBackgroundColor = (pressure) => { const ratio = (pressure - 950) / (1050 - 950); const r = Math.round(100 + ratio * 30); const g = Math.round(110 + ratio * 90); const b = Math.round(120 + ratio * 135); return `rgb(${r}, ${g}, ${b})`; }; const particleCount = 400; const particles = Array.from({ length: particleCount }, () => ({ x: Math.random() * width, y: Math.random() * height, size: Math.random() * 2 + 1, speedMultiplier: Math.random() * 0.8 + 0.4, yDrift: (Math.random() - 0.5) * 0.5, phase: Math.random() * Math.PI * 2 })); let animationId; function render(time) { ctx.clearRect(0, 0, width, height); const midP = (pressureA + pressureB) / 2; const gradLeft = ctx.createLinearGradient(0, 0, width / 2, 0); gradLeft.addColorStop(0, getBackgroundColor(pressureA)); gradLeft.addColorStop(1, getBackgroundColor(midP)); const gradRight = ctx.createLinearGradient(width / 2, 0, width, 0); gradRight.addColorStop(0, getBackgroundColor(midP)); gradRight.addColorStop(1, getBackgroundColor(pressureB)); ctx.fillStyle = gradLeft; ctx.fillRect(0, 0, width / 2, height); ctx.fillStyle = gradRight; ctx.fillRect(width / 2, 0, width / 2, height); ctx.beginPath(); ctx.setLineDash([10, 10]); ctx.moveTo(width / 2, 0); ctx.lineTo(width / 2, height); ctx.strokeStyle = "rgba(255, 255, 255, 0.3)"; ctx.lineWidth = 2; ctx.stroke(); ctx.setLineDash([]); ctx.font = "bold 80px system-ui, sans-serif"; ctx.textAlign = "center"; ctx.textBaseline = "middle"; ctx.fillStyle = pressureA > pressureB ? "rgba(180, 220, 255, 0.8)" : pressureA < pressureB ? "rgba(255, 180, 180, 0.8)" : "rgba(255, 255, 255, 0.4)"; ctx.fillText(pressureA > pressureB ? "H" : pressureA < pressureB ? "L" : "", width * 0.25, height / 2); ctx.fillStyle = pressureB > pressureA ? "rgba(180, 220, 255, 0.8)" : pressureB < pressureA ? "rgba(255, 180, 180, 0.8)" : "rgba(255, 255, 255, 0.4)"; ctx.fillText(pressureB > pressureA ? "H" : pressureB < pressureA ? "L" : "", width * 0.75, height / 2); let baseVelocity = ((pressureA - pressureB) / 100) * 15; ctx.fillStyle = "rgba(255, 255, 255, 0.7)"; for (const p of particles) { let vx = baseVelocity * p.speedMultiplier; if (Math.abs(vx) < 0.1) { vx = (Math.random() - 0.5) * 0.5; p.y += (Math.random() - 0.5) * 0.5; } p.x += vx; p.y += Math.sin(time * 0.002 + p.phase) * (Math.abs(baseVelocity) * 0.05) + p.yDrift; if (p.x > width + 10) p.x = -10; if (p.x < -10) p.x = width + 10; if (p.y > height + 10) p.y = -10; if (p.y < -10) p.y = height + 10; ctx.beginPath(); const stretch = Math.max(1, Math.abs(vx) * 1.5); if (Math.abs(vx) > 1) { ctx.ellipse(p.x, p.y, p.size * stretch, p.size, vx > 0 ? 0 : Math.PI, 0, Math.PI * 2); } else { ctx.arc(p.x, p.y, p.size, 0, Math.PI * 2); } ctx.fill(); } animationId = requestAnimationFrame(render); } animationId = requestAnimationFrame(render); invalidation.then(() => cancelAnimationFrame(animationId)); return canvas;}```::: {.callout-note}Did you know? In real weather systems, wind doesn't flow in a perfectly straight line from high to low pressure. Because the Earth is spinning, the Coriolis Effect causes the wind to curve (to the right in the Northern Hemisphere, and left in the Southern Hemisphere). However, the primary driving force of the wind is always this pressure difference!:::## Atmospheric Convection CurrentsThis interactive simulation demonstrates how thermal differences create pressure zones and drive wind patterns in the troposphere. Adjust the temperatures of the land masses to observe the resulting changes in air density and wind currents.```{ojs}//| echo: false//| panel: input//| layout-ncol: 2viewof leftTemp = Inputs.range([0, 100], {value: 20, step: 1, label: "Left Land Temperature"})viewof rightTemp = Inputs.range([0, 100], {value: 80, step: 1, label: "Right Land Temperature"})//| label: convection-canvaschart = { // 1. Setup Canvas const canvas = document.createElement('canvas'); const WIDTH = 800; const HEIGHT = 600; canvas.width = WIDTH; canvas.height = HEIGHT; canvas.style.width = "100%"; canvas.style.maxWidth = "800px"; canvas.style.height = "auto"; canvas.style.border = "1px solid #e2e8f0"; canvas.style.borderRadius = "0.5rem"; canvas.style.backgroundColor = "#fff"; const ctx = canvas.getContext('2d'); // 2. Constants & Helpers const LEFT_X = 200; const RIGHT_X = 600; const TOP_Y = 120; const BOTTOM_Y_BASE = 360; const lerp = (a, b, t) => a + (b - a) * t; const getEarthY = (x) => 400 - 30 * Math.sin((x / WIDTH) * Math.PI); const getWindPos = (t, direction) => { let x, y, angle; let pathT = direction > 0 ? t : (4 - t) % 4; if (pathT < 1) { x = lerp(LEFT_X, RIGHT_X, pathT); y = getEarthY(x) - 30; angle = 0; } else if (pathT < 2) { const localT = pathT - 1; x = RIGHT_X; y = lerp(getEarthY(RIGHT_X) - 30, TOP_Y, localT); angle = -Math.PI / 2; } else if (pathT < 3) { const localT = pathT - 2; x = lerp(RIGHT_X, LEFT_X, localT); y = TOP_Y - 20 * Math.sin((x / WIDTH) * Math.PI); angle = Math.PI; } else { const localT = pathT - 3; x = LEFT_X; y = lerp(TOP_Y, getEarthY(LEFT_X) - 30, localT); angle = Math.PI / 2; } if (direction < 0) angle += Math.PI; return { x, y, angle }; }; const drawArrow = (ctx, x, y, angle, color) => { ctx.save(); ctx.translate(x, y); ctx.rotate(angle); ctx.beginPath(); ctx.moveTo(-15, -5); ctx.lineTo(5, -5); ctx.lineTo(5, -10); ctx.lineTo(15, 0); ctx.lineTo(5, 10); ctx.lineTo(5, 5); ctx.lineTo(-15, 5); ctx.closePath(); ctx.fillStyle = color; ctx.fill(); ctx.strokeStyle = 'white'; ctx.lineWidth = 1; ctx.stroke(); ctx.restore(); }; // 3. Particle System State const createParticles = (count) => Array.from({ length: count }, () => ({ x: Math.random() * 140 + 5, y: Math.random() * 140 + 5, vx: (Math.random() - 0.5) * 2, vy: (Math.random() - 0.5) * 2, })); const leftParticles = createParticles(200); const rightParticles = createParticles(200); let windOffset = 0; let animationFrameId; // 4. Main Animation Loop function render() { // Read directly from the DOM elements to prevent reactive re-evaluation of the cell, // which would cause the canvas/particles to reset every time the slider moves. const lTemp = viewof leftTemp.value; const rTemp = viewof rightTemp.value; const tempDiff = rTemp - lTemp; const windSpeed = Math.abs(tempDiff) * 0.0003; const direction = Math.sign(tempDiff); windOffset = (windOffset + windSpeed) % 4; // --- Draw Sky --- ctx.fillStyle = '#f0f9ff'; ctx.fillRect(0, 0, WIDTH, HEIGHT); const skyGradient = ctx.createLinearGradient(0, 0, 0, 400); skyGradient.addColorStop(0, '#bae6fd'); skyGradient.addColorStop(1, '#e0f2fe'); ctx.fillStyle = skyGradient; ctx.fillRect(0, 0, WIDTH, HEIGHT); // --- Draw Upper Troposphere --- ctx.beginPath(); for (let x = 0; x <= WIDTH; x += 10) { const y = TOP_Y - 40 - 20 * Math.sin((x / WIDTH) * Math.PI); if (x === 0) ctx.moveTo(x, y); else ctx.lineTo(x, y); } ctx.strokeStyle = '#94a3b8'; ctx.lineWidth = 2; ctx.setLineDash([10, 10]); ctx.stroke(); ctx.setLineDash([]); ctx.fillStyle = '#64748b'; ctx.font = '14px sans-serif'; ctx.textAlign = 'center'; ctx.fillText('Upper Troposphere = cool', WIDTH / 2, TOP_Y - 70); // --- Draw Auras --- const drawAura = (x, y, temp) => { const gradient = ctx.createRadialGradient(x, y, 10, x, y, 120); if (temp > 50) { const intensity = (temp - 50) / 50; gradient.addColorStop(0, `rgba(239, 68, 68, ${intensity * 0.6})`); } else { const intensity = (50 - temp) / 50; gradient.addColorStop(0, `rgba(59, 130, 246, ${intensity * 0.6})`); } gradient.addColorStop(1, 'rgba(255, 255, 255, 0)'); ctx.fillStyle = gradient; ctx.beginPath(); ctx.arc(x, y, 120, 0, Math.PI * 2); ctx.fill(); }; drawAura(LEFT_X, getEarthY(LEFT_X), lTemp); drawAura(RIGHT_X, getEarthY(RIGHT_X), rTemp); // --- Draw Earth Surface --- ctx.beginPath(); for (let x = 0; x <= WIDTH; x += 5) { if (x === 0) ctx.moveTo(x, getEarthY(x)); else ctx.lineTo(x, getEarthY(x)); } ctx.lineTo(WIDTH, HEIGHT); ctx.lineTo(0, HEIGHT); ctx.closePath(); ctx.fillStyle = '#22c55e'; ctx.fill(); ctx.strokeStyle = '#166534'; ctx.lineWidth = 4; ctx.stroke(); // --- Draw Wind Arrows --- if (Math.abs(tempDiff) > 2) { const arrowCount = 12; for (let i = 0; i < arrowCount; i++) { const t = (windOffset + (i / arrowCount) * 4) % 4; const pos = getWindPos(t, direction); let color = '#94a3b8'; if (direction > 0) { if (t > 0.5 && t < 1.5) color = '#ef4444'; if (t > 2.5 && t < 3.5) color = '#3b82f6'; } else { if (t > 2.5 && t < 3.5) color = '#ef4444'; if (t > 0.5 && t < 1.5) color = '#3b82f6'; } drawArrow(ctx, pos.x, pos.y, pos.angle, color); } } // --- Draw Text Labels --- ctx.font = 'bold 14px sans-serif'; ctx.textAlign = 'center'; const drawSideLabels = (x, temp, isLeft) => { const isWarm = temp > 50; const yBase = getEarthY(x); ctx.fillStyle = isWarm ? '#7f1d1d' : '#1e3a8a'; ctx.fillText(`Land is ${isWarm ? 'warmer' : 'cooler'} here`, x, yBase + 25); if (Math.abs(tempDiff) > 2) { const isHighPressure = (isLeft && direction > 0) || (!isLeft && direction < 0); ctx.fillStyle = isHighPressure ? '#1d4ed8' : '#b91c1c'; ctx.fillText(`${isHighPressure ? 'High' : 'Low'} pressure`, x - 50, yBase - 40); ctx.fillText(isHighPressure ? 'Cool sinking air' : 'Warm rising air', x, yBase - 120); } else { ctx.fillStyle = '#334155'; ctx.fillText('Equal pressure', x - 40, yBase - 40); } }; drawSideLabels(LEFT_X, lTemp, true); drawSideLabels(RIGHT_X, rTemp, false); ctx.fillStyle = '#334155'; ctx.textAlign = 'left'; ctx.fillText('Troposphere }', 20, (TOP_Y + BOTTOM_Y_BASE)/2); // --- Draw Magnifying Particle Boxes --- const drawBox = (boxX, boxY, targetX, targetY, temp, particlesArr) => { const boxSize = 130; ctx.beginPath(); ctx.moveTo(boxX, boxY); ctx.lineTo(targetX - 20, targetY); ctx.lineTo(boxX + boxSize, boxY); ctx.moveTo(boxX + boxSize, boxY); ctx.lineTo(targetX + 20, targetY); ctx.strokeStyle = 'rgba(0,0,0,0.3)'; ctx.lineWidth = 1; ctx.stroke(); ctx.fillStyle = '#ffffff'; ctx.fillRect(boxX, boxY, boxSize, boxSize); ctx.strokeStyle = '#000000'; ctx.lineWidth = 2; ctx.strokeRect(boxX, boxY, boxSize, boxSize); const densityCount = Math.floor(180 - temp * 1.5); const speedMultiplier = 0.2 + (temp / 100) * 1.5; for (let i = 0; i < densityCount; i++) { const p = particlesArr[i]; p.x += p.vx * speedMultiplier; p.y += p.vy * speedMultiplier; if (p.x < 4 || p.x > boxSize - 4) p.vx *= -1; if (p.y < 4 || p.y > boxSize - 4) p.vy *= -1; p.x = Math.max(4, Math.min(boxSize - 4, p.x)); p.y = Math.max(4, Math.min(boxSize - 4, p.y)); ctx.beginPath(); ctx.arc(boxX + p.x, boxY + p.y, 4, 0, Math.PI * 2); const gradient = ctx.createRadialGradient( boxX + p.x - 1, boxY + p.y - 1, 0, boxX + p.x, boxY + p.y, 4 ); gradient.addColorStop(0, '#ffffff'); gradient.addColorStop(1, '#000000'); ctx.fillStyle = gradient; ctx.fill(); } }; drawBox(LEFT_X - 65, 440, LEFT_X, getEarthY(LEFT_X) - 5, lTemp, leftParticles); drawBox(RIGHT_X - 65, 440, RIGHT_X, getEarthY(RIGHT_X) - 5, rTemp, rightParticles); // Loop animationFrameId = requestAnimationFrame(render); } // 5. Start Animation & Cleanup Handler render(); invalidation.then(() => cancelAnimationFrame(animationFrameId)); // 6. Yield Canvas to Document return canvas;}```## Land & Sea Breeze Simulator```{=html}<div id="lsb-sim" style="font-family:'Inter',system-ui,sans-serif; max-width:900px; margin:24px auto; border-radius:16px; overflow:hidden; box-shadow:0 8px 40px rgba(0,0,0,0.18); background:#fff;"> <!-- Header --> <div style="background:#0f172a; color:white; padding:18px 24px; display:flex; justify-content:space-between; align-items:center; flex-wrap:wrap; gap:12px;"> <div> <div style="font-size:1.15em; font-weight:700; display:flex; align-items:center; gap:8px;">💨 Land & Sea Breeze Simulator</div> <div style="color:#94a3b8; font-size:0.83em; margin-top:4px;">Observe how temperature differences between the land and ocean drive coastal winds.</div> </div> <div style="display:flex; align-items:center; gap:12px; background:#1e293b; padding:8px 18px; border-radius:999px; border:1px solid #334155;"> <div id="lsb-time-display" style="font-size:1.35em; font-family:monospace; font-weight:700; color:#fbbf24; min-width:90px; text-align:center;">2:00 PM</div> <div style="width:1px; height:24px; background:#475569;"></div> <button id="lsb-play-btn" onclick="window.lsbTogglePlay()" title="Play/Pause" style="background:none; border:none; color:white; cursor:pointer; font-size:1.2em; padding:4px 8px; border-radius:50%;">▶</button> <button onclick="window.lsbReset()" title="Reset to 2 PM" style="background:none; border:none; color:#94a3b8; cursor:pointer; font-size:1.1em; padding:4px 8px; border-radius:50%;">↺</button> </div> </div> <!-- Slider --> <div style="padding:12px 24px 8px; background:#f1f5f9; border-bottom:1px solid #e2e8f0;"> <div style="display:flex; align-items:center; gap:10px;"> <span style="font-size:1.1em;">🌙</span> <input id="lsb-slider" type="range" min="0" max="24" step="0.1" value="14" oninput="window.lsbSlide(this.value)" style="flex:1; height:6px; accent-color:#4338ca; cursor:pointer;"> <span style="font-size:1.1em;">☀️</span> </div> <div style="display:flex; justify-content:space-between; font-size:0.75em; color:#64748b; font-weight:600; margin-top:4px; padding:0 2px;"> <span>Midnight</span><span>6 AM</span><span>Noon</span><span>6 PM</span><span>Midnight</span> </div> </div> <!-- SVG Viewport --> <div style="width:100%; overflow:hidden;"> <svg id="lsb-svg" viewBox="0 0 1000 500" style="display:block; width:100%; height:auto;" preserveAspectRatio="xMidYMid meet"> <defs> <linearGradient id="lsb-sky-grad" x1="0" y1="0" x2="0" y2="1" gradientUnits="objectBoundingBox"> <stop id="lsb-sky-stop1" offset="0%" stop-color="#60a5fa"/> <stop id="lsb-sky-stop2" offset="100%" stop-color="#a5f3fc"/> </linearGradient> <radialGradient id="lsb-sun-glow" cx="50%" cy="50%" r="50%"> <stop offset="0%" stop-color="#fef08a" stop-opacity="1"/> <stop offset="60%" stop-color="#fbbf24" stop-opacity="0.7"/> <stop offset="100%" stop-color="#fbbf24" stop-opacity="0"/> </radialGradient> </defs> <!-- Sky --> <rect width="1000" height="500" fill="url(#lsb-sky-grad)"/> <!-- Sun --> <g id="lsb-sun-g" transform="translate(-100,500)"> <circle r="40" fill="url(#lsb-sun-glow)" opacity="0.7"/> <circle r="20" fill="#fef08a"/> <circle r="14" fill="#fffde7"/> </g> <!-- Moon --> <g id="lsb-moon-g" transform="translate(-100,500)"> <circle r="18" fill="#e2e8f0"/> <circle id="lsb-moon-shadow" cx="6" cy="-4" r="14" fill="#1e1b4b" opacity="0.6"/> </g> <!-- Wind: Sea Breeze --> <g id="lsb-wind-sea" opacity="0"> <line x1="800" y1="420" x2="200" y2="420" stroke="white" stroke-width="4" stroke-dasharray="15 15" stroke-linecap="round" opacity="0.75"> <animate attributeName="stroke-dashoffset" values="30;0" dur="2s" repeatCount="indefinite"/> </line> <polygon points="190,420 212,410 212,430" fill="white" opacity="0.85"/> <line x1="150" y1="400" x2="150" y2="110" stroke="#fca5a5" stroke-width="4" stroke-dasharray="15 15" stroke-linecap="round" opacity="0.75"> <animate attributeName="stroke-dashoffset" values="30;0" dur="2s" repeatCount="indefinite"/> </line> <polygon points="150,98 140,120 160,120" fill="#fca5a5" opacity="0.85"/> <line x1="200" y1="80" x2="800" y2="80" stroke="white" stroke-width="3" stroke-dasharray="15 15" stroke-linecap="round" opacity="0.45"> <animate attributeName="stroke-dashoffset" values="30;0" dur="2s" repeatCount="indefinite"/> </line> <polygon points="812,80 790,70 790,90" fill="white" opacity="0.6"/> <line x1="850" y1="110" x2="850" y2="400" stroke="#93c5fd" stroke-width="4" stroke-dasharray="15 15" stroke-linecap="round" opacity="0.75"> <animate attributeName="stroke-dashoffset" values="30;0" dur="2s" repeatCount="indefinite"/> </line> <polygon points="850,412 840,390 860,390" fill="#93c5fd" opacity="0.85"/> </g> <!-- Wind: Land Breeze --> <g id="lsb-wind-land" opacity="0"> <line x1="200" y1="420" x2="800" y2="420" stroke="white" stroke-width="4" stroke-dasharray="15 15" stroke-linecap="round" opacity="0.75"> <animate attributeName="stroke-dashoffset" values="30;0" dur="2s" repeatCount="indefinite"/> </line> <polygon points="812,420 790,410 790,430" fill="white" opacity="0.85"/> <line x1="850" y1="400" x2="850" y2="110" stroke="#fca5a5" stroke-width="4" stroke-dasharray="15 15" stroke-linecap="round" opacity="0.75"> <animate attributeName="stroke-dashoffset" values="30;0" dur="2s" repeatCount="indefinite"/> </line> <polygon points="850,98 840,120 860,120" fill="#fca5a5" opacity="0.85"/> <line x1="800" y1="80" x2="200" y2="80" stroke="white" stroke-width="3" stroke-dasharray="15 15" stroke-linecap="round" opacity="0.45"> <animate attributeName="stroke-dashoffset" values="30;0" dur="2s" repeatCount="indefinite"/> </line> <polygon points="188,80 210,70 210,90" fill="white" opacity="0.6"/> <line x1="150" y1="110" x2="150" y2="400" stroke="#93c5fd" stroke-width="4" stroke-dasharray="15 15" stroke-linecap="round" opacity="0.75"> <animate attributeName="stroke-dashoffset" values="30;0" dur="2s" repeatCount="indefinite"/> </line> <polygon points="150,412 140,390 160,390" fill="#93c5fd" opacity="0.85"/> </g> <!-- Land (left) --> <rect id="lsb-land-rect" x="0" y="378" width="500" height="122" fill="#4ade80"/> <rect x="0" y="374" width="500" height="8" rx="3" fill="#166534" opacity="0.6"/> <!-- Trees --> <polygon points="80,370 65,390 95,390" fill="#15803d" opacity="0.9"/> <polygon points="80,356 60,376 100,376" fill="#166534" opacity="0.85"/> <polygon points="150,372 138,388 162,388" fill="#15803d" opacity="0.8"/> <polygon points="320,370 308,388 332,388" fill="#166534" opacity="0.8"/> <polygon points="320,356 304,374 336,374" fill="#15803d" opacity="0.85"/> <!-- House --> <rect x="390" y="366" width="34" height="22" fill="#cbd5e1" stroke="#94a3b8" stroke-width="1.5"/> <polygon points="388,366 407,351 426,366" fill="#b91c1c" opacity="0.9"/> <rect x="399" y="372" width="10" height="16" fill="#fef9c3" opacity="0.9"/> <!-- Ocean (right) --> <rect id="lsb-ocean-rect" x="500" y="378" width="500" height="122" fill="#2563eb" opacity="0.85"/> <rect x="500" y="374" width="500" height="8" rx="0" fill="#1d4ed8" opacity="0.6"/> <!-- Waves --> <path d="M 510 394 Q 530 389 550 394 Q 570 399 590 394 Q 610 389 630 394" stroke="white" stroke-width="2" fill="none" opacity="0.3"/> <path d="M 640 404 Q 660 399 680 404 Q 700 409 720 404 Q 740 399 760 404" stroke="white" stroke-width="2" fill="none" opacity="0.25"/> <path d="M 760 394 Q 780 389 800 394 Q 820 399 840 394 Q 860 389 880 394" stroke="white" stroke-width="2" fill="none" opacity="0.3"/> <!-- Temp labels --> <text id="lsb-land-temp-txt" x="250" y="430" text-anchor="middle" fill="white" font-size="14" font-weight="bold" font-family="sans-serif">20.0°C</text> <text x="250" y="448" text-anchor="middle" fill="rgba(255,255,255,0.75)" font-size="11" font-family="sans-serif">Land Temp</text> <text id="lsb-sea-temp-txt" x="750" y="430" text-anchor="middle" fill="white" font-size="14" font-weight="bold" font-family="sans-serif">20.0°C</text> <text x="750" y="448" text-anchor="middle" fill="rgba(255,255,255,0.75)" font-size="11" font-family="sans-serif">Ocean Temp</text> <!-- Pressure labels --> <text id="lsb-land-pressure" x="250" y="348" text-anchor="middle" fill="white" font-size="14" font-weight="bold" font-family="sans-serif" opacity="0">LOW ↑</text> <text id="lsb-sea-pressure" x="750" y="348" text-anchor="middle" fill="white" font-size="14" font-weight="bold" font-family="sans-serif" opacity="0">HIGH ↓</text> <!-- Status pill --> <rect x="330" y="16" width="340" height="40" rx="20" fill="white" fill-opacity="0.93" stroke="#e2e8f0" stroke-width="1.5"/> <text id="lsb-status-txt" x="500" y="41" text-anchor="middle" font-size="14" font-weight="700" fill="#334155" font-family="sans-serif">Calm / Transitioning</text> <!-- Surface wind arrow --> <g id="lsb-surface-arrow" opacity="0"> <rect x="420" y="283" width="160" height="54" rx="27" fill="rgba(0,0,0,0.25)"/> <text x="500" y="306" text-anchor="middle" fill="white" font-size="11" font-weight="bold" font-family="sans-serif" letter-spacing="1">SURFACE WIND</text> <text id="lsb-arrow-symbol" x="500" y="330" text-anchor="middle" fill="white" font-size="24" font-family="sans-serif">←</text> </g> </svg> </div> <!-- Info Panel --> <div style="padding:20px 28px; display:flex; gap:16px; align-items:flex-start; border-top:1px solid #e2e8f0; background:#fff;"> <div id="lsb-info-icon" style="width:48px; height:48px; border-radius:50%; display:flex; align-items:center; justify-content:center; flex-shrink:0; font-size:1.4em; background:#dbeafe; color:#1d4ed8;">ℹ️</div> <div style="flex:1;"> <div style="font-weight:700; font-size:1.05em; margin-bottom:8px;" id="lsb-info-title">What's happening?</div> <div id="lsb-info-body" style="color:#475569; font-size:0.92em; line-height:1.65;"></div> </div> </div></div><script>(function () { let lsbTime = 14; let lsbPlaying = false; let lsbInterval = null; const formatTime = (t) => { const h = Math.floor(t) % 24; const m = Math.floor((t - Math.floor(t)) * 60); const ampm = h >= 12 ? 'PM' : 'AM'; const dh = h % 12 === 0 ? 12 : h % 12; return `${dh}:${m.toString().padStart(2, '0')} ${ampm}`; }; const getSkyColors = (t) => { if (t < 4) return ['#0f0e35', '#141624']; if (t < 6) return ['#1e1b4b', '#4c1d95']; if (t < 8) return ['#fb923c', '#fcd34d']; if (t < 16) return ['#60a5fa', '#a5f3fc']; if (t < 18) return ['#f97316', '#fda4af']; if (t < 20) return ['#1e1b4b', '#581c87']; return ['#0f0e35', '#141624']; }; function update() { const t = lsbTime; const landTemp = 20 + 12 * Math.sin((t - 8) * (Math.PI / 12)); const seaTemp = 20 + 3 * Math.sin((t - 10) * (Math.PI / 12)); const diff = landTemp - seaTemp; const breezeType = diff > 1 ? 'sea' : diff < -1 ? 'land' : 'calm'; const strength = Math.min(Math.abs(diff) / 10, 1); const animDur = `${Math.max(0.5, 3 - strength * 2).toFixed(1)}s`; document.getElementById('lsb-time-display').textContent = formatTime(t); document.getElementById('lsb-slider').value = t; // Sky gradient const [c1, c2] = getSkyColors(t); document.getElementById('lsb-sky-stop1').setAttribute('stop-color', c1); document.getElementById('lsb-sky-stop2').setAttribute('stop-color', c2); // Sun const sunG = document.getElementById('lsb-sun-g'); if (t >= 5 && t <= 19) { const p = (t - 5) / 14; sunG.setAttribute('transform', `translate(${p * 1000},${450 - Math.sin(p * Math.PI) * 350})`); } else { sunG.setAttribute('transform', 'translate(-100,500)'); } // Moon const moonG = document.getElementById('lsb-moon-g'); if (t >= 17 || t <= 7) { const p = t >= 17 ? (t - 17) / 14 : (t + 7) / 14; moonG.setAttribute('transform', `translate(${p * 1000},${450 - Math.sin(p * Math.PI) * 350})`); document.getElementById('lsb-moon-shadow').setAttribute('fill', (c1.startsWith('#0') || c1.startsWith('#1')) ? '#0a0922' : 'transparent'); } else { moonG.setAttribute('transform', 'translate(-100,500)'); } // Land/Ocean colors const lBright = 30 + Math.max(0, Math.sin((t - 6) * (Math.PI / 12))) * 20; const lHue = 100 - (landTemp - 10) * 1.5; document.getElementById('lsb-land-rect').setAttribute('fill', `hsl(${lHue},50%,${lBright}%)`); const sBright = 38 + Math.max(0, Math.sin((t - 6) * (Math.PI / 12))) * 14; document.getElementById('lsb-ocean-rect').setAttribute('fill', `hsl(210,80%,${sBright}%)`); // Temp labels document.getElementById('lsb-land-temp-txt').textContent = `${landTemp.toFixed(1)}°C`; document.getElementById('lsb-sea-temp-txt').textContent = `${seaTemp.toFixed(1)}°C`; // Wind groups const seaG = document.getElementById('lsb-wind-sea'); const landG = document.getElementById('lsb-wind-land'); seaG.setAttribute('opacity', breezeType === 'sea' ? strength.toFixed(2) : '0'); landG.setAttribute('opacity', breezeType === 'land' ? strength.toFixed(2) : '0'); if (breezeType !== 'calm') { (breezeType === 'sea' ? seaG : landG).querySelectorAll('animate').forEach(a => a.setAttribute('dur', animDur)); } // Pressure labels const lp = document.getElementById('lsb-land-pressure'); const sp = document.getElementById('lsb-sea-pressure'); if (breezeType !== 'calm') { lp.setAttribute('opacity', '0.9'); sp.setAttribute('opacity', '0.9'); if (breezeType === 'sea') { lp.textContent = 'LOW ↑'; lp.setAttribute('fill', '#fca5a5'); sp.textContent = 'HIGH ↓'; sp.setAttribute('fill', '#93c5fd'); } else { lp.textContent = 'HIGH ↓'; lp.setAttribute('fill', '#93c5fd'); sp.textContent = 'LOW ↑'; sp.setAttribute('fill', '#fca5a5'); } } else { lp.setAttribute('opacity', '0'); sp.setAttribute('opacity', '0'); } // Status pill const statusText = { sea: '🌊 Sea Breeze Active', land: '🏔️ Land Breeze Active', calm: '⚖️ Calm / Transitioning' }; const statusColor = { sea: '#2563eb', land: '#16a34a', calm: '#64748b' }; const txt = document.getElementById('lsb-status-txt'); txt.textContent = statusText[breezeType]; txt.setAttribute('fill', statusColor[breezeType]); // Surface arrow const arrowG = document.getElementById('lsb-surface-arrow'); arrowG.setAttribute('opacity', breezeType === 'calm' ? '0' : '0.9'); document.getElementById('lsb-arrow-symbol').textContent = breezeType === 'sea' ? '←' : '→'; // Info panel const iconBg = { sea: '#dbeafe', land: '#dcfce7', calm: '#f1f5f9' }; const iconColor = { sea: '#1d4ed8', land: '#166534', calm: '#64748b' }; const iconEmoji = { sea: '🌊', land: '🏔️', calm: '⚖️' }; const el = document.getElementById('lsb-info-icon'); el.textContent = iconEmoji[breezeType]; el.style.background = iconBg[breezeType]; el.style.color = iconColor[breezeType]; document.getElementById('lsb-info-title').textContent = `What's happening at ${formatTime(t)}?`; const bodyEl = document.getElementById('lsb-info-body'); if (breezeType === 'sea') { bodyEl.innerHTML = '<p style="margin:0 0 8px"><strong>The mechanism:</strong> During the day, the sun heats the land surface much faster than the ocean (water has a higher specific heat capacity).</p>' + '<p style="margin:0 0 8px">Because the land is hot, the air above it warms up, expands, and <strong>rises</strong>, creating an area of <strong>low pressure</strong> over the land.</p>' + '<p style="margin:0">The cooler, denser air over the ocean (relatively <strong>high pressure</strong>) rushes inland to fill the void. This refreshing daytime wind blowing from the sea to the land is called a <strong>Sea Breeze</strong>.</p>'; } else if (breezeType === 'land') { bodyEl.innerHTML = '<p style="margin:0 0 8px"><strong>The mechanism:</strong> At night, the land loses its heat to the atmosphere very rapidly, while the ocean holds onto its heat much longer.</p>' + '<p style="margin:0 0 8px">Now, the ocean is relatively warmer than the land. The air above the water warms and <strong>rises</strong>, creating a <strong>low pressure</strong> zone over the sea.</p>' + '<p style="margin:0">The cooler, denser air from over the land (now <strong>high pressure</strong>) flows outward over the ocean. This nighttime wind blowing from land to sea is called a <strong>Land Breeze</strong>.</p>'; } else { bodyEl.innerHTML = '<p style="margin:0 0 8px"><strong>The mechanism:</strong> The temperatures of the land and the ocean are currently very close to equal.</p>' + '<p style="margin:0">Because there is no significant temperature difference, there is no major pressure difference either. This results in relatively <strong>calm</strong> conditions. This usually happens briefly during the morning and evening transitions.</p>'; } } window.lsbTogglePlay = function () { lsbPlaying = !lsbPlaying; document.getElementById('lsb-play-btn').textContent = lsbPlaying ? '⏸' : '▶'; if (lsbPlaying) { lsbInterval = setInterval(() => { lsbTime = (lsbTime + 0.1) % 24; update(); }, 50); } else { clearInterval(lsbInterval); } }; window.lsbReset = function () { lsbTime = 14; lsbPlaying = false; clearInterval(lsbInterval); document.getElementById('lsb-play-btn').textContent = '▶'; update(); }; window.lsbSlide = function (val) { lsbTime = parseFloat(val); lsbPlaying = false; clearInterval(lsbInterval); document.getElementById('lsb-play-btn').textContent = '▶'; update(); }; update();})();</script>``````{ojs}//| echo: falsebuildQuiz("land-sea-breeze", [ { q: "In the Land & Sea Breeze Simulator at 2 PM, why does surface wind blow FROM sea TO land?", options: [ "The sun heats the ocean faster, causing cold ocean air to push toward land", "Hot land air rises (low pressure), and cooler ocean air (high pressure) flows inland to replace it", "Sea breezes are a night-time phenomenon — during the day wind blows from land to sea", "Ocean water evaporates and pulls surface air outward from the coast" ], correct: 1, explanation: "Sea breeze: land heats faster than water → air above land warms, expands, and RISES → surface LOW pressure over land. Cooler, denser ocean air (HIGH pressure) flows landward to fill the void. This is the identical pressure-gradient mechanism as all surface wind — uneven heating creates pressure differences, air flows HIGH → LOW." }, { q: "At midnight, the land temperature drops below the ocean temperature. How does the wind direction change and why?", options: [ "Wind continues blowing from sea to land — thermal inertia keeps the pattern going", "Wind reverses to blow from land to sea — now the ocean is warmer, ocean air rises (low pressure), and cooler land air flows outward", "Wind stops completely at night when solar heating ends", "The Coriolis Effect reverses the wind direction after sunset" ], correct: 1, explanation: "Land radiates heat rapidly at night; the ocean retains heat far longer (high specific heat capacity). Now ocean > land temperature → ocean air rises (LOW pressure over water) → cooler land air flows seaward. This LAND BREEZE is driven by the same HIGH-to-LOW pressure rule — just with the temperature differential flipped from daytime." }])```::: {.key-idea}### 💡 Key Concept: What Drives Wind**Uneven heating → Temperature differences → Pressure differences → WIND**1. When the Sun heats Earth's surface unevenly, some areas become **warmer** than others.2. Warm air is **less dense** and rises, creating an area of **low pressure** at the surface.3. Cool air is **more dense** and sinks, creating an area of **high pressure** at the surface.4. Air flows from **high pressure → low pressure**. This is wind!5. The greater the pressure difference (the **pressure gradient**), the faster the wind.:::```{ojs}//| echo: falsebuildQuiz("wind-pressure", [ { q: "You set the left surface to 98°F (hot desert) and the right to 8°F (frozen tundra). Which direction does the surface wind blow?", options: [ "From the hot (left/low-pressure) side toward the cold (right/high-pressure) side", "From the cold (right/high-pressure) side toward the hot (left/low-pressure) side", "Wind does not form — the temperatures are too extreme", "Wind direction is determined solely by Earth's rotation" ], correct: 1, explanation: "Hot air rises → creates LOW pressure at the surface. Cold air sinks → creates HIGH pressure. Surface wind always flows FROM high pressure TOWARD low pressure, so from cold → toward hot. The bigger the temperature (pressure) difference, the faster the wind!" }, { q: "What is the pressure gradient force?", options: [ "The force of gravity pulling air downward", "The force pushing air from areas of HIGH pressure toward areas of LOW pressure", "The Coriolis deflection caused by Earth's rotation", "The force that lifts warm air at frontal boundaries" ], correct: 1, explanation: "The pressure gradient force is the FUNDAMENTAL driver of all wind. It pushes air 'downhill' from high to low pressure. The steeper the gradient (larger pressure difference over a shorter distance), the stronger the wind — just like steeper hills create faster-moving rivers." }, { q: "A blizzard requires sustained 35+ mph winds. What atmospheric feature generates these extreme winds?", options: [ "The weight of heavy snowflakes pushing down on the air column", "A steep pressure gradient around a deep low-pressure cyclone with tightly packed isobars", "Cold temperatures causing air molecules to vibrate and move faster", "Friction between falling snow crystals and the atmosphere" ], correct: 1, explanation: "Blizzards form within powerful mid-latitude cyclones — very deep low-pressure systems. The enormous pressure difference between the low center and surrounding high pressure creates a steep gradient. This drives extreme winds toward the low center, spinning counter-clockwise due to the Coriolis Effect." }])```# Explore: Fronts — When Air Masses Collide::: {.explore-box}## 🌪️ What Happens When Air Masses Meet?When two air masses with different properties meet, they don't mix easily. The boundary between them is called a **front**. Fronts are where weather happens!:::## Types of Fronts```{=html}<iframe src="weatherfronts.html" width="100%" height="900px" style="border:none;"></iframe>```# Explain: How Do Blizzards Form?::: {.explain-box}## 🧊 Building a Blizzard ModelNow that you understand wind (from pressure differences) and precipitation (from air mass collisions at fronts), let's put it all together to explain how blizzards form.:::## The Mid-Latitude CycloneBlizzards are produced by powerful **mid-latitude cyclones** — large low-pressure systems that form at the boundary between polar and tropical air masses, typically between 30°N and 60°N latitude.```{ojs}//| echo: false{ // ── helpers ─────────────────────────────────────────────────────────── const _lerp = (a,b,t) => a+(b-a)*t; const _bPt = (t,p0,p1,p2) => ({ x:(1-t)**2*p0.x+2*(1-t)*t*p1.x+t*t*p2.x, y:(1-t)**2*p0.y+2*(1-t)*t*p1.y+t*t*p2.y }); const _bTan = (t,p0,p1,p2) => { const dx=2*(1-t)*(p1.x-p0.x)+2*t*(p2.x-p1.x); const dy=2*(1-t)*(p1.y-p0.y)+2*t*(p2.y-p1.y); const n=Math.sqrt(dx*dx+dy*dy)||1; return {x:dx/n,y:dy/n}; }; const _KF = [ {p:0, L:{x:.5,y:.5}, T:{x:.5,y:.5}, coldEnd:{x:0,y:.5}, coldCtrl:{x:.25,y:.5}, warmEnd:{x:1,y:.5}, warmCtrl:{x:.75,y:.5}, occCtrl:{x:.5,y:.5}, pressure:1010, desc:"Stationary Front Stage: Cold air to the north and warm air to the south flow parallel to the boundary in opposite directions. There is no active weather development yet."}, {p:20, L:{x:.5,y:.45}, T:{x:.5,y:.45}, coldEnd:{x:0,y:.6}, coldCtrl:{x:.3,y:.6}, warmEnd:{x:1,y:.4}, warmCtrl:{x:.7,y:.35}, occCtrl:{x:.5,y:.45}, pressure:1004, desc:"Incipient Wave Stage: A perturbation (kink) forms on the front. A localized low-pressure center develops. Cold air starts pushing south (cold front), and warm air pushes north (warm front)."}, {p:50, L:{x:.55,y:.35}, T:{x:.55,y:.35}, coldEnd:{x:.1,y:.9}, coldCtrl:{x:.35,y:.7}, warmEnd:{x:.9,y:.25}, warmCtrl:{x:.8,y:.45}, occCtrl:{x:.55,y:.35}, pressure:992, desc:"Mature Stage (Open Wave): The cyclone is fully developed with well-defined cold and warm fronts. A clear 'warm sector' exists between them. Central pressure drops rapidly, creating strong winds."}, {p:80, L:{x:.6,y:.25}, T:{x:.68,y:.45}, coldEnd:{x:.25,y:1}, coldCtrl:{x:.45,y:.8}, warmEnd:{x:1,y:.3}, warmCtrl:{x:.9,y:.5}, occCtrl:{x:.62,y:.35}, pressure:988, desc:"Occluded Stage: The faster-moving cold front catches up to the warm front, lifting the warm air completely off the ground near the center. This forms an occluded front. The storm reaches peak intensity."}, {p:100,L:{x:.65,y:.2}, T:{x:.85,y:.6}, coldEnd:{x:.4,y:1}, coldCtrl:{x:.65,y:.9}, warmEnd:{x:1,y:.45}, warmCtrl:{x:.95,y:.55}, occCtrl:{x:.7,y:.3}, pressure:998, desc:"Dissipation Stage: The low-pressure center is entirely surrounded by cold air. Cut off from its warm air energy source (the temperature gradient), the cyclone slowly weakens and spins down."} ]; const _getState = (p) => { let kf1=_KF[0], kf2=_KF[_KF.length-1]; for(let i=0;i<_KF.length-1;i++) if(p>=_KF[i].p&&p<=_KF[i+1].p){kf1=_KF[i];kf2=_KF[i+1];break;} const t=(p-kf1.p)/((kf2.p-kf1.p)||1); const s={desc:p<90?kf1.desc:_KF[4].desc, pressure:_lerp(kf1.pressure,kf2.pressure,t)}; for(const k of['L','T','coldEnd','coldCtrl','warmEnd','warmCtrl','occCtrl']) s[k]={x:_lerp(kf1[k].x,kf2[k].x,t),y:_lerp(kf1[k].y,kf2[k].y,t)}; return s; }; // ── state ───────────────────────────────────────────────────────────── let prog=0, playing=false, raf=null, lastT=null; const lay={airMasses:true,precipitation:true,fronts:true,winds:true,isobars:true}; // ── DOM ─────────────────────────────────────────────────────────────── const root = document.createElement('div'); root.style.cssText='width:100%;font-family:system-ui,sans-serif;border:1px solid #e2e8f0;border-radius:12px;overflow:hidden;background:#f8fafc;'; // Header const hdr = document.createElement('div'); hdr.style.cssText='background:#1e293b;color:white;padding:13px 18px;display:flex;align-items:center;justify-content:space-between;'; const hdrLeft = document.createElement('div'); hdrLeft.innerHTML='<div style="font-size:15px;font-weight:700;">🌪️ Mid-Latitude Cyclone</div><div style="font-size:11px;color:#94a3b8;margin-top:2px;">Interactive Cyclogenesis Model</div>'; const hdrBtns = document.createElement('div'); hdrBtns.style.cssText='display:flex;align-items:center;gap:8px;'; const resetBtn = document.createElement('button'); resetBtn.title='Reset'; resetBtn.textContent='↺'; resetBtn.style.cssText='background:none;border:none;color:#94a3b8;cursor:pointer;font-size:20px;padding:4px 7px;line-height:1;'; const playBtn = document.createElement('button'); playBtn.textContent='▶'; playBtn.style.cssText='background:#3b82f6;border:none;color:white;border-radius:50%;width:38px;height:38px;cursor:pointer;font-size:14px;display:flex;align-items:center;justify-content:center;flex-shrink:0;'; const endBtn = document.createElement('button'); endBtn.title='Skip to End'; endBtn.textContent='⏭'; endBtn.style.cssText='background:none;border:none;color:#94a3b8;cursor:pointer;font-size:18px;padding:4px 7px;line-height:1;'; hdrBtns.append(resetBtn, playBtn, endBtn); hdr.append(hdrLeft, hdrBtns); root.appendChild(hdr); // Slider row const slRow = document.createElement('div'); slRow.style.cssText='background:white;padding:10px 18px;border-bottom:1px solid #e2e8f0;display:flex;align-items:center;gap:10px;'; const slLbl1 = document.createElement('span'); slLbl1.textContent='Stationary'; slLbl1.style.cssText='font-size:11px;color:#64748b;white-space:nowrap;'; const slider = document.createElement('input'); slider.type='range'; slider.min=0; slider.max=100; slider.step=0.5; slider.value=0; slider.style.cssText='flex:1;accent-color:#3b82f6;height:6px;cursor:pointer;'; const slLbl2 = document.createElement('span'); slLbl2.textContent='Dissipating'; slLbl2.style.cssText='font-size:11px;color:#64748b;white-space:nowrap;'; slRow.append(slLbl1, slider, slLbl2); root.appendChild(slRow); // Lower: sidebar + canvas const lower = document.createElement('div'); lower.style.cssText='display:flex;width:100%;'; // Sidebar const sidebar = document.createElement('div'); sidebar.style.cssText='width:190px;flex-shrink:0;background:white;border-right:1px solid #e2e8f0;padding:12px;display:flex;flex-direction:column;gap:8px;'; const layTitle = document.createElement('div'); layTitle.textContent='Display Layers'; layTitle.style.cssText='font-size:10px;font-weight:700;color:#64748b;text-transform:uppercase;letter-spacing:.05em;'; sidebar.appendChild(layTitle); const layDefs=[ {key:'airMasses',label:'Air Masses',color:'#f97316'}, {key:'precipitation',label:'Precipitation',color:'#22c55e'}, {key:'fronts',label:'Fronts',color:'#3b82f6'}, {key:'winds',label:'Wind Vectors',color:'#64748b'}, {key:'isobars',label:'Isobars',color:'#94a3b8'}, ]; const layBtns={}; function styleLB(btn,active){ btn.style.background=active?'#eff6ff':'white'; btn.style.borderColor=active?'#bfdbfe':'#e2e8f0'; btn.style.fontWeight=active?'600':'400'; } for(const ld of layDefs){ const btn=document.createElement('button'); btn.style.cssText=`width:100%;text-align:left;padding:6px 9px;border-radius:7px;font-size:12px;cursor:pointer;display:flex;align-items:center;gap:7px;border:1.5px solid #e2e8f0;background:white;color:#334155;`; btn.innerHTML=`<span style="width:9px;height:9px;border-radius:2px;background:${ld.color};flex-shrink:0;"></span>${ld.label}`; layBtns[ld.key]=btn; styleLB(btn,lay[ld.key]); btn.addEventListener('click',()=>{lay[ld.key]=!lay[ld.key];styleLB(btn,lay[ld.key]);}); sidebar.appendChild(btn); } const infoBox = document.createElement('div'); infoBox.style.cssText='background:#eff6ff;border:1px solid #dbeafe;border-radius:8px;padding:10px;font-size:11px;color:#334155;line-height:1.55;margin-top:4px;'; sidebar.appendChild(infoBox); lower.appendChild(sidebar); // Canvas const canvas = document.createElement('canvas'); canvas.style.cssText='flex:1;display:block;background:#e0f2fe;min-height:460px;'; lower.appendChild(canvas); root.appendChild(lower); // ── draw ────────────────────────────────────────────────────────────── function draw(){ const cw=canvas.clientWidth, ch=canvas.clientHeight; if(!cw||!ch) return; if(canvas.width!==cw||canvas.height!==ch){canvas.width=cw;canvas.height=ch;} const ctx=canvas.getContext('2d'); const s=_getState(prog); const sc=pt=>({x:pt.x*cw,y:pt.y*ch}); const Lp=sc(s.L),Tp=sc(s.T),ce=sc(s.coldEnd),cc=sc(s.coldCtrl),we=sc(s.warmEnd),wc=sc(s.warmCtrl),oc=sc(s.occCtrl); ctx.clearRect(0,0,cw,ch); // Air masses if(lay.airMasses){ ctx.fillStyle='#e0f2fe'; ctx.fillRect(0,0,cw,ch); ctx.beginPath(); ctx.moveTo(Tp.x,Tp.y); ctx.quadraticCurveTo(wc.x,wc.y,we.x,we.y); if(we.y<ch) ctx.lineTo(cw,ch); ctx.lineTo(ce.x,ch); ctx.lineTo(ce.x,ce.y); ctx.quadraticCurveTo(cc.x,cc.y,Tp.x,Tp.y); ctx.fillStyle='#ffedd5'; ctx.fill(); const lbl=(text,desc,x,y,col)=>{ ctx.fillStyle=col;ctx.font='bold 26px sans-serif';ctx.textAlign='center';ctx.textBaseline='middle'; ctx.fillText(text,x,y);ctx.font='11px sans-serif';ctx.fillText(desc,x,y+21); }; if(prog<5){ lbl('cP','Continental Polar',cw*.5,ch*.22,'#0284c7'); lbl('mT','Maritime Tropical',cw*.5,ch*.75,'#c2410c'); } else { lbl('cP','Continental Polar',Math.max(45,Math.min(ce.x-50,cw*.18)),Math.max(45,Math.min(Lp.y+85,ch*.5)),'#0284c7'); lbl('mT','Maritime Tropical',Math.min(cw-65,Tp.x+(we.x-Tp.x)*.4),Math.min(ch-40,Math.max(Tp.y+105,we.y+45)),'#c2410c'); lbl('mP','Maritime Polar',Math.min(cw-65,we.x-65),Math.max(40,Tp.y-85),'#0369a1'); } } // Precipitation if(lay.precipitation){ const band=(p0,p1,p2,wLim,side,fill)=>{ const steps=30,pts=[],offs=[]; for(let i=0;i<=steps;i++){ const t_=i/steps,pt=_bPt(t_,p0,p1,p2),tn=_bTan(t_,p0,p1,p2); pts.push(pt); const tap=Math.sin(t_*Math.PI),w=wLim*(.3+.7*tap); offs.push({x:pt.x+(-tn.y*side)*w,y:pt.y+(tn.x*side)*w}); } ctx.beginPath(); ctx.moveTo(pts[0].x,pts[0].y); for(let i=1;i<=steps;i++) ctx.lineTo(pts[i].x,pts[i].y); for(let i=steps;i>=0;i--) ctx.lineTo(offs[i].x,offs[i].y); ctx.closePath(); ctx.fillStyle=fill; ctx.fill(); }; if(prog<5){ band(ce,Lp,we,40,-1,'rgba(74,222,128,.4)'); } else { band(Tp,wc,we,90,-1,'rgba(74,222,128,.5)'); band(Tp,cc,ce,30,1,'rgba(22,163,74,.7)'); const inten=Math.sin(prog/100*Math.PI); if(prog>10){ctx.beginPath();ctx.ellipse(Lp.x-10,Lp.y-10,60*inten+20,45*inten+20,Math.PI/4,0,Math.PI*2);ctx.fillStyle='rgba(74,222,128,.6)';ctx.fill();} if(prog>55){band(Lp,oc,Tp,60,1,'rgba(74,222,128,.6)');band(Lp,oc,Tp,60,-1,'rgba(74,222,128,.6)');} } } // Isobars if(lay.isobars){ ctx.strokeStyle='rgba(0,0,0,.15)'; ctx.lineWidth=1; const inten=Math.sin(prog/100*Math.PI), n=3+Math.floor(inten*5); for(let i=1;i<=n;i++){ctx.beginPath();ctx.ellipse(Lp.x,Lp.y,i*40*(1-inten*.2),i*35*(1-inten*.3),Math.PI/8,0,Math.PI*2);ctx.stroke();} } // Fronts if(lay.fronts){ const sym=(x,y,ang,type)=>{ ctx.save(); ctx.translate(x,y); ctx.rotate(ang); ctx.beginPath(); if(type==='cold'){ctx.moveTo(-8,0);ctx.lineTo(8,0);ctx.lineTo(0,14);ctx.fillStyle='#2563eb';} else if(type==='warm'){ctx.arc(0,0,8,Math.PI,0);ctx.fillStyle='#dc2626';} else if(type==='occ-cold'){ctx.moveTo(-8,0);ctx.lineTo(8,0);ctx.lineTo(0,14);ctx.fillStyle='#9333ea';} else if(type==='occ-warm'){ctx.arc(0,0,8,Math.PI,0);ctx.fillStyle='#9333ea';} ctx.fill(); ctx.restore(); }; const fp=(p0,p1,p2,col,sType,spacing,isOcc)=>{ ctx.strokeStyle=col; ctx.lineWidth=3; ctx.beginPath(); ctx.moveTo(p0.x,p0.y); ctx.quadraticCurveTo(p1.x,p1.y,p2.x,p2.y); ctx.stroke(); let d=0,tog=false; for(let i=1;i<30;i++){ const t_=i/30,pt=_bPt(t_,p0,p1,p2),pp=_bPt((i-1)/30,p0,p1,p2); d+=Math.sqrt((pt.x-pp.x)**2+(pt.y-pp.y)**2); if(d>spacing){ const tn=_bTan(t_,p0,p1,p2); let ang=Math.atan2(tn.y,tn.x); if(sType==='cold') ang+=Math.PI/2; else if(sType==='warm') ang-=Math.PI/2; else ang+=Math.PI/2; let cur=sType; if(isOcc){cur=tog?'occ-cold':'occ-warm';tog=!tog;} sym(pt.x,pt.y,ang,cur); d=0; } } }; if(prog<5){ ctx.strokeStyle='#6b7280'; ctx.lineWidth=3; ctx.beginPath(); ctx.moveTo(ce.x,ce.y); ctx.lineTo(we.x,we.y); ctx.stroke(); } else { fp(Tp,cc,ce,'#2563eb','cold',50,false); fp(Tp,wc,we,'#dc2626','warm',50,false); if(prog>55) fp(Lp,oc,Tp,'#9333ea','occ',35,true); } if(prog>5){ ctx.fillStyle='#ef4444'; ctx.font='bold 22px sans-serif'; ctx.textAlign='center'; ctx.textBaseline='middle'; ctx.strokeStyle='rgba(255,255,255,.8)'; ctx.lineWidth=4; ctx.strokeText('L',Lp.x,Lp.y-14); ctx.fillText('L',Lp.x,Lp.y-14); ctx.fillStyle='#1f2937'; ctx.font='11px sans-serif'; ctx.fillText(`${Math.round(s.pressure)} mb`,Lp.x,Lp.y+9); } } // Winds if(lay.winds){ ctx.strokeStyle='rgba(75,85,99,.4)'; ctx.fillStyle='rgba(75,85,99,.6)'; ctx.lineWidth=1.5; const grid=40; for(let x=grid/2;x<cw;x+=grid){ for(let y=grid/2;y<ch;y+=grid){ const dx=x-Lp.x,dy=y-Lp.y,dist=Math.sqrt(dx*dx+dy*dy); let wAng=0,wSpd=0; if(prog<10){wAng=y<Lp.y?Math.PI:0;wSpd=10;} else{ const inten=Math.sin(prog/100*Math.PI); wAng=Math.atan2(dy,dx)-Math.PI/2-.35; wSpd=dist<300?(dist/300)*20*inten+5:5; if(dist<40) wSpd=dist/8; } if(wSpd>2){ const al=Math.min(wSpd,25),ex=x+Math.cos(wAng)*al,ey=y+Math.sin(wAng)*al; ctx.beginPath(); ctx.moveTo(x,y); ctx.lineTo(ex,ey); ctx.stroke(); const ha=Math.PI/6,hl=4; ctx.beginPath(); ctx.moveTo(ex,ey); ctx.lineTo(ex-hl*Math.cos(wAng-ha),ey-hl*Math.sin(wAng-ha)); ctx.lineTo(ex-hl*Math.cos(wAng+ha),ey-hl*Math.sin(wAng+ha)); ctx.closePath(); ctx.fill(); } } } } infoBox.textContent = s.desc; } // ── animation loop ──────────────────────────────────────────────────── function loop(t){ if(playing){ if(lastT!=null){ prog=Math.min(100,prog+(t-lastT)*0.005); slider.value=prog; if(prog>=100){playing=false;playBtn.textContent='▶';} } lastT=t; } draw(); raf=requestAnimationFrame(loop); } raf=requestAnimationFrame(loop); invalidation.then(()=>cancelAnimationFrame(raf)); // ── events ──────────────────────────────────────────────────────────── slider.oninput=()=>{prog=+slider.value;playing=false;playBtn.textContent='▶';}; playBtn.onclick=()=>{playing=!playing;playBtn.textContent=playing?'⏸':'▶';if(playing)lastT=null;}; resetBtn.onclick=()=>{prog=0;slider.value=0;playing=false;playBtn.textContent='▶';}; endBtn.onclick=()=>{prog=100;slider.value=100;playing=false;playBtn.textContent='▶';}; return root;}```::: {.key-idea}### 💡 Key Concept: Mid-Latitude Cyclone → BlizzardA blizzard is a severe mid-latitude cyclone that produces:- **Heavy snow** (reduces visibility to less than ¼ mile)- **Strong winds** (sustained 35+ mph for 3+ hours)- **Cold temperatures** (usually below 20°F)These conditions occur on the **cold side** of the low-pressure center (north and west of the center in the Northern Hemisphere), where cold, dry polar air wraps around the system and collides with moist air being lifted along the fronts.:::```{ojs}//| echo: falsebuildQuiz("cyclone", [ { q: "Use the Cyclone Life Stage slider. At Stage 3 (Mature Cyclone), the BLIZZARD zone is shown north/northwest of the low center. Why that location specifically?", options: [ "East of the center, in the warm sector between the two fronts", "North/northwest of the center — where cold polar air wraps around the system, pressure gradient is steepest, and precipitation from lifted moist air is heaviest", "Directly at the low center, where pressure is lowest", "South of the warm front, far from any cold air" ], correct: 1, explanation: "The blizzard zone needs THREE things simultaneously: (1) temperatures below freezing — provided by cold polar air wrapping north/west of the low; (2) extreme winds — provided by the steep pressure gradient near the low center; (3) heavy precipitation — provided by moist air being lifted along the cold front. Only the north/west quadrant has all three!" }, { q: "Why do mid-latitude cyclones in the Northern Hemisphere spin COUNTER-CLOCKWISE?", options: [ "Warm air rises and cold air sinks simultaneously, creating opposite rotations", "The Coriolis Effect deflects inflowing air to the RIGHT, causing it to spiral counter-clockwise around the low center", "Ocean currents beneath force the overlying atmosphere to rotate counter-clockwise", "High-pressure systems push air in a counter-clockwise direction toward low-pressure centers" ], correct: 1, explanation: "As air converges toward a low-pressure center, Earth's rotation deflects it to the right (Coriolis Effect). This rightward deflection makes air spiral counter-clockwise around the Northern Hemisphere low. In the Southern Hemisphere, the deflection is to the LEFT, so cyclones spin clockwise. This is why all Northern Hemisphere low-pressure systems (including blizzards, hurricanes, and tornadoes) rotate counter-clockwise." }, { q: "What happens to a mid-latitude cyclone after the cold front overtakes the warm front (Stage 4 — Occlusion)?", options: [ "The storm intensifies dramatically as both fronts combine their energy", "The storm weakens — warm air is completely lifted off the surface, cutting off the temperature contrast that was driving the cyclone", "The storm stalls and becomes a long-lived stationary front", "Occlusion has no effect on storm intensity" ], correct: 1, explanation: "Once the fast-moving cold front catches the warm front, all the warm air is lifted off the surface — creating the occluded front. With no more warm vs. cold temperature contrast at the surface, the pressure gradient weakens, the system fills in, and the storm dissipates. Every mid-latitude cyclone (including every blizzard) eventually dies this way." }])```## Reading Weather MapsMeteorologists use **surface analysis maps** to track air masses, fronts, and pressure systems. Understanding these maps is key to predicting blizzards.::: {.weather-map-note}### 🗺️ Weather Map Symbols| Symbol | Meaning ||--------|---------|| **L** (red) | Low-pressure center — rising air, clouds, precipitation || **H** (blue) | High-pressure center — sinking air, clear skies || Blue line with triangles ▲ | Cold front (triangles point in direction of movement) || Red line with semicircles ⦿ | Warm front (semicircles point in direction of movement) || Alternating blue/red | Stationary front || Purple line with both | Occluded front || Concentric circles | Isobars — lines of equal pressure (closer = stronger wind) |:::# Explain: Precipitation — Where Does Snow Come From?::: {.explain-box}## 💧 From Water Vapor to SnowflakesUnderstanding precipitation requires connecting energy, water, and air movement. Let's trace the journey from evaporation to snowfall.:::## The Precipitation Process```{ojs}//| echo: false{ const width = 750; const height = 500; const svg = d3.create("svg").attr("width", width).attr("height", height).attr("viewBox", `0 0 ${width} ${height}`); svg.append("rect").attr("width", width).attr("height", height).attr("fill", "#e8f4fd").attr("rx", 10); svg.append("text").attr("x", 375).attr("y", 30).attr("text-anchor", "middle").attr("font-size", 20).attr("font-weight", "bold").text("How Precipitation Forms at a Front"); // Ground svg.append("rect").attr("x", 0).attr("y", 420).attr("width", 375).attr("height", 80).attr("fill", "#ff7675").attr("opacity", 0.3); svg.append("rect").attr("x", 375).attr("y", 420).attr("width", 375).attr("height", 80).attr("fill", "#74b9ff").attr("opacity", 0.3); svg.append("text").attr("x", 190).attr("y", 460).attr("text-anchor", "middle").attr("font-size", 14).attr("font-weight", "bold").attr("fill", "#d63031").text("Warm Surface (mT air)"); svg.append("text").attr("x", 560).attr("y", 460).attr("text-anchor", "middle").attr("font-size", 14).attr("font-weight", "bold").attr("fill", "#0984e3").text("Cold Surface (cP air)"); // Steps with arrows const steps = [ {x: 120, y: 390, num: "①", text: "Warm, moist air holds\nlots of water vapor", color: "#d63031"}, {x: 120, y: 300, num: "②", text: "Warm air is forced UP\nat the front boundary", color: "#e17055"}, {x: 280, y: 200, num: "③", text: "Rising air COOLS\n(adiabatic cooling)", color: "#6c5ce7"}, {x: 430, y: 120, num: "④", text: "Cool air can't hold as\nmuch moisture → CONDENSATION", color: "#0984e3"}, {x: 550, y: 60, num: "⑤", text: "Water droplets form clouds;\nif cold enough → ICE CRYSTALS", color: "#00b894"}, {x: 550, y: 300, num: "⑥", text: "Crystals grow heavy\nand FALL as snow ❄️", color: "#2d3436"} ]; steps.forEach(s => { svg.append("circle").attr("cx", s.x - 30).attr("cy", s.y).attr("r", 18).attr("fill", s.color).attr("opacity", 0.2); svg.append("text").attr("x", s.x - 30).attr("y", s.y + 6).attr("text-anchor", "middle").attr("font-size", 18).attr("font-weight", "bold").attr("fill", s.color).text(s.num); const lines = s.text.split("\n"); lines.forEach((line, i) => { svg.append("text").attr("x", s.x + 10).attr("y", s.y - 5 + i * 16).attr("font-size", 13).attr("fill", "#2d3436").text(line); }); }); // Rising air arrow svg.append("path").attr("d", "M 150,380 Q 200,280 350,180 Q 450,100 550,70") .attr("stroke", "#e17055").attr("stroke-width", 3).attr("fill", "none").attr("stroke-dasharray", "8,4"); // Falling snow for (let i = 0; i < 8; i++) { svg.append("text").attr("x", 520 + Math.random() * 100).attr("y", 200 + Math.random() * 180).attr("font-size", 16).text("❄️"); } // Front line svg.append("line").attr("x1", 375).attr("y1", 420).attr("x2", 375).attr("y2", 200).attr("stroke", "#6c5ce7").attr("stroke-width", 3).attr("stroke-dasharray", "5,3"); svg.append("text").attr("x", 380).attr("y", 195).attr("font-size", 12).attr("fill", "#6c5ce7").text("FRONT"); return svg.node();}```::: {.key-idea}### 💡 Key Concept: Why Fronts Produce Precipitation1. **Warm, moist air** (often from the Gulf of Mexico) contains lots of **water vapor**2. At a front, this warm air is **forced upward** over cold air3. As air rises, it **cools** (because atmospheric pressure decreases with altitude)4. Cool air holds **less water vapor** than warm air5. Excess moisture **condenses** into water droplets or **freezes** into ice crystals6. When crystals grow heavy enough, they fall as **snow** (if temperatures remain below freezing all the way to the ground):::::: {.mind-blown}❄️ A single mid-latitude cyclone can lift BILLIONS of tons of moist air, producing enough snow to bury an entire state! ❄️:::```{ojs}//| echo: falsebuildQuiz("precipitation", [ { q: "Looking at the precipitation diagram above: what triggers water vapor to condense into cloud droplets or ice crystals?", options: [ "Increased atmospheric pressure as air rises compresses the moisture", "Adiabatic cooling — rising air expands (pressure decreases with altitude) and cools, eventually reaching its dew point", "Mixing with cold polar air molecules at the frontal surface", "Solar radiation heating water droplets already in the cloud" ], correct: 1, explanation: "Adiabatic cooling is the key mechanism: as air rises, it moves into lower-pressure regions and EXPANDS. Expansion requires energy (work done against surroundings), so the air cools WITHOUT exchanging heat with its environment. When the rising air cools to its dew point temperature, condensation begins — water droplets or ice crystals form." }, { q: "Why does precipitation fall as SNOW rather than RAIN during a blizzard?", options: [ "Snow forms when two ice crystals collide and stick together in a turbulent cloud", "Temperatures must remain below freezing from the cloud base all the way to the ground so precipitation stays frozen", "Snow only forms from cP continental air masses; rain only forms from mT maritime air", "Low atmospheric pressure prevents water droplets from remaining liquid" ], correct: 1, explanation: "Whether you get snow or rain depends on the entire atmospheric temperature profile. If a warm layer exists aloft (above freezing), snow melts into rain. For blizzards, the cold polar air mass extends from the cloud level all the way to the surface — temperatures below 32°F throughout — so ice crystals fall as snow and stay frozen until they hit the ground." }, { q: "Why is moist air (carrying lots of water vapor) LESS dense than dry air at the same temperature?", options: [ "Water vapor molecules are heavier than nitrogen and oxygen, making moist air denser", "Water vapor molecules (H₂O, MW=18) are LIGHTER than nitrogen (N₂, MW=28) and oxygen (O₂, MW=32), so moist air has lower average molecular weight", "Water vapor adds extra pressure that spreads air molecules further apart", "Humidity has no effect on air density — only temperature matters" ], correct: 1, explanation: "This surprises many students! Water vapor (H₂O) has a molecular weight of 18, while N₂ = 28 and O₂ = 32. When water vapor replaces heavier N₂ and O₂ molecules in air, the average molecular weight DECREASES — making moist air LIGHTER than dry air. This is why warm, moist Gulf air rises so readily when it meets colder, drier polar air at a frontal boundary." }])```# Elaborate: Blizzards & Climate Change::: {.elaborate-box}## 🌡️ Will Blizzards Get Worse in a Warming World?This might seem like a contradiction — how can global warming lead to bigger snowstorms? Let's investigate with data.:::## The Paradox: More Warming → More Snow?It sounds counterintuitive, but a warmer atmosphere can actually fuel more intense winter storms in some regions. Here's why:```{ojs}//| echo: falseviewof showMechanism = Inputs.radio( ["Warmer = More Moisture", "Arctic Amplification", "Both Together"], {label: "Explore the mechanism:", value: "Warmer = More Moisture"})``````{ojs}//| echo: false{ const width = 750; const height = 400; const svg = d3.create("svg").attr("width", width).attr("height", height).attr("viewBox", `0 0 ${width} ${height}`); svg.append("rect").attr("width", width).attr("height", height).attr("fill", "#f8f9fa").attr("rx", 10); if (showMechanism === "Warmer = More Moisture") { svg.append("text").attr("x", 375).attr("y", 35).attr("text-anchor", "middle").attr("font-size", 18).attr("font-weight", "bold").text("🌡️ Warmer Air Holds More Water Vapor"); // Clausius-Clapeyron: ~7% more moisture per 1°C warming const tempData = d3.range(-10, 35, 1).map(t => ({temp: t, moisture: 3.5 * Math.exp(0.07 * (t + 10))})); // Simple bar-style chart const xScale = d3.scaleLinear().domain([-10, 34]).range([80, 700]); const yScale = d3.scaleLinear().domain([0, 30]).range([350, 60]); // Axes svg.append("line").attr("x1", 80).attr("y1", 350).attr("x2", 700).attr("y2", 350).attr("stroke", "#2d3436"); svg.append("line").attr("x1", 80).attr("y1", 60).attr("x2", 80).attr("y2", 350).attr("stroke", "#2d3436"); svg.append("text").attr("x", 390).attr("y", 390).attr("text-anchor", "middle").attr("font-size", 13).text("Temperature (°C)"); svg.append("text").attr("x", 30).attr("y", 200).attr("text-anchor", "middle").attr("font-size", 12).attr("transform", "rotate(-90, 30, 200)").text("Max Moisture (g/kg)"); // Curve svg.append("path").attr("d", d3.line().x(d => xScale(d.temp)).y(d => yScale(d.moisture)).curve(d3.curveBasis)(tempData)) .attr("stroke", "#e74c3c").attr("stroke-width", 3).attr("fill", "none"); // Annotation svg.append("rect").attr("x", 400).attr("y", 80).attr("width", 280).attr("height", 90).attr("fill", "#fff3e0").attr("rx", 8); svg.append("text").attr("x", 540).attr("y", 105).attr("text-anchor", "middle").attr("font-size", 13).attr("font-weight", "bold").text("Clausius-Clapeyron Relation:"); svg.append("text").attr("x", 540).attr("y", 125).attr("text-anchor", "middle").attr("font-size", 12).text("For every 1°C of warming, air can"); svg.append("text").attr("x", 540).attr("y", 145).attr("text-anchor", "middle").attr("font-size", 13).attr("font-weight", "bold").attr("fill", "#e74c3c").text("hold ~7% more water vapor"); svg.append("text").attr("x", 540).attr("y", 162).attr("text-anchor", "middle").attr("font-size", 12).text("→ More fuel for storms!"); } else if (showMechanism === "Arctic Amplification") { svg.append("text").attr("x", 375).attr("y", 35).attr("text-anchor", "middle").attr("font-size", 18).attr("font-weight", "bold").text("🧊 Arctic Amplification & the Jet Stream"); // Simplified diagram svg.append("rect").attr("x", 50).attr("y", 60).attr("width", 650).attr("height", 130).attr("fill", "#dfe6e9").attr("rx", 8); svg.append("text").attr("x", 375).attr("y", 90).attr("text-anchor", "middle").attr("font-size", 14).attr("font-weight", "bold").text("BEFORE: Strong temperature gradient → Tight, fast jet stream"); svg.append("path").attr("d", "M 80,150 Q 200,130 300,150 Q 400,170 500,150 Q 600,130 680,150") .attr("stroke", "#0984e3").attr("stroke-width", 4).attr("fill", "none"); svg.append("text").attr("x", 100).attr("y", 180).attr("font-size", 12).attr("fill", "#0984e3").text("Jet stream stays mostly straight → storms track predictably"); svg.append("rect").attr("x", 50).attr("y", 220).attr("width", 650).attr("height", 150).attr("fill", "#ffeaa7").attr("rx", 8); svg.append("text").attr("x", 375).attr("y", 250).attr("text-anchor", "middle").attr("font-size", 14).attr("font-weight", "bold").text("AFTER: Reduced gradient → Wavy, slow jet stream"); svg.append("path").attr("d", "M 80,310 Q 160,260 240,320 Q 320,380 400,300 Q 480,220 560,320 Q 640,370 680,310") .attr("stroke", "#e74c3c").attr("stroke-width", 4).attr("fill", "none"); svg.append("text").attr("x", 100).attr("y", 360).attr("font-size", 12).attr("fill", "#e74c3c").text("Jet stream develops deep waves → cold air plunges south, storms stall"); } else { svg.append("text").attr("x", 375).attr("y", 35).attr("text-anchor", "middle").attr("font-size", 18).attr("font-weight", "bold").text("🌨️ Combined Effect: More Intense Blizzards"); const boxes = [ {x: 50, y: 60, w: 300, h: 80, color: "#e74c3c", text: "🌡️ Warmer atmosphere\nholds ~7% more moisture per °C"}, {x: 400, y: 60, w: 300, h: 80, color: "#0984e3", text: "🧊 Arctic warming weakens\njet stream → deeper troughs"}, {x: 150, y: 180, w: 450, h: 60, color: "#6c5ce7", text: "When deep cold outbreaks meet extra-moist air..."}, {x: 100, y: 280, w: 550, h: 90, color: "#2d3436", text: "⛈️ RESULT: Potentially MORE INTENSE blizzards\neven as average winters get milder\n(fewer but stronger storms)"} ]; boxes.forEach(b => { svg.append("rect").attr("x", b.x).attr("y", b.y).attr("width", b.w).attr("height", b.h) .attr("fill", b.color).attr("opacity", 0.15).attr("rx", 10).attr("stroke", b.color).attr("stroke-width", 2); const lines = b.text.split("\n"); lines.forEach((line, i) => { svg.append("text").attr("x", b.x + b.w/2).attr("y", b.y + 25 + i * 20).attr("text-anchor", "middle") .attr("font-size", 13).attr("font-weight", i === 0 ? "bold" : "normal").text(line); }); }); // Connecting arrows svg.append("line").attr("x1", 200).attr("y1", 140).attr("x2", 300).attr("y2", 180).attr("stroke", "#636e72").attr("stroke-width", 2); svg.append("line").attr("x1", 550).attr("y1", 140).attr("x2", 450).attr("y2", 180).attr("stroke", "#636e72").attr("stroke-width", 2); svg.append("line").attr("x1", 375).attr("y1", 240).attr("x2", 375).attr("y2", 280).attr("stroke", "#636e72").attr("stroke-width", 2); } return svg.node();}```## Snowfall Trends: The Data```{ojs}//| echo: false// Extreme snowfall events in the US Northeast (simplified data)snowData = [ {decade: "1960s", extremeEvents: 3, avgSnowfall: 58}, {decade: "1970s", extremeEvents: 4, avgSnowfall: 62}, {decade: "1980s", extremeEvents: 3, avgSnowfall: 55}, {decade: "1990s", extremeEvents: 5, avgSnowfall: 52}, {decade: "2000s", extremeEvents: 6, avgSnowfall: 48}, {decade: "2010s", extremeEvents: 8, avgSnowfall: 45}, {decade: "2020s*", extremeEvents: 4, avgSnowfall: 42}]Plot.plot({ title: "Northeast US: Extreme Snowfall Events vs. Average Annual Snowfall", subtitle: "More extreme events even as average snowfall decreases", width: 700, height: 350, x: {label: "Decade", padding: 0.3}, y: {label: "Count / Inches"}, color: {legend: true}, marks: [ Plot.barY(snowData, {x: "decade", y: "extremeEvents", fill: "#3498db", title: d => `${d.extremeEvents} extreme events`, tip: true}), Plot.dot(snowData, {x: "decade", y: "avgSnowfall", fill: "#e74c3c", r: 8, tip: true}), Plot.line(snowData, {x: "decade", y: "avgSnowfall", stroke: "#e74c3c", strokeWidth: 2, tip: true}), Plot.text([{x: "2020s*", y: 48}], {x: "x", y: "y", text: d => "🔴 Avg Annual Snowfall (inches)", dx: -80, fill: "#e74c3c", fontSize: 11}), Plot.text([{x: "2020s*", y: 7}], {x: "x", y: "y", text: d => "🔵 Extreme events per decade", dx: -80, fill: "#3498db", fontSize: 11}) ]})``````{ojs}//| echo: falsebuildQuiz("climate-paradox", [ { q: "The NYC snowfall charts show total seasonal snowfall DECLINING over the 20th century. Does this mean blizzards are becoming less dangerous?", options: [ "Yes — less total snow means weaker storms across the board", "No — the peak single-month snowfall has INCREASED since the 2000s; fewer but more intense extreme events are possible", "Yes — warming temperatures prevent any future blizzard conditions", "No — the data shows both total and peak snowfall are increasing simultaneously" ], correct: 1, explanation: "This is the winter storm paradox! The 15-year rolling average of peak monthly snowfall INCREASED after 2000, even as seasonal totals declined. This means the number of small-to-moderate snowfall events is decreasing, but the most extreme individual storms may be getting more intense. The 2025-26 season already shows a 24.9-inch February — the highest single-month total in decades." }, { q: "The Clausius-Clapeyron relation: for every 1°C of warming, air holds ~7% more water vapor. What does this mean for blizzard intensity?", options: [ "Blizzards will disappear because warmer temperatures will keep precipitation above freezing", "When cold outbreaks DO occur, the extra moisture in the atmosphere can fuel heavier snowfall than the same cold event would have produced in a cooler era", "The 7% increase is negligible at the scale of weather systems", "More moisture only affects summer storms — not cold-season precipitation" ], correct: 1, explanation: "More atmospheric moisture = more potential snowfall when temperatures are cold enough. Even if severe cold outbreaks become less frequent, when they DO occur (driven by a wavy jet stream), they can tap into an atmosphere loaded with extra water vapor. This extra fuel can produce heavier snowfall than the same cold air mass would have in the 1950s or 1960s." }, { q: "What is 'Arctic amplification' and how does it connect to blizzards in the northeastern US?", options: [ "The Arctic growing larger, physically blocking cold air from moving southward", "The Arctic warming 2–4× faster than the global average, weakening the polar jet stream so it develops larger meanders that can send Arctic air deep into the US", "A natural 60-year climate cycle that has always caused periodic blizzard outbreaks", "Arctic amplification only affects sea ice and marine ecosystems with no impact on mid-latitude weather" ], correct: 1, explanation: "Arctic amplification reduces the temperature difference between the poles and mid-latitudes. This temperature contrast is what keeps the polar jet stream tight and fast. A weaker, wavier jet stream develops deeper Rossby waves — meanders that can dip Arctic air far south into the US, even into Georgia or Texas. When these cold outbreaks encounter extra-moist air, intense blizzards can result." }])``````{=html}<div id="blizzard-myths" style="background:linear-gradient(135deg,#1a1a2e 0%,#16213e 50%,#0f3460 100%); color:white; padding:30px; border-radius:16px; margin:30px 0; font-family:'Inter',sans-serif; box-shadow:0 20px 60px rgba(0,0,0,0.3);"> <h2 style="font-family:'Space Grotesk',sans-serif; font-size:1.8em; margin:0 0 8px 0; background:linear-gradient(135deg,#74b9ff,#a29bfe); -webkit-background-clip:text; -webkit-text-fill-color:transparent;"> 🌨️ Blizzard Myths vs. Facts </h2> <p style="color:#94a3b8; margin:0 0 20px 0; font-size:0.95em;">Think you know blizzards? Is each statement a MYTH or a FACT?</p> <div style="display:flex; justify-content:space-between; align-items:center; background:rgba(255,255,255,0.05); padding:12px 20px; border-radius:12px; margin-bottom:20px;"> <div id="bmf-progress" style="color:#94a3b8; font-size:0.9em;">Card 1 of 10</div> <div style="color:#94a3b8; font-size:0.9em;">Score: <span id="bmf-score" style="color:#74b9ff; font-weight:700;">0</span> / <span id="bmf-total">0</span></div> </div> <div id="bmf-card" style="background:rgba(255,255,255,0.08); border-radius:14px; padding:28px; min-height:100px; display:flex; align-items:center; justify-content:center; text-align:center; font-size:1.1em; font-weight:600; line-height:1.6; border:1.5px solid rgba(255,255,255,0.1);"></div> <div style="display:flex; gap:12px; margin-top:16px;"> <button id="bmf-myth" onclick="bmfAnswer('Myth')" style="flex:1; padding:14px; border:none; border-radius:10px; background:linear-gradient(135deg,#e17055,#d63031); color:white; font-size:1em; font-weight:700; cursor:pointer; box-shadow:0 4px 15px rgba(214,48,49,0.3);">❌ MYTH</button> <button id="bmf-fact" onclick="bmfAnswer('Fact')" style="flex:1; padding:14px; border:none; border-radius:10px; background:linear-gradient(135deg,#00b894,#00cec9); color:white; font-size:1em; font-weight:700; cursor:pointer; box-shadow:0 4px 15px rgba(0,184,148,0.3);">✅ FACT</button> </div> <div id="bmf-feedback" style="margin-top:16px; padding:16px; border-radius:10px; display:none; font-size:0.95em; line-height:1.6;"></div> <button id="bmf-next" onclick="bmfNext()" style="display:none; margin-top:12px; width:100%; padding:12px; border:2px solid rgba(255,255,255,0.2); border-radius:10px; background:transparent; color:white; font-size:0.95em; font-weight:600; cursor:pointer;">Next Card →</button> <div id="bmf-done" style="display:none; text-align:center; padding:20px;"> <div style="font-size:2.5em; margin-bottom:10px;">🎉</div> <div style="font-size:1.3em; font-weight:700;">Quiz complete!</div> <div style="color:#94a3b8; margin:8px 0;">Final score: <span id="bmf-final" style="color:#74b9ff; font-weight:700;"></span> / 10</div> <button onclick="bmfRestart()" style="margin-top:12px; padding:10px 24px; border:2px solid #74b9ff; border-radius:20px; background:transparent; color:#74b9ff; font-size:0.9em; cursor:pointer; font-weight:600;">Play Again</button> </div></div><script>(function() { var cards = [ { text: "More total annual snowfall always means a worse blizzard season.", type: "Myth", explanation: "High totals don't equal more blizzards. A season with 60 inches spread over many small storms differs greatly from one with 30 inches in one catastrophic event. NYC data shows total snowfall DECLINING while peak storm intensity INCREASES." }, { text: "The official NWS blizzard definition requires sustained 35+ mph winds AND visibility under one-quarter mile for at least 3 hours.", type: "Fact", explanation: "Correct — heavy snow alone is NOT a blizzard by definition. You need the combination of heavy snow or blowing snow reducing visibility to under 1/4 mile, PLUS sustained 35+ mph winds, for at least 3 consecutive hours." }, { text: "Because the Arctic is warming, blizzards in the northeastern US will soon completely disappear.", type: "Myth", explanation: "Arctic warming makes the jet stream WAVIER, allowing cold Arctic air to plunge deeper south more often. Combined with more moisture in a warmer atmosphere, the most extreme blizzards may become MORE severe even as average winters get milder." }, { text: "Warm ocean temperatures can fuel more powerful blizzards.", type: "Fact", explanation: "Yes! The Clausius-Clapeyron relation: warmer seawater evaporates more, loading the atmosphere with extra moisture. When cold air sweeps in, it can produce far heavier snowfall than the same storm would in a cooler-ocean era." }, { text: "The coldest Arctic air produces the heaviest snowfall.", type: "Myth", explanation: "Very cold Arctic air is extremely DRY. It holds almost no moisture. The heaviest snowfall requires temperatures just below 32°F with a moisture-rich mT air source nearby — not the deepest Arctic cold. That's why the biggest snowstorms often occur at 25-30°F, not -10°F." }, { text: "Wind always flows from HIGH pressure toward LOW pressure at the surface.", type: "Fact", explanation: "This is the pressure gradient force — the fundamental driver of ALL surface wind. Air flows 'downhill' from high to low pressure, just as water flows downhill. The steeper the gradient (bigger pressure difference over shorter distance), the faster the wind." }, { text: "The 'L' (low pressure) on a weather map marks where the clearest, calmest weather will be found.", type: "Myth", explanation: "The opposite! In a low-pressure center, air converges and RISES — it cools, condenses, and produces clouds and precipitation. CLEAR weather is found under HIGH pressure centers, where air sinks, compresses, and warms, inhibiting cloud formation." }, { text: "Gulf of Mexico moisture is the primary source of water vapor for major East Coast blizzards.", type: "Fact", explanation: "For northeast US nor'easters and major blizzards, warm moist mT air from the Gulf of Mexico provides the critical moisture. It flows northward, collides with cold polar air, and gets lifted — producing heavy snowfall. Without Gulf moisture, there is not enough water vapor for significant snow accumulation." }, { text: "All mid-latitude cyclones (blizzards included) eventually weaken and die when the cold front overtakes the warm front.", type: "Fact", explanation: "Yes — this occlusion process is how ALL mid-latitude cyclones die. When the cold front catches the warm front, warm air is completely lifted off the surface. With no more warm vs. cold temperature contrast, the pressure gradient weakens, the system fills, and the storm dissipates." }, { text: "Climate change will only make summer storms more extreme — winter blizzards will be unaffected.", type: "Myth", explanation: "A warmer atmosphere holds more water vapor year-round (Clausius-Clapeyron). When cold air still exists in winter (guaranteed for decades), it encounters this extra moisture — potentially producing more intense precipitation events. Additionally, Arctic amplification can increase the frequency and depth of polar air outbreaks into the mid-latitudes." } ]; var idx = 0, score = 0, answered = 0, cardAnswered = false; function updateCard() { if (idx >= cards.length) { document.getElementById('bmf-card').style.display = 'none'; document.getElementById('bmf-myth').style.display = 'none'; document.getElementById('bmf-fact').style.display = 'none'; document.getElementById('bmf-feedback').style.display = 'none'; document.getElementById('bmf-next').style.display = 'none'; document.getElementById('bmf-done').style.display = 'block'; document.getElementById('bmf-final').textContent = score; return; } document.getElementById('bmf-card').textContent = cards[idx].text; document.getElementById('bmf-progress').textContent = 'Card ' + (idx + 1) + ' of ' + cards.length; document.getElementById('bmf-feedback').style.display = 'none'; document.getElementById('bmf-next').style.display = 'none'; document.getElementById('bmf-myth').disabled = false; document.getElementById('bmf-fact').disabled = false; document.getElementById('bmf-myth').style.opacity = '1'; document.getElementById('bmf-fact').style.opacity = '1'; cardAnswered = false; } window.bmfAnswer = function(choice) { if (cardAnswered) return; cardAnswered = true; answered++; var card = cards[idx]; var correct = (choice === card.type); if (correct) score++; document.getElementById('bmf-score').textContent = score; document.getElementById('bmf-total').textContent = answered; var fb = document.getElementById('bmf-feedback'); fb.style.display = 'block'; fb.style.background = correct ? 'rgba(0,184,148,0.2)' : 'rgba(214,48,49,0.2)'; fb.style.border = '1px solid ' + (correct ? '#00b894' : '#e17055'); fb.innerHTML = '<strong>' + (correct ? '✅ Correct! ' : '❌ That\'s a ' + card.type + '! ') + '</strong>' + card.explanation; document.getElementById('bmf-myth').disabled = true; document.getElementById('bmf-fact').disabled = true; document.getElementById('bmf-myth').style.opacity = (choice === 'Myth') ? '1' : '0.5'; document.getElementById('bmf-fact').style.opacity = (choice === 'Fact') ? '1' : '0.5'; document.getElementById('bmf-next').style.display = 'block'; }; window.bmfNext = function() { idx++; updateCard(); }; window.bmfRestart = function() { idx = 0; score = 0; answered = 0; document.getElementById('bmf-card').style.display = 'flex'; document.getElementById('bmf-myth').style.display = 'block'; document.getElementById('bmf-fact').style.display = 'block'; document.getElementById('bmf-done').style.display = 'none'; document.getElementById('bmf-score').textContent = '0'; document.getElementById('bmf-total').textContent = '0'; updateCard(); }; updateCard();})();</script>```# Evaluate: Putting It All Together::: {.evaluate-box}## ✅ Assessment: Build Your Blizzard ModelYou now have all the pieces to explain how blizzards form and how they may change with climate change. Let's check your understanding!:::::: {.evaluate-box}### 🧠 Comprehensive Unit QuizTest your complete understanding of the blizzard formation model from wind to climate change!:::```{ojs}//| echo: falsebuildUnitQuiz("blizzard-unit-quiz", "🧠 Comprehensive Unit Quiz", [ { q: "What is the FUNDAMENTAL cause of all surface wind?", options: [ "Temperature differences between Earth's surface and the upper atmosphere", "Pressure differences created by uneven heating — air flows from HIGH to LOW pressure", "Earth's rotation deflecting air molecules sideways (Coriolis Effect)", "Evaporation of ocean water creating updrafts near the equator" ], correct: 1, explanation: "The pressure gradient force — pushing air from high to low pressure — is the fundamental driver of all wind. Uneven heating creates temperature differences → density differences → pressure differences. The Coriolis Effect then curves the wind, but the pressure gradient is always the primary driver." }, { q: "A cold, dry air mass that forms over central Canada in January is classified as:", options: ["mT — maritime Tropical", "cP — continental Polar", "mP — maritime Polar", "cA — continental Arctic"], correct: 1, explanation: "Continental Polar (cP): 'c' = forms over LAND (dry), 'P' = polar latitude (cold). This is the primary cold air mass responsible for most US blizzards. True cA (continental Arctic) forms only over the permanent Arctic ice cap and is even more extreme." }, { q: "In a mature mid-latitude cyclone, where are blizzard conditions (heavy snow + extreme winds) most likely to occur?", options: [ "Ahead of the warm front, in the warm and moist sector", "North and west of the low center — where cold air, strong winds, and heavy precipitation all overlap", "Exactly at the eye of the low-pressure center, where pressure is lowest", "South of the cold front, in the retreating warm air mass" ], correct: 1, explanation: "The blizzard zone needs all three ingredients at once: (1) sub-freezing temperatures from cold polar air wrapping north/west; (2) extreme winds from the steep pressure gradient near the low; (3) heavy precipitation from moist air being lifted along the cold front. Only the north/west quadrant has all three!" }, { q: "What process causes water vapor to condense into snow at a frontal boundary?", options: [ "Compression heating increases air density until condensation is forced", "Adiabatic cooling — rising air expands as pressure decreases, cooling until it reaches its dew point", "Cold polar air absorbs moisture from warm air molecules at the surface", "Solar radiation activates ice nuclei already present in the cloud" ], correct: 1, explanation: "Adiabatic cooling: warm moist air is forced UP at the front, moves into lower-pressure regions, EXPANDS (doing work against surroundings), and therefore COOLS without exchanging heat. When it cools to its dew point, water vapor condenses. If temperatures stay below 32°F from cloud to ground, precipitation falls and stays as snow." }, { q: "Climate change and the Clausius-Clapeyron relation (~7% more moisture per °C of warming): what does this mean for blizzard intensity?", options: [ "Blizzards will disappear as warmer temperatures prevent any freezing precipitation", "When cold outbreaks occur, extra atmospheric moisture can fuel heavier snowfall than the same cold event would have produced in a cooler era", "The 7% change is too small to have any measurable effect on storm precipitation", "More moisture only makes summer convective storms stronger, not winter cyclones" ], correct: 1, explanation: "More atmospheric moisture = more potential snowfall when temperatures are cold enough. Even if severe cold outbreaks become less frequent, when they DO occur (driven by Arctic amplification and a wavy jet stream), they encounter an atmosphere loaded with extra water vapor. This extra fuel can produce historically extreme snowfall — the 'less frequent but more intense' pattern visible in the NYC snowfall data." }, { q: "Arctic amplification (the Arctic warming 2–4× faster than the global average) affects mid-latitude blizzards by:", options: [ "Physically blocking cold air from escaping the Arctic region", "Reducing the temperature contrast that keeps the polar jet stream tight, causing it to develop deep meanders that send Arctic air far south", "Only affecting sea ice and Arctic marine ecosystems with no mid-latitude impact", "Permanently locking cold air in the Arctic and reducing blizzard risk in the US" ], correct: 1, explanation: "The polar jet stream is maintained by the temperature contrast between cold polar and warm tropical air. Arctic amplification weakens this contrast, slowing and waving the jet stream. Deeper Rossby wave troughs can send Arctic air deep into the southern US. When this frigid air encounters a moisture-laden atmosphere (thanks to warm oceans), catastrophic blizzards can result — exactly the conditions that made Winter Storm Jonas possible." }, { q: "What is the official National Weather Service (NWS) definition of a blizzard?", options: [ "Any storm that drops more than 12 inches of snow in 24 hours", "Sustained 35+ mph winds AND visibility under ¼ mile due to snow or blowing snow for at least 3 consecutive hours", "Temperatures below 10°F combined with any snowfall", "A winter storm watch upgraded to a warning by the NWS" ], correct: 1, explanation: "Heavy snow alone is NOT a blizzard. You need the trifecta: 35+ mph sustained winds, visibility reduced to under ¼ mile by falling or blowing snow, and both conditions lasting at least 3 continuous hours. A storm can produce 2 feet of snow and still not be a blizzard if wind speeds are low." }, { q: "Due to the Coriolis Effect, surface winds spiral INTO a Northern Hemisphere low-pressure center in which direction?", options: [ "Clockwise — same as water draining in a bathtub", "Counterclockwise — deflected to the right as it flows inward", "Directly inward with no rotation — the Coriolis Effect only applies at high altitude", "Outward and clockwise — away from the low-pressure center" ], correct: 1, explanation: "In the Northern Hemisphere, the Coriolis Effect deflects moving air to the RIGHT. As air rushes inward toward a low, those rightward deflections produce a counterclockwise spin. This is why all Northern Hemisphere lows (hurricanes, mid-latitude cyclones, blizzard-producing storms) rotate counterclockwise. Southern Hemisphere lows spin clockwise." }, { q: "Along a WARM front (warm air advancing over cold air), what type of precipitation and cloud sequence is typically observed?", options: [ "Sudden, violent thunderstorms with large hail along a sharp narrow band", "Gradual, widespread precipitation — high cirrus clouds hours in advance, then thickening stratus clouds, then steady rain or snow", "Clear skies immediately followed by a brief snow squall", "Only freezing rain, because warm air cannot produce snowfall" ], correct: 1, explanation: "Warm fronts have a very gentle slope — warm air glides slowly up and over cold air across hundreds of miles. This gradual lifting produces layered stratus clouds that thicken over 12–24 hours before precipitation begins. The sequence is: cirrus → cirrostratus → altostratus → nimbostratus → steady precipitation. Contrast with cold fronts, which are steep and produce short, violent weather." }, { q: "Why do the HEAVIEST snowfalls usually occur at temperatures between 25–32°F rather than at -10°F?", options: [ "Snowflakes can only form above 20°F; below that, precipitation falls as sleet", "Very cold Arctic air is extremely dry and holds almost no moisture — heavy snow requires air near 32°F combined with a nearby warm moisture source", "Wind speeds are always higher at -10°F, which prevents snow from accumulating", "At temperatures below 20°F, precipitation freezes before leaving the cloud" ], correct: 1, explanation: "The Clausius-Clapeyron equation tells us that colder air holds exponentially LESS water vapor. Air at -10°F is essentially a desert in terms of moisture content. The biggest snowstorms combine two air masses: cold enough to freeze precipitation (25–32°F is 'optimal snow-making temperature') AND close to a warm, moist source like the Gulf of Mexico or Atlantic Ocean. Deep Arctic cold alone never produces historic snowfall totals." }])```# Summary: Key Takeaways| Concept | Key Idea ||---------|----------|| **Wind** | Caused by pressure differences from uneven heating || **Air Masses** | Large bodies of air with uniform temp & humidity || **Fronts** | Boundaries between air masses; where weather happens || **Mid-Latitude Cyclone** | Low-pressure system with warm & cold fronts; produces blizzards || **Precipitation** | Warm moist air rises at front → cools → condenses → snow/rain || **Climate Connection** | Warmer air holds more moisture + weakened jet stream = potentially more intense blizzards |**Next up:** We'll investigate **why storms follow the paths they do** — and whether those paths are changing. 🗺️